зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1350646: Part 8 - Remove SDK page-mod modules. r=Mossop
MozReview-Commit-ID: C3JrCITSjj6 --HG-- extra : rebase_source : 579e28e4eaecd3df5db061de0837e34473fa441d
This commit is contained in:
Родитель
e4716b5f70
Коммит
e195d182cf
|
@ -46,7 +46,6 @@ modules = [
|
||||||
'sdk/content/l10n-html.js',
|
'sdk/content/l10n-html.js',
|
||||||
'sdk/content/loader.js',
|
'sdk/content/loader.js',
|
||||||
'sdk/content/mod.js',
|
'sdk/content/mod.js',
|
||||||
'sdk/content/page-mod.js',
|
|
||||||
'sdk/content/sandbox.js',
|
'sdk/content/sandbox.js',
|
||||||
'sdk/content/sandbox/events.js',
|
'sdk/content/sandbox/events.js',
|
||||||
'sdk/content/tab-events.js',
|
'sdk/content/tab-events.js',
|
||||||
|
@ -104,8 +103,6 @@ modules = [
|
||||||
'sdk/net/xhr.js',
|
'sdk/net/xhr.js',
|
||||||
'sdk/notifications.js',
|
'sdk/notifications.js',
|
||||||
'sdk/output/system.js',
|
'sdk/output/system.js',
|
||||||
'sdk/page-mod.js',
|
|
||||||
'sdk/page-mod/match-pattern.js',
|
|
||||||
'sdk/passwords.js',
|
'sdk/passwords.js',
|
||||||
'sdk/passwords/utils.js',
|
'sdk/passwords/utils.js',
|
||||||
'sdk/platform/xpcom.js',
|
'sdk/platform/xpcom.js',
|
||||||
|
@ -169,9 +166,7 @@ modules = [
|
||||||
'sdk/util/deprecate.js',
|
'sdk/util/deprecate.js',
|
||||||
'sdk/util/dispatcher.js',
|
'sdk/util/dispatcher.js',
|
||||||
'sdk/util/list.js',
|
'sdk/util/list.js',
|
||||||
'sdk/util/match-pattern.js',
|
|
||||||
'sdk/util/object.js',
|
'sdk/util/object.js',
|
||||||
'sdk/util/rules.js',
|
|
||||||
'sdk/util/sequence.js',
|
'sdk/util/sequence.js',
|
||||||
'sdk/util/uuid.js',
|
'sdk/util/uuid.js',
|
||||||
'sdk/view/core.js',
|
'sdk/view/core.js',
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.metadata = {
|
|
||||||
"stability": "stable"
|
|
||||||
};
|
|
||||||
|
|
||||||
lazyRequire(this, '../content/utils', 'getAttachEventType');
|
|
||||||
const { Class } = require('../core/heritage');
|
|
||||||
const { Disposable } = require('../core/disposable');
|
|
||||||
lazyRequire(this, './worker-child', 'WorkerChild');
|
|
||||||
const { EventTarget } = require('../event/target');
|
|
||||||
const { on, emit, once, setListeners } = require('../event/core');
|
|
||||||
lazyRequire(this, '../dom/events',{'on': 'domOn', 'removeListener': 'domOff'});
|
|
||||||
lazyRequire(this, '../util/object', "merge");
|
|
||||||
lazyRequire(this, '../window/utils', "getFrames");
|
|
||||||
lazyRequire(this, '../private-browsing/utils', "ignoreWindow");
|
|
||||||
lazyRequire(this, '../stylesheet/style', 'Style');
|
|
||||||
lazyRequire(this, '../content/mod', 'attach', 'detach');
|
|
||||||
lazyRequire(this, '../util/rules', 'Rules');
|
|
||||||
lazyRequire(this, '../util/uuid', 'uuid');
|
|
||||||
const { frames, process } = require('../remote/child');
|
|
||||||
|
|
||||||
const pagemods = new Map();
|
|
||||||
const styles = new WeakMap();
|
|
||||||
var styleFor = (mod) => styles.get(mod);
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PageMod constructor (exported below).
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
const ChildPageMod = Class({
|
|
||||||
implements: [
|
|
||||||
EventTarget,
|
|
||||||
Disposable,
|
|
||||||
],
|
|
||||||
setup: function PageMod(model) {
|
|
||||||
merge(this, model);
|
|
||||||
|
|
||||||
// Set listeners on {PageMod} itself, not the underlying worker,
|
|
||||||
// like `onMessage`, as it'll get piped.
|
|
||||||
setListeners(this, model);
|
|
||||||
|
|
||||||
function* deserializeRules(rules) {
|
|
||||||
for (let rule of rules) {
|
|
||||||
yield rule.type == "string" ? rule.value
|
|
||||||
: new RegExp(rule.pattern, rule.flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let include = [...deserializeRules(this.include)];
|
|
||||||
this.include = Rules();
|
|
||||||
this.include.add.apply(this.include, include);
|
|
||||||
|
|
||||||
let exclude = [...deserializeRules(this.exclude)];
|
|
||||||
this.exclude = Rules();
|
|
||||||
this.exclude.add.apply(this.exclude, exclude);
|
|
||||||
|
|
||||||
if (this.contentStyle || this.contentStyleFile) {
|
|
||||||
styles.set(this, Style({
|
|
||||||
uri: this.contentStyleFile,
|
|
||||||
source: this.contentStyle
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pagemods.set(this.id, this);
|
|
||||||
this.seenDocuments = new WeakMap();
|
|
||||||
|
|
||||||
// `applyOnExistingDocuments` has to be called after `pagemods.add()`
|
|
||||||
// otherwise its calls to `onContent` method won't do anything.
|
|
||||||
if (this.attachTo.includes('existing'))
|
|
||||||
applyOnExistingDocuments(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
dispose: function() {
|
|
||||||
let style = styleFor(this);
|
|
||||||
if (style)
|
|
||||||
detach(style);
|
|
||||||
|
|
||||||
for (let i in this.include)
|
|
||||||
this.include.remove(this.include[i]);
|
|
||||||
|
|
||||||
pagemods.delete(this.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function onContentWindow({ target: document }) {
|
|
||||||
// Return if we have no pagemods
|
|
||||||
if (pagemods.size === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let window = document.defaultView;
|
|
||||||
// XML documents don't have windows, and we don't yet support them.
|
|
||||||
if (!window)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Frame event listeners are bound to the frame the event came from by default
|
|
||||||
let frame = this;
|
|
||||||
// We apply only on documents in tabs of Firefox
|
|
||||||
if (!frame.isTab)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// When the tab is private, only addons with 'private-browsing' flag in
|
|
||||||
// their package.json can apply content script to private documents
|
|
||||||
if (ignoreWindow(window))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (let pagemod of pagemods.values()) {
|
|
||||||
if (modMatchesURI(pagemod, window.location.href))
|
|
||||||
onContent(pagemod, window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frames.addEventListener("DOMDocElementInserted", onContentWindow, true);
|
|
||||||
|
|
||||||
function applyOnExistingDocuments (mod) {
|
|
||||||
for (let frame of frames) {
|
|
||||||
// Fake a newly created document
|
|
||||||
let window = frame.content;
|
|
||||||
// on startup with e10s, contentWindow might not exist yet,
|
|
||||||
// in which case we will get notified by "document-element-inserted".
|
|
||||||
if (!window || !window.frames)
|
|
||||||
return;
|
|
||||||
let uri = window.location.href;
|
|
||||||
if (mod.attachTo.includes("top") && modMatchesURI(mod, uri))
|
|
||||||
onContent(mod, window);
|
|
||||||
if (mod.attachTo.includes("frame"))
|
|
||||||
getFrames(window).
|
|
||||||
filter(iframe => modMatchesURI(mod, iframe.location.href)).
|
|
||||||
forEach(frame => onContent(mod, frame));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWorker(mod, window) {
|
|
||||||
let workerId = String(uuid());
|
|
||||||
|
|
||||||
// Instruct the parent to connect to this worker. Do this first so the parent
|
|
||||||
// side is connected before the worker attempts to send any messages there
|
|
||||||
let frame = frames.getFrameForWindow(window.top);
|
|
||||||
frame.port.emit('sdk/page-mod/worker-create', mod.id, {
|
|
||||||
id: workerId,
|
|
||||||
url: window.location.href
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a child worker and notify the parent
|
|
||||||
let worker = WorkerChild({
|
|
||||||
id: workerId,
|
|
||||||
window: window,
|
|
||||||
contentScript: mod.contentScript,
|
|
||||||
contentScriptFile: mod.contentScriptFile,
|
|
||||||
contentScriptOptions: mod.contentScriptOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
once(worker, 'detach', () => worker.destroy());
|
|
||||||
}
|
|
||||||
|
|
||||||
function onContent (mod, window) {
|
|
||||||
let isTopDocument = window.top === window;
|
|
||||||
// Is a top level document and `top` is not set, ignore
|
|
||||||
if (isTopDocument && !mod.attachTo.includes("top"))
|
|
||||||
return;
|
|
||||||
// Is a frame document and `frame` is not set, ignore
|
|
||||||
if (!isTopDocument && !mod.attachTo.includes("frame"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// ensure we attach only once per document
|
|
||||||
let seen = mod.seenDocuments;
|
|
||||||
if (seen.has(window.document))
|
|
||||||
return;
|
|
||||||
seen.set(window.document, true);
|
|
||||||
|
|
||||||
let style = styleFor(mod);
|
|
||||||
if (style)
|
|
||||||
attach(style, window);
|
|
||||||
|
|
||||||
// Immediately evaluate content script if the document state is already
|
|
||||||
// matching contentScriptWhen expectations
|
|
||||||
if (isMatchingAttachState(mod, window)) {
|
|
||||||
createWorker(mod, window);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let eventName = getAttachEventType(mod) || 'load';
|
|
||||||
domOn(window, eventName, function onReady (e) {
|
|
||||||
if (e.target.defaultView !== window)
|
|
||||||
return;
|
|
||||||
domOff(window, eventName, onReady, true);
|
|
||||||
createWorker(mod, window);
|
|
||||||
|
|
||||||
// Attaching is asynchronous so if the document is already loaded we will
|
|
||||||
// miss the pageshow event so send a synthetic one.
|
|
||||||
if (window.document.readyState == "complete") {
|
|
||||||
mod.on('attach', worker => {
|
|
||||||
try {
|
|
||||||
worker.send('pageshow');
|
|
||||||
emit(worker, 'pageshow');
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
// This can fail if an earlier attach listener destroyed the worker
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMatchingAttachState (mod, window) {
|
|
||||||
let state = window.document.readyState;
|
|
||||||
return 'start' === mod.contentScriptWhen ||
|
|
||||||
// Is `load` event already dispatched?
|
|
||||||
'complete' === state ||
|
|
||||||
// Is DOMContentLoaded already dispatched and waiting for it?
|
|
||||||
('ready' === mod.contentScriptWhen && state === 'interactive')
|
|
||||||
}
|
|
||||||
|
|
||||||
process.port.on('sdk/page-mod/create', (process, model) => {
|
|
||||||
if (pagemods.has(model.id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
new ChildPageMod(model);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.port.on('sdk/page-mod/destroy', (process, id) => {
|
|
||||||
let mod = pagemods.get(id);
|
|
||||||
if (mod)
|
|
||||||
mod.destroy();
|
|
||||||
});
|
|
|
@ -1,190 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.metadata = {
|
|
||||||
"stability": "stable"
|
|
||||||
};
|
|
||||||
|
|
||||||
const { contract: loaderContract } = require('./content/loader');
|
|
||||||
const { contract } = require('./util/contract');
|
|
||||||
const { WorkerHost, connect } = require('./content/utils');
|
|
||||||
const { Class } = require('./core/heritage');
|
|
||||||
const { Disposable } = require('./core/disposable');
|
|
||||||
lazyRequire(this, './content/worker', "Worker");
|
|
||||||
const { EventTarget } = require('./event/target');
|
|
||||||
lazyRequire(this, './event/core', "on", "emit", "once", "setListeners");
|
|
||||||
lazyRequire(this, './lang/type', "isRegExp", "isUndefined");
|
|
||||||
const { merge, omit } = require('./util/object');
|
|
||||||
lazyRequire(this, "./util/array", "remove", "has", "hasAny");
|
|
||||||
lazyRequire(this, "./util/rules", "Rules");
|
|
||||||
const { processes, frames, remoteRequire } = require('./remote/parent');
|
|
||||||
remoteRequire('sdk/content/page-mod');
|
|
||||||
|
|
||||||
const pagemods = new Map();
|
|
||||||
const workers = new Map();
|
|
||||||
const models = new WeakMap();
|
|
||||||
var modelFor = (mod) => models.get(mod);
|
|
||||||
var workerFor = (mod) => workers.get(mod)[0];
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
var isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string';
|
|
||||||
|
|
||||||
var PAGEMOD_ID = 0;
|
|
||||||
|
|
||||||
// Validation Contracts
|
|
||||||
const modOptions = {
|
|
||||||
// contentStyle* / contentScript* are sharing the same validation constraints,
|
|
||||||
// so they can be mostly reused, except for the messages.
|
|
||||||
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
|
|
||||||
msg: 'The `contentStyle` option must be a string or an array of strings.'
|
|
||||||
}),
|
|
||||||
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
|
|
||||||
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
|
|
||||||
}),
|
|
||||||
include: {
|
|
||||||
is: ['string', 'array', 'regexp'],
|
|
||||||
ok: (rule) => {
|
|
||||||
if (isRegExpOrString(rule))
|
|
||||||
return true;
|
|
||||||
if (Array.isArray(rule) && rule.length > 0)
|
|
||||||
return rule.every(isRegExpOrString);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.'
|
|
||||||
},
|
|
||||||
exclude: {
|
|
||||||
is: ['string', 'array', 'regexp', 'undefined'],
|
|
||||||
ok: (rule) => {
|
|
||||||
if (isRegExpOrString(rule) || isUndefined(rule))
|
|
||||||
return true;
|
|
||||||
if (Array.isArray(rule) && rule.length > 0)
|
|
||||||
return rule.every(isRegExpOrString);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
msg: 'If set, the `exclude` option must always contain at least one ' +
|
|
||||||
'rule as a string, regular expression, or an array of strings and ' +
|
|
||||||
'regular expressions.'
|
|
||||||
},
|
|
||||||
attachTo: {
|
|
||||||
is: ['string', 'array', 'undefined'],
|
|
||||||
map: function (attachTo) {
|
|
||||||
if (!attachTo) return ['top', 'frame'];
|
|
||||||
if (typeof attachTo === 'string') return [attachTo];
|
|
||||||
return attachTo;
|
|
||||||
},
|
|
||||||
ok: function (attachTo) {
|
|
||||||
return hasAny(attachTo, ['top', 'frame']) &&
|
|
||||||
attachTo.every(has.bind(null, ['top', 'frame', 'existing']));
|
|
||||||
},
|
|
||||||
msg: 'The `attachTo` option must be a string or an array of strings. ' +
|
|
||||||
'The only valid options are "existing", "top" and "frame", and must ' +
|
|
||||||
'contain at least "top" or "frame" values.'
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const modContract = contract(merge({}, loaderContract.rules, modOptions));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PageMod constructor (exported below).
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
const PageMod = Class({
|
|
||||||
implements: [
|
|
||||||
modContract.properties(modelFor),
|
|
||||||
EventTarget,
|
|
||||||
Disposable,
|
|
||||||
],
|
|
||||||
extends: WorkerHost(workerFor),
|
|
||||||
setup: function PageMod(options) {
|
|
||||||
let mod = this;
|
|
||||||
let model = modContract(options);
|
|
||||||
models.set(this, model);
|
|
||||||
model.id = PAGEMOD_ID++;
|
|
||||||
|
|
||||||
let include = model.include;
|
|
||||||
model.include = Rules();
|
|
||||||
model.include.add.apply(model.include, [].concat(include));
|
|
||||||
|
|
||||||
let exclude = isUndefined(model.exclude) ? [] : model.exclude;
|
|
||||||
model.exclude = Rules();
|
|
||||||
model.exclude.add.apply(model.exclude, [].concat(exclude));
|
|
||||||
|
|
||||||
// Set listeners on {PageMod} itself, not the underlying worker,
|
|
||||||
// like `onMessage`, as it'll get piped.
|
|
||||||
setListeners(this, options);
|
|
||||||
|
|
||||||
pagemods.set(model.id, this);
|
|
||||||
workers.set(this, []);
|
|
||||||
|
|
||||||
function* serializeRules(rules) {
|
|
||||||
for (let rule of rules) {
|
|
||||||
yield isRegExp(rule) ? { type: "regexp", pattern: rule.source, flags: rule.flags }
|
|
||||||
: { type: "string", value: rule };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.childOptions = omit(model, ["include", "exclude", "contentScriptOptions"]);
|
|
||||||
model.childOptions.include = [...serializeRules(model.include)];
|
|
||||||
model.childOptions.exclude = [...serializeRules(model.exclude)];
|
|
||||||
model.childOptions.contentScriptOptions = model.contentScriptOptions ?
|
|
||||||
JSON.stringify(model.contentScriptOptions) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
processes.port.emit('sdk/page-mod/create', model.childOptions);
|
|
||||||
},
|
|
||||||
|
|
||||||
dispose: function(reason) {
|
|
||||||
processes.port.emit('sdk/page-mod/destroy', modelFor(this).id);
|
|
||||||
pagemods.delete(modelFor(this).id);
|
|
||||||
workers.delete(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy: function(reason) {
|
|
||||||
// Explicit destroy call, i.e. not via unload so destroy the workers
|
|
||||||
let list = workers.get(this);
|
|
||||||
if (!list)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Triggers dispose which will cause the child page-mod to be destroyed
|
|
||||||
Disposable.prototype.destroy.call(this, reason);
|
|
||||||
|
|
||||||
// Destroy any active workers
|
|
||||||
for (let worker of list)
|
|
||||||
worker.destroy(reason);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports.PageMod = PageMod;
|
|
||||||
|
|
||||||
// Whenever a new process starts send over the list of page-mods
|
|
||||||
processes.forEvery(process => {
|
|
||||||
for (let mod of pagemods.values())
|
|
||||||
process.port.emit('sdk/page-mod/create', modelFor(mod).childOptions);
|
|
||||||
});
|
|
||||||
|
|
||||||
frames.port.on('sdk/page-mod/worker-create', (frame, modId, workerOptions) => {
|
|
||||||
let mod = pagemods.get(modId);
|
|
||||||
if (!mod)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Attach the parent side of the worker to the child
|
|
||||||
let worker = Worker();
|
|
||||||
|
|
||||||
workers.get(mod).unshift(worker);
|
|
||||||
worker.on('*', (event, ...args) => {
|
|
||||||
// page-mod's "attach" event needs to be passed a worker
|
|
||||||
if (event === 'attach')
|
|
||||||
emit(mod, event, worker)
|
|
||||||
else
|
|
||||||
emit(mod, event, ...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
worker.on('detach', () => {
|
|
||||||
let array = workers.get(mod);
|
|
||||||
if (array)
|
|
||||||
remove(array, worker);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(worker, frame, workerOptions);
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var { deprecateUsage } = require("../util/deprecate");
|
|
||||||
|
|
||||||
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
|
|
||||||
|
|
||||||
module.exports = require("../util/match-pattern");
|
|
|
@ -1,113 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.metadata = {
|
|
||||||
"stability": "unstable"
|
|
||||||
};
|
|
||||||
|
|
||||||
lazyRequire(this, '../url', "URL");
|
|
||||||
const cache = {};
|
|
||||||
|
|
||||||
function MatchPattern(pattern) {
|
|
||||||
if (cache[pattern]) return cache[pattern];
|
|
||||||
|
|
||||||
if (typeof pattern.test == "function") {
|
|
||||||
// For compatibility with -moz-document rules, we require the RegExp's
|
|
||||||
// global, ignoreCase, and multiline flags to be set to false.
|
|
||||||
if (pattern.global) {
|
|
||||||
throw new Error("A RegExp match pattern cannot be set to `global` " +
|
|
||||||
"(i.e. //g).");
|
|
||||||
}
|
|
||||||
if (pattern.multiline) {
|
|
||||||
throw new Error("A RegExp match pattern cannot be set to `multiline` " +
|
|
||||||
"(i.e. //m).");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.regexp = pattern;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let firstWildcardPosition = pattern.indexOf("*");
|
|
||||||
let lastWildcardPosition = pattern.lastIndexOf("*");
|
|
||||||
if (firstWildcardPosition != lastWildcardPosition)
|
|
||||||
throw new Error("There can be at most one '*' character in a wildcard.");
|
|
||||||
|
|
||||||
if (firstWildcardPosition == 0) {
|
|
||||||
if (pattern.length == 1)
|
|
||||||
this.anyWebPage = true;
|
|
||||||
else if (pattern[1] != ".")
|
|
||||||
throw new Error("Expected a *.<domain name> string, got: " + pattern);
|
|
||||||
else
|
|
||||||
this.domain = pattern.substr(2);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (pattern.indexOf(":") == -1) {
|
|
||||||
throw new Error("When not using *.example.org wildcard, the string " +
|
|
||||||
"supplied is expected to be either an exact URL to " +
|
|
||||||
"match or a URL prefix. The provided string ('" +
|
|
||||||
pattern + "') is unlikely to match any pages.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstWildcardPosition == -1)
|
|
||||||
this.exactURL = pattern;
|
|
||||||
else if (firstWildcardPosition == pattern.length - 1)
|
|
||||||
this.urlPrefix = pattern.substr(0, pattern.length - 1);
|
|
||||||
else {
|
|
||||||
throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
|
|
||||||
"in an unexpected position. It is expected to be the " +
|
|
||||||
"first or the last character in the wildcard.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache[pattern] = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
MatchPattern.prototype = {
|
|
||||||
test: function MatchPattern_test(urlStr) {
|
|
||||||
try {
|
|
||||||
var url = URL(urlStr);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the URL against a RegExp pattern. For compatibility with
|
|
||||||
// -moz-document rules, we require the RegExp to match the entire URL,
|
|
||||||
// so we not only test for a match, we also make sure the matched string
|
|
||||||
// is the entire URL string.
|
|
||||||
//
|
|
||||||
// Assuming most URLs don't match most match patterns, we call `test` for
|
|
||||||
// speed when determining whether or not the URL matches, then call `exec`
|
|
||||||
// for the small subset that match to make sure the entire URL matches.
|
|
||||||
if (this.regexp && this.regexp.test(urlStr) &&
|
|
||||||
this.regexp.exec(urlStr)[0] == urlStr)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this.exactURL && this.exactURL == urlStr)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Tests the urlStr against domain and check if
|
|
||||||
// wildcard submitted (*.domain.com), it only allows
|
|
||||||
// subdomains (sub.domain.com) or from the root (http://domain.com)
|
|
||||||
// and reject non-matching domains (otherdomain.com)
|
|
||||||
// bug 856913
|
|
||||||
if (this.domain && url.host &&
|
|
||||||
(url.host === this.domain ||
|
|
||||||
url.host.slice(-this.domain.length - 1) === "." + this.domain))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: () => '[object MatchPattern]'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.MatchPattern = MatchPattern;
|
|
|
@ -1,53 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.metadata = {
|
|
||||||
"stability": "unstable"
|
|
||||||
};
|
|
||||||
|
|
||||||
const { Class } = require('../core/heritage');
|
|
||||||
lazyRequire(this, './match-pattern', "MatchPattern");
|
|
||||||
lazyRequire(this, '../event/core', "emit");
|
|
||||||
const { EventTarget } = require('../event/target');
|
|
||||||
const { List, addListItem, removeListItem } = require('./list');
|
|
||||||
|
|
||||||
// Should deprecate usage of EventEmitter/compose
|
|
||||||
const Rules = Class({
|
|
||||||
implements: [
|
|
||||||
EventTarget,
|
|
||||||
List
|
|
||||||
],
|
|
||||||
add: function(...rules) {
|
|
||||||
return [].concat(rules).forEach(function onAdd(rule) {
|
|
||||||
addListItem(this, rule);
|
|
||||||
emit(this, 'add', rule);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
remove: function(...rules) {
|
|
||||||
return [].concat(rules).forEach(function onRemove(rule) {
|
|
||||||
removeListItem(this, rule);
|
|
||||||
emit(this, 'remove', rule);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
get: function(rule) {
|
|
||||||
let found = false;
|
|
||||||
for (let i in this) if (this[i] === rule) found = true;
|
|
||||||
return found;
|
|
||||||
},
|
|
||||||
// Returns true if uri matches atleast one stored rule
|
|
||||||
matchesAny: function(uri) {
|
|
||||||
return !!filterMatches(this, uri).length;
|
|
||||||
},
|
|
||||||
toString: () => '[object Rules]'
|
|
||||||
});
|
|
||||||
exports.Rules = Rules;
|
|
||||||
|
|
||||||
function filterMatches(instance, uri) {
|
|
||||||
let matches = [];
|
|
||||||
for (let i in instance) {
|
|
||||||
if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче