mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 00:35:44 +00:00
Bug 1443964: Part 2 - Remove most add-on compatibility shims. r=mconley
The TabBrowser.addProgressListener shim is the only remaining exception, since the browser_google_behavior.js non-trivially relies on it. MozReview-Commit-ID: Cc2ARwLkjTA --HG-- extra : rebase_source : beea6f21dda0517c0a4c9cf09daeafcff85b93c0
This commit is contained in:
parent
3eba816ee8
commit
f66b3733ba
@ -1144,9 +1144,11 @@ xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prin
|
|||||||
if (options.addonId) {
|
if (options.addonId) {
|
||||||
addonId = JS::NewAddonId(cx, options.addonId);
|
addonId = JS::NewAddonId(cx, options.addonId);
|
||||||
NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE);
|
NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE);
|
||||||
} else if (JSObject* obj = JS::CurrentGlobalOrNull(cx)) {
|
} else if (principal == nsXPConnect::SystemPrincipal()) {
|
||||||
if (JSAddonId* id = JS::AddonIdOfObject(obj))
|
if (JSObject* obj = JS::CurrentGlobalOrNull(cx)) {
|
||||||
addonId = id;
|
if (JSAddonId* id = JS::AddonIdOfObject(obj))
|
||||||
|
addonId = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
creationOptions.setAddonId(addonId);
|
creationOptions.setAddonId(addonId);
|
||||||
|
@ -1,99 +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/.
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["CompatWarning"];
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
|
|
||||||
|
|
||||||
function section(number, url) {
|
|
||||||
const baseURL = "https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts";
|
|
||||||
return { number, url: baseURL + url };
|
|
||||||
}
|
|
||||||
|
|
||||||
var CompatWarning = {
|
|
||||||
// Sometimes we want to generate a warning, but put off issuing it
|
|
||||||
// until later. For example, if someone registers a listener, we
|
|
||||||
// might only want to warn about it if the listener actually
|
|
||||||
// fires. However, we want the warning to show a stack for the
|
|
||||||
// registration site.
|
|
||||||
delayedWarning(msg, addon, warning) {
|
|
||||||
function isShimLayer(filename) {
|
|
||||||
return filename.includes("CompatWarning.jsm") ||
|
|
||||||
filename.includes("RemoteAddonsParent.jsm") ||
|
|
||||||
filename.includes("RemoteAddonsChild.jsm") ||
|
|
||||||
filename.includes("multiprocessShims.js");
|
|
||||||
}
|
|
||||||
|
|
||||||
let stack = Components.stack;
|
|
||||||
while (stack && isShimLayer(stack.filename))
|
|
||||||
stack = stack.caller;
|
|
||||||
|
|
||||||
let alreadyWarned = false;
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
if (alreadyWarned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alreadyWarned = true;
|
|
||||||
|
|
||||||
if (addon) {
|
|
||||||
let histogram = Services.telemetry.getKeyedHistogramById("ADDON_SHIM_USAGE");
|
|
||||||
histogram.add(addon, warning ? warning.number : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Preferences.get("dom.ipc.shims.enabledWarnings", false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
let error = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
|
|
||||||
if (!error || !Services.console) {
|
|
||||||
// Too late during shutdown to use the nsIConsole
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = `Warning: ${msg}`;
|
|
||||||
if (warning)
|
|
||||||
message += `\nMore info at: ${warning.url}`;
|
|
||||||
|
|
||||||
error.init(
|
|
||||||
/* message*/ message,
|
|
||||||
/* sourceName*/ stack ? stack.filename : "",
|
|
||||||
/* sourceLine*/ stack ? stack.sourceLine : "",
|
|
||||||
/* lineNumber*/ stack ? stack.lineNumber : 0,
|
|
||||||
/* columnNumber*/ 0,
|
|
||||||
/* flags*/ Ci.nsIScriptError.warningFlag,
|
|
||||||
/* category*/ "chrome javascript");
|
|
||||||
Services.console.logMessage(error);
|
|
||||||
|
|
||||||
if (Preferences.get("dom.ipc.shims.dumpWarnings", false)) {
|
|
||||||
dump(message + "\n");
|
|
||||||
while (stack) {
|
|
||||||
dump(stack + "\n");
|
|
||||||
stack = stack.caller;
|
|
||||||
}
|
|
||||||
dump("\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
warn(msg, addon, warning) {
|
|
||||||
let delayed = this.delayedWarning(msg, addon, warning);
|
|
||||||
delayed();
|
|
||||||
},
|
|
||||||
|
|
||||||
warnings: {
|
|
||||||
content: section(1, "#gBrowser.contentWindow.2C_window.content..."),
|
|
||||||
limitations_of_CPOWs: section(2, "#Limitations_of_CPOWs"),
|
|
||||||
nsIContentPolicy: section(3, "#nsIContentPolicy"),
|
|
||||||
nsIWebProgressListener: section(4, "#nsIWebProgressListener"),
|
|
||||||
observers: section(5, "#Observers_in_the_chrome_process"),
|
|
||||||
DOM_events: section(6, "#DOM_Events"),
|
|
||||||
sandboxes: section(7, "#Sandboxes"),
|
|
||||||
JSMs: section(8, "#JavaScript_code_modules_(JSMs)"),
|
|
||||||
nsIAboutModule: section(9, "#nsIAboutModule"),
|
|
||||||
// If more than 14 values appear here, you need to change the
|
|
||||||
// ADDON_SHIM_USAGE histogram definition in Histograms.json.
|
|
||||||
},
|
|
||||||
};
|
|
@ -4,24 +4,8 @@
|
|||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["RemoteAddonsChild"];
|
var EXPORTED_SYMBOLS = ["RemoteAddonsChild"];
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
|
|
||||||
"resource://gre/modules/BrowserUtils.jsm");
|
|
||||||
ChromeUtils.defineModuleGetter(this, "Prefetcher",
|
|
||||||
"resource://gre/modules/Prefetcher.jsm");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(this, "SystemPrincipal",
|
|
||||||
"@mozilla.org/systemprincipal;1", "nsIPrincipal");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
|
|
||||||
"@mozilla.org/contentsecuritymanager;1",
|
|
||||||
"nsIContentSecurityManager");
|
|
||||||
|
|
||||||
const TELEMETRY_SHOULD_LOAD_LOADING_KEY = "ADDON_CONTENT_POLICY_SHIM_BLOCKING_LOADING_MS";
|
|
||||||
const TELEMETRY_SHOULD_LOAD_LOADED_KEY = "ADDON_CONTENT_POLICY_SHIM_BLOCKING_LOADED_MS";
|
|
||||||
|
|
||||||
// Similar to Python. Returns dict[key] if it exists. Otherwise,
|
// Similar to Python. Returns dict[key] if it exists. Otherwise,
|
||||||
// sets dict[key] to default_ and returns default_.
|
// sets dict[key] to default_ and returns default_.
|
||||||
function setDefault(dict, key, default_) {
|
function setDefault(dict, key, default_) {
|
||||||
@ -144,472 +128,11 @@ var NotificationTracker = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This code registers an nsIContentPolicy in the child process. When
|
|
||||||
// it runs, it notifies the parent that it needs to run its own
|
|
||||||
// nsIContentPolicy list. If any policy in the parent rejects a
|
|
||||||
// resource load, that answer is returned to the child.
|
|
||||||
var ContentPolicyChild = {
|
|
||||||
_classDescription: "Addon shim content policy",
|
|
||||||
_classID: Components.ID("6e869130-635c-11e2-bcfd-0800200c9a66"),
|
|
||||||
_contractID: "@mozilla.org/addon-child/policy;1",
|
|
||||||
|
|
||||||
// A weak map of time spent blocked in hooks for a given document.
|
|
||||||
// WeakMap[document -> Map[addonId -> timeInMS]]
|
|
||||||
timings: new WeakMap(),
|
|
||||||
|
|
||||||
init() {
|
|
||||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
|
||||||
registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
|
|
||||||
|
|
||||||
this.loadingHistogram = Services.telemetry.getKeyedHistogramById(TELEMETRY_SHOULD_LOAD_LOADING_KEY);
|
|
||||||
this.loadedHistogram = Services.telemetry.getKeyedHistogramById(TELEMETRY_SHOULD_LOAD_LOADED_KEY);
|
|
||||||
|
|
||||||
NotificationTracker.watch("content-policy", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
|
|
||||||
Ci.nsIChannelEventSink, Ci.nsIFactory,
|
|
||||||
Ci.nsISupportsWeakReference]),
|
|
||||||
|
|
||||||
track(path, register) {
|
|
||||||
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
|
|
||||||
if (register) {
|
|
||||||
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
|
|
||||||
} else {
|
|
||||||
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns a map of cumulative time spent in shouldLoad hooks for a
|
|
||||||
// given add-on in the given node's document. May return null if
|
|
||||||
// telemetry recording is disabled, or the given context does not
|
|
||||||
// point to a document.
|
|
||||||
getTimings(context) {
|
|
||||||
if (!Services.telemetry.canRecordExtended) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc;
|
|
||||||
if (context instanceof Ci.nsIDOMNode) {
|
|
||||||
doc = context.ownerDocument;
|
|
||||||
} else if (context instanceof Ci.nsIDOMDocument) {
|
|
||||||
doc = context;
|
|
||||||
} else if (context instanceof Ci.nsIDOMWindow) {
|
|
||||||
doc = context.document;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!doc) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let map = this.timings.get(doc);
|
|
||||||
if (!map) {
|
|
||||||
// No timing object exists for this document yet. Create one, and
|
|
||||||
// set up a listener to record the final values at the right time.
|
|
||||||
map = new Map();
|
|
||||||
this.timings.set(doc, map);
|
|
||||||
|
|
||||||
// If the document is still loading, record aggregate pre-load
|
|
||||||
// timings when the load event fires. If it's already loaded,
|
|
||||||
// record aggregate post-load timings when the page is hidden.
|
|
||||||
let eventName = doc.readyState == "complete" ? "pagehide" : "load";
|
|
||||||
|
|
||||||
let listener = event => {
|
|
||||||
if (event.target == doc) {
|
|
||||||
event.currentTarget.removeEventListener(eventName, listener, true);
|
|
||||||
this.logTelemetry(doc, eventName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
doc.defaultView.addEventListener(eventName, listener, true);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Logs the accumulated telemetry for the given document, into the
|
|
||||||
// appropriate telemetry histogram based on the DOM event name that
|
|
||||||
// triggered it.
|
|
||||||
logTelemetry(doc, eventName) {
|
|
||||||
let map = this.timings.get(doc);
|
|
||||||
this.timings.delete(doc);
|
|
||||||
|
|
||||||
let histogram = eventName == "load" ? this.loadingHistogram : this.loadedHistogram;
|
|
||||||
|
|
||||||
for (let [addon, time] of map.entries()) {
|
|
||||||
histogram.add(addon, time);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldLoad(contentType, contentLocation, requestOrigin,
|
|
||||||
node, mimeTypeGuess, extra, requestPrincipal) {
|
|
||||||
let startTime = Cu.now();
|
|
||||||
|
|
||||||
let addons = NotificationTracker.findSuffixes(["content-policy"]);
|
|
||||||
let [prefetched, cpows] = Prefetcher.prefetch("ContentPolicy.shouldLoad",
|
|
||||||
addons, {InitNode: node});
|
|
||||||
cpows.node = node;
|
|
||||||
|
|
||||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsISyncMessageSender);
|
|
||||||
let rval = cpmm.sendRpcMessage("Addons:ContentPolicy:Run", {
|
|
||||||
contentType,
|
|
||||||
contentLocation: contentLocation.spec,
|
|
||||||
requestOrigin: requestOrigin ? requestOrigin.spec : null,
|
|
||||||
mimeTypeGuess,
|
|
||||||
requestPrincipal,
|
|
||||||
prefetched,
|
|
||||||
}, cpows);
|
|
||||||
|
|
||||||
let timings = this.getTimings(node);
|
|
||||||
if (timings) {
|
|
||||||
let delta = Cu.now() - startTime;
|
|
||||||
|
|
||||||
for (let addon of addons) {
|
|
||||||
let old = timings.get(addon) || 0;
|
|
||||||
timings.set(addon, old + delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rval.length != 1) {
|
|
||||||
return Ci.nsIContentPolicy.ACCEPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rval[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldProcess(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
|
|
||||||
return Ci.nsIContentPolicy.ACCEPT;
|
|
||||||
},
|
|
||||||
|
|
||||||
createInstance(outer, iid) {
|
|
||||||
if (outer) {
|
|
||||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
||||||
}
|
|
||||||
return this.QueryInterface(iid);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is a shim channel whose only purpose is to return some string
|
|
||||||
// data from an about: protocol handler.
|
|
||||||
function AboutProtocolChannel(uri, contractID, loadInfo) {
|
|
||||||
this.URI = uri;
|
|
||||||
this.originalURI = uri;
|
|
||||||
this._contractID = contractID;
|
|
||||||
this._loadingPrincipal = loadInfo.loadingPrincipal;
|
|
||||||
this._securityFlags = loadInfo.securityFlags;
|
|
||||||
this._contentPolicyType = loadInfo.externalContentPolicyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutProtocolChannel.prototype = {
|
|
||||||
contentCharset: "utf-8",
|
|
||||||
contentLength: 0,
|
|
||||||
owner: SystemPrincipal,
|
|
||||||
securityInfo: null,
|
|
||||||
notificationCallbacks: null,
|
|
||||||
loadFlags: 0,
|
|
||||||
loadGroup: null,
|
|
||||||
name: null,
|
|
||||||
status: Cr.NS_OK,
|
|
||||||
|
|
||||||
asyncOpen(listener, context) {
|
|
||||||
// Ask the parent to synchronously read all the data from the channel.
|
|
||||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsISyncMessageSender);
|
|
||||||
let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:OpenChannel", {
|
|
||||||
uri: this.URI.spec,
|
|
||||||
contractID: this._contractID,
|
|
||||||
loadingPrincipal: this._loadingPrincipal,
|
|
||||||
securityFlags: this._securityFlags,
|
|
||||||
contentPolicyType: this._contentPolicyType
|
|
||||||
}, {
|
|
||||||
notificationCallbacks: this.notificationCallbacks,
|
|
||||||
loadGroupNotificationCallbacks: this.loadGroup ? this.loadGroup.notificationCallbacks : null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rval.length != 1) {
|
|
||||||
throw Cr.NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {data, contentType} = rval[0];
|
|
||||||
this.contentType = contentType;
|
|
||||||
|
|
||||||
// Return the data via an nsIStringInputStream.
|
|
||||||
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
|
||||||
stream.setData(data, data.length);
|
|
||||||
|
|
||||||
let runnable = {
|
|
||||||
run: () => {
|
|
||||||
try {
|
|
||||||
listener.onStartRequest(this, context);
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
listener.onDataAvailable(this, context, stream, 0, stream.available());
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
listener.onStopRequest(this, context, Cr.NS_OK);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Services.tm.dispatchToMainThread(runnable);
|
|
||||||
},
|
|
||||||
|
|
||||||
asyncOpen2(listener) {
|
|
||||||
// throws an error if security checks fail
|
|
||||||
var outListener = contentSecManager.performSecurityCheck(this, listener);
|
|
||||||
this.asyncOpen(outListener, null);
|
|
||||||
},
|
|
||||||
|
|
||||||
open() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
open2() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
isPending() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
suspend() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
resume() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
|
|
||||||
};
|
|
||||||
|
|
||||||
// This shim protocol handler is used when content fetches an about: URL.
|
|
||||||
function AboutProtocolInstance(contractID) {
|
|
||||||
this._contractID = contractID;
|
|
||||||
this._uriFlags = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
AboutProtocolInstance.prototype = {
|
|
||||||
createInstance(outer, iid) {
|
|
||||||
if (outer != null) {
|
|
||||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.QueryInterface(iid);
|
|
||||||
},
|
|
||||||
|
|
||||||
getURIFlags(uri) {
|
|
||||||
// Cache the result to avoid the extra IPC.
|
|
||||||
if (this._uriFlags !== undefined) {
|
|
||||||
return this._uriFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsISyncMessageSender);
|
|
||||||
|
|
||||||
let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:GetURIFlags", {
|
|
||||||
uri: uri.spec,
|
|
||||||
contractID: this._contractID
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rval.length != 1) {
|
|
||||||
throw Cr.NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._uriFlags = rval[0];
|
|
||||||
return this._uriFlags;
|
|
||||||
},
|
|
||||||
|
|
||||||
// We take some shortcuts here. Ideally, we would return a CPOW that
|
|
||||||
// wraps the add-on's nsIChannel. However, many of the methods
|
|
||||||
// related to nsIChannel are marked [noscript], so they're not
|
|
||||||
// available to CPOWs. Consequently, we return a shim channel that,
|
|
||||||
// when opened, asks the parent to open the channel and read out all
|
|
||||||
// the data.
|
|
||||||
newChannel(uri, loadInfo) {
|
|
||||||
return new AboutProtocolChannel(uri, this._contractID, loadInfo);
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
|
|
||||||
};
|
|
||||||
|
|
||||||
var AboutProtocolChild = {
|
|
||||||
_classDescription: "Addon shim about: protocol handler",
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Maps contractIDs to instances
|
|
||||||
this._instances = new Map();
|
|
||||||
// Maps contractIDs to classIDs
|
|
||||||
this._classIDs = new Map();
|
|
||||||
NotificationTracker.watch("about-protocol", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
track(path, register) {
|
|
||||||
let contractID = path[1];
|
|
||||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
|
||||||
if (register) {
|
|
||||||
let instance = new AboutProtocolInstance(contractID);
|
|
||||||
let classID = Cc["@mozilla.org/uuid-generator;1"]
|
|
||||||
.getService(Ci.nsIUUIDGenerator)
|
|
||||||
.generateUUID();
|
|
||||||
|
|
||||||
this._instances.set(contractID, instance);
|
|
||||||
this._classIDs.set(contractID, classID);
|
|
||||||
registrar.registerFactory(classID, this._classDescription, contractID, instance);
|
|
||||||
} else {
|
|
||||||
let instance = this._instances.get(contractID);
|
|
||||||
let classID = this._classIDs.get(contractID);
|
|
||||||
registrar.unregisterFactory(classID, instance);
|
|
||||||
this._instances.delete(contractID);
|
|
||||||
this._classIDs.delete(contractID);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This code registers observers in the child whenever an add-on in
|
|
||||||
// the parent asks for notifications on the given topic.
|
|
||||||
var ObserverChild = {
|
|
||||||
init() {
|
|
||||||
NotificationTracker.watch("observer", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
track(path, register) {
|
|
||||||
let topic = path[1];
|
|
||||||
if (register) {
|
|
||||||
Services.obs.addObserver(this, topic);
|
|
||||||
} else {
|
|
||||||
Services.obs.removeObserver(this, topic);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observe(subject, topic, data) {
|
|
||||||
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsISyncMessageSender);
|
|
||||||
cpmm.sendRpcMessage("Addons:Observer:Run", {}, {
|
|
||||||
topic,
|
|
||||||
subject,
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// There is one of these objects per browser tab in the child. When an
|
|
||||||
// add-on in the parent listens for an event, this child object
|
|
||||||
// listens for that event in the child.
|
|
||||||
function EventTargetChild(childGlobal) {
|
|
||||||
this._childGlobal = childGlobal;
|
|
||||||
this.capturingHandler = (event) => this.handleEvent(true, event);
|
|
||||||
this.nonCapturingHandler = (event) => this.handleEvent(false, event);
|
|
||||||
NotificationTracker.watch("event", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventTargetChild.prototype = {
|
|
||||||
uninit() {
|
|
||||||
NotificationTracker.unwatch("event", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
track(path, register) {
|
|
||||||
let eventType = path[1];
|
|
||||||
let useCapture = path[2];
|
|
||||||
let listener = useCapture ? this.capturingHandler : this.nonCapturingHandler;
|
|
||||||
if (register) {
|
|
||||||
this._childGlobal.addEventListener(eventType, listener, useCapture, true);
|
|
||||||
} else {
|
|
||||||
this._childGlobal.removeEventListener(eventType, listener, useCapture);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(capturing, event) {
|
|
||||||
let addons = NotificationTracker.findSuffixes(["event", event.type, capturing]);
|
|
||||||
let [prefetched, cpows] = Prefetcher.prefetch("EventTarget.handleEvent",
|
|
||||||
addons,
|
|
||||||
{Event: event,
|
|
||||||
Window: this._childGlobal.content});
|
|
||||||
cpows.event = event;
|
|
||||||
cpows.eventTarget = event.target;
|
|
||||||
|
|
||||||
this._childGlobal.sendRpcMessage("Addons:Event:Run",
|
|
||||||
{type: event.type,
|
|
||||||
capturing,
|
|
||||||
isTrusted: event.isTrusted,
|
|
||||||
prefetched},
|
|
||||||
cpows);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The parent can create a sandbox to run code in the child
|
|
||||||
// process. We actually create the sandbox in the child so that the
|
|
||||||
// code runs there. However, managing the lifetime of these sandboxes
|
|
||||||
// can be tricky. The parent references these sandboxes using CPOWs,
|
|
||||||
// which only keep weak references. So we need to create a strong
|
|
||||||
// reference in the child. For simplicity, we kill off these strong
|
|
||||||
// references whenever we navigate away from the page for which the
|
|
||||||
// sandbox was created.
|
|
||||||
function SandboxChild(chromeGlobal) {
|
|
||||||
this.chromeGlobal = chromeGlobal;
|
|
||||||
this.sandboxes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
SandboxChild.prototype = {
|
|
||||||
uninit() {
|
|
||||||
this.clearSandboxes();
|
|
||||||
},
|
|
||||||
|
|
||||||
addListener() {
|
|
||||||
let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebProgress);
|
|
||||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeListener() {
|
|
||||||
let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebProgress);
|
|
||||||
webProgress.removeProgressListener(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
onLocationChange(webProgress, request, location, flags) {
|
|
||||||
this.clearSandboxes();
|
|
||||||
},
|
|
||||||
|
|
||||||
addSandbox(sandbox) {
|
|
||||||
if (this.sandboxes.length == 0) {
|
|
||||||
this.addListener();
|
|
||||||
}
|
|
||||||
this.sandboxes.push(sandbox);
|
|
||||||
},
|
|
||||||
|
|
||||||
clearSandboxes() {
|
|
||||||
if (this.sandboxes.length) {
|
|
||||||
this.removeListener();
|
|
||||||
}
|
|
||||||
this.sandboxes = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
|
||||||
Ci.nsISupportsWeakReference])
|
|
||||||
};
|
|
||||||
|
|
||||||
var RemoteAddonsChild = {
|
var RemoteAddonsChild = {
|
||||||
_ready: false,
|
_ready: false,
|
||||||
|
|
||||||
makeReady() {
|
makeReady() {
|
||||||
let shims = [
|
|
||||||
Prefetcher,
|
|
||||||
NotificationTracker,
|
|
||||||
ContentPolicyChild,
|
|
||||||
AboutProtocolChild,
|
|
||||||
ObserverChild,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let shim of shims) {
|
|
||||||
try {
|
|
||||||
shim.init();
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
init(global) {
|
init(global) {
|
||||||
@ -625,11 +148,7 @@ var RemoteAddonsChild = {
|
|||||||
|
|
||||||
global.sendAsyncMessage("Addons:RegisterGlobal", {}, {global});
|
global.sendAsyncMessage("Addons:RegisterGlobal", {}, {global});
|
||||||
|
|
||||||
let sandboxChild = new SandboxChild(global);
|
return [];
|
||||||
global.addSandbox = sandboxChild.addSandbox.bind(sandboxChild);
|
|
||||||
|
|
||||||
// Return this so it gets rooted in the content script.
|
|
||||||
return [new EventTargetChild(global), sandboxChild];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
uninit(perTabShims) {
|
uninit(perTabShims) {
|
||||||
|
@ -4,17 +4,9 @@
|
|||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["RemoteAddonsParent"];
|
var EXPORTED_SYMBOLS = ["RemoteAddonsParent"];
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/RemoteWebProgress.jsm");
|
ChromeUtils.import("resource://gre/modules/RemoteWebProgress.jsm");
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(this, "NetUtil",
|
|
||||||
"resource://gre/modules/NetUtil.jsm");
|
|
||||||
ChromeUtils.defineModuleGetter(this, "Prefetcher",
|
|
||||||
"resource://gre/modules/Prefetcher.jsm");
|
|
||||||
ChromeUtils.defineModuleGetter(this, "CompatWarning",
|
|
||||||
"resource://gre/modules/CompatWarning.jsm");
|
|
||||||
|
|
||||||
Cu.permitCPOWsInScope(this);
|
Cu.permitCPOWsInScope(this);
|
||||||
|
|
||||||
// Similar to Python. Returns dict[key] if it exists. Otherwise,
|
// Similar to Python. Returns dict[key] if it exists. Otherwise,
|
||||||
@ -93,868 +85,7 @@ function Interposition(name, base) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This object is responsible for notifying the child when a new
|
var TabBrowserElementInterposition = new Interposition("TabBrowserElementInterposition");
|
||||||
// content policy is added or removed. It also runs all the registered
|
|
||||||
// add-on content policies when the child asks it to do so.
|
|
||||||
var ContentPolicyParent = {
|
|
||||||
init() {
|
|
||||||
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsIMessageBroadcaster);
|
|
||||||
ppmm.addMessageListener("Addons:ContentPolicy:Run", this);
|
|
||||||
|
|
||||||
this._policies = new Map();
|
|
||||||
},
|
|
||||||
|
|
||||||
addContentPolicy(addon, name, cid) {
|
|
||||||
this._policies.set(name, cid);
|
|
||||||
NotificationTracker.add(["content-policy", addon]);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeContentPolicy(addon, name) {
|
|
||||||
this._policies.delete(name);
|
|
||||||
NotificationTracker.remove(["content-policy", addon]);
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage(aMessage) {
|
|
||||||
switch (aMessage.name) {
|
|
||||||
case "Addons:ContentPolicy:Run":
|
|
||||||
return this.shouldLoad(aMessage.data, aMessage.objects);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldLoad(aData, aObjects) {
|
|
||||||
for (let policyCID of this._policies.values()) {
|
|
||||||
let policy;
|
|
||||||
try {
|
|
||||||
policy = Cc[policyCID].getService(Ci.nsIContentPolicy);
|
|
||||||
} catch (e) {
|
|
||||||
// Current Gecko behavior is to ignore entries that don't QI.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let contentLocation = Services.io.newURI(aData.contentLocation);
|
|
||||||
let requestOrigin = aData.requestOrigin ? Services.io.newURI(aData.requestOrigin) : null;
|
|
||||||
|
|
||||||
let result = Prefetcher.withPrefetching(aData.prefetched, aObjects, () => {
|
|
||||||
return policy.shouldLoad(aData.contentType,
|
|
||||||
contentLocation,
|
|
||||||
requestOrigin,
|
|
||||||
aObjects.node,
|
|
||||||
aData.mimeTypeGuess,
|
|
||||||
null,
|
|
||||||
aData.requestPrincipal);
|
|
||||||
});
|
|
||||||
if (result != Ci.nsIContentPolicy.ACCEPT && result != 0)
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ci.nsIContentPolicy.ACCEPT;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ContentPolicyParent.init();
|
|
||||||
|
|
||||||
// This interposition intercepts calls to add or remove new content
|
|
||||||
// policies and forwards these requests to ContentPolicyParent.
|
|
||||||
var CategoryManagerInterposition = new Interposition("CategoryManagerInterposition");
|
|
||||||
|
|
||||||
CategoryManagerInterposition.methods.addCategoryEntry =
|
|
||||||
function(addon, target, category, entry, value, persist, replace) {
|
|
||||||
if (category == "content-policy") {
|
|
||||||
CompatWarning.warn("content-policy should be added from the child process only.",
|
|
||||||
addon, CompatWarning.warnings.nsIContentPolicy);
|
|
||||||
ContentPolicyParent.addContentPolicy(addon, entry, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.addCategoryEntry(category, entry, value, persist, replace);
|
|
||||||
};
|
|
||||||
|
|
||||||
CategoryManagerInterposition.methods.deleteCategoryEntry =
|
|
||||||
function(addon, target, category, entry, persist) {
|
|
||||||
if (category == "content-policy") {
|
|
||||||
CompatWarning.warn("content-policy should be removed from the child process only.",
|
|
||||||
addon, CompatWarning.warnings.nsIContentPolicy);
|
|
||||||
ContentPolicyParent.removeContentPolicy(addon, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.deleteCategoryEntry(category, entry, persist);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This shim handles the case where an add-on registers an about:
|
|
||||||
// protocol handler in the parent and we want the child to be able to
|
|
||||||
// use it. This code is pretty specific to Adblock's usage.
|
|
||||||
var AboutProtocolParent = {
|
|
||||||
init() {
|
|
||||||
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsIMessageBroadcaster);
|
|
||||||
ppmm.addMessageListener("Addons:AboutProtocol:GetURIFlags", this);
|
|
||||||
ppmm.addMessageListener("Addons:AboutProtocol:OpenChannel", this);
|
|
||||||
this._protocols = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
registerFactory(addon, class_, className, contractID, factory) {
|
|
||||||
this._protocols.push({contractID, factory});
|
|
||||||
NotificationTracker.add(["about-protocol", contractID, addon]);
|
|
||||||
},
|
|
||||||
|
|
||||||
unregisterFactory(addon, class_, factory) {
|
|
||||||
for (let i = 0; i < this._protocols.length; i++) {
|
|
||||||
if (this._protocols[i].factory == factory) {
|
|
||||||
NotificationTracker.remove(["about-protocol", this._protocols[i].contractID, addon]);
|
|
||||||
this._protocols.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage(msg) {
|
|
||||||
switch (msg.name) {
|
|
||||||
case "Addons:AboutProtocol:GetURIFlags":
|
|
||||||
return this.getURIFlags(msg);
|
|
||||||
case "Addons:AboutProtocol:OpenChannel":
|
|
||||||
return this.openChannel(msg);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
getURIFlags(msg) {
|
|
||||||
let uri = Services.io.newURI(msg.data.uri);
|
|
||||||
let contractID = msg.data.contractID;
|
|
||||||
let module = Cc[contractID].getService(Ci.nsIAboutModule);
|
|
||||||
try {
|
|
||||||
return module.getURIFlags(uri);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// We immediately read all the data out of the channel here and
|
|
||||||
// return it to the child.
|
|
||||||
openChannel(msg) {
|
|
||||||
function wrapGetInterface(cpow) {
|
|
||||||
return {
|
|
||||||
getInterface(intf) { return cpow.getInterface(intf); }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let uri = Services.io.newURI(msg.data.uri);
|
|
||||||
let channelParams;
|
|
||||||
if (msg.data.contentPolicyType === Ci.nsIContentPolicy.TYPE_DOCUMENT) {
|
|
||||||
// For TYPE_DOCUMENT loads, we cannot recreate the loadinfo here in the
|
|
||||||
// parent. In that case, treat this as a chrome (addon)-requested
|
|
||||||
// subload. When we use the data in the child, we'll load it into the
|
|
||||||
// correctly-principaled document.
|
|
||||||
channelParams = {
|
|
||||||
uri,
|
|
||||||
contractID: msg.data.contractID,
|
|
||||||
loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
||||||
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
||||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// We can recreate the loadinfo here in the parent for non TYPE_DOCUMENT
|
|
||||||
// loads.
|
|
||||||
channelParams = {
|
|
||||||
uri,
|
|
||||||
contractID: msg.data.contractID,
|
|
||||||
loadingPrincipal: msg.data.loadingPrincipal,
|
|
||||||
securityFlags: msg.data.securityFlags,
|
|
||||||
contentPolicyType: msg.data.contentPolicyType
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let channel = NetUtil.newChannel(channelParams);
|
|
||||||
|
|
||||||
// We're not allowed to set channel.notificationCallbacks to a
|
|
||||||
// CPOW, since the setter for notificationCallbacks is in C++,
|
|
||||||
// which can't tolerate CPOWs. Instead we just use a JS object
|
|
||||||
// that wraps the CPOW.
|
|
||||||
channel.notificationCallbacks = wrapGetInterface(msg.objects.notificationCallbacks);
|
|
||||||
if (msg.objects.loadGroupNotificationCallbacks) {
|
|
||||||
channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
|
|
||||||
} else {
|
|
||||||
channel.loadGroup = null;
|
|
||||||
}
|
|
||||||
let stream = channel.open2();
|
|
||||||
let data = NetUtil.readInputStreamToString(stream, stream.available(), {});
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
contentType: channel.contentType
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AboutProtocolParent.init();
|
|
||||||
|
|
||||||
var ComponentRegistrarInterposition = new Interposition("ComponentRegistrarInterposition");
|
|
||||||
|
|
||||||
ComponentRegistrarInterposition.methods.registerFactory =
|
|
||||||
function(addon, target, class_, className, contractID, factory) {
|
|
||||||
if (contractID && contractID.startsWith("@mozilla.org/network/protocol/about;1?")) {
|
|
||||||
CompatWarning.warn("nsIAboutModule should be registered in the content process" +
|
|
||||||
" as well as the chrome process. (If you do that already, ignore" +
|
|
||||||
" this warning.)",
|
|
||||||
addon, CompatWarning.warnings.nsIAboutModule);
|
|
||||||
AboutProtocolParent.registerFactory(addon, class_, className, contractID, factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.registerFactory(class_, className, contractID, factory);
|
|
||||||
};
|
|
||||||
|
|
||||||
ComponentRegistrarInterposition.methods.unregisterFactory =
|
|
||||||
function(addon, target, class_, factory) {
|
|
||||||
AboutProtocolParent.unregisterFactory(addon, class_, factory);
|
|
||||||
target.unregisterFactory(class_, factory);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This object manages add-on observers that might fire in the child
|
|
||||||
// process. Rather than managing the observers itself, it uses the
|
|
||||||
// parent's observer service. When an add-on listens on topic T,
|
|
||||||
// ObserverParent asks the child process to listen on T. It also adds
|
|
||||||
// an observer in the parent for the topic e10s-T. When the T observer
|
|
||||||
// fires in the child, the parent fires all the e10s-T observers,
|
|
||||||
// passing them CPOWs for the subject and data. We don't want to use T
|
|
||||||
// in the parent because there might be non-add-on T observers that
|
|
||||||
// won't expect to get notified in this case.
|
|
||||||
var ObserverParent = {
|
|
||||||
init() {
|
|
||||||
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
|
||||||
.getService(Ci.nsIMessageBroadcaster);
|
|
||||||
ppmm.addMessageListener("Addons:Observer:Run", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
addObserver(addon, observer, topic, ownsWeak) {
|
|
||||||
Services.obs.addObserver(observer, "e10s-" + topic, ownsWeak);
|
|
||||||
NotificationTracker.add(["observer", topic, addon]);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeObserver(addon, observer, topic) {
|
|
||||||
Services.obs.removeObserver(observer, "e10s-" + topic);
|
|
||||||
NotificationTracker.remove(["observer", topic, addon]);
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage(msg) {
|
|
||||||
switch (msg.name) {
|
|
||||||
case "Addons:Observer:Run":
|
|
||||||
this.notify(msg.objects.subject, msg.objects.topic, msg.objects.data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
notify(subject, topic, data) {
|
|
||||||
let e = Services.obs.enumerateObservers("e10s-" + topic);
|
|
||||||
while (e.hasMoreElements()) {
|
|
||||||
let obs = e.getNext().QueryInterface(Ci.nsIObserver);
|
|
||||||
try {
|
|
||||||
obs.observe(subject, topic, data);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ObserverParent.init();
|
|
||||||
|
|
||||||
// We only forward observers for these topics.
|
|
||||||
var TOPIC_WHITELIST = [
|
|
||||||
"content-document-global-created",
|
|
||||||
"document-element-inserted",
|
|
||||||
"dom-window-destroyed",
|
|
||||||
"inner-window-destroyed",
|
|
||||||
"outer-window-destroyed",
|
|
||||||
"csp-on-violate-policy",
|
|
||||||
];
|
|
||||||
|
|
||||||
// This interposition listens for
|
|
||||||
// nsIObserverService.{add,remove}Observer.
|
|
||||||
var ObserverInterposition = new Interposition("ObserverInterposition");
|
|
||||||
|
|
||||||
ObserverInterposition.methods.addObserver =
|
|
||||||
function(addon, target, observer, topic, ownsWeak) {
|
|
||||||
if (TOPIC_WHITELIST.includes(topic)) {
|
|
||||||
CompatWarning.warn(`${topic} observer should be added from the child process only.`,
|
|
||||||
addon, CompatWarning.warnings.observers);
|
|
||||||
|
|
||||||
ObserverParent.addObserver(addon, observer, topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.addObserver(observer, topic, ownsWeak);
|
|
||||||
};
|
|
||||||
|
|
||||||
ObserverInterposition.methods.removeObserver =
|
|
||||||
function(addon, target, observer, topic) {
|
|
||||||
if (TOPIC_WHITELIST.includes(topic)) {
|
|
||||||
ObserverParent.removeObserver(addon, observer, topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.removeObserver(observer, topic);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This object is responsible for forwarding events from the child to
|
|
||||||
// the parent.
|
|
||||||
var EventTargetParent = {
|
|
||||||
init() {
|
|
||||||
// The _listeners map goes from targets (either <browser> elements
|
|
||||||
// or windows) to a dictionary from event types to listeners.
|
|
||||||
this._listeners = new WeakMap();
|
|
||||||
|
|
||||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].
|
|
||||||
getService(Ci.nsIMessageListenerManager);
|
|
||||||
mm.addMessageListener("Addons:Event:Run", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
// If target is not on the path from a <browser> element to the
|
|
||||||
// window root, then we return null here to ignore the
|
|
||||||
// target. Otherwise, if the target is a browser-specific element
|
|
||||||
// (the <browser> or <tab> elements), then we return the
|
|
||||||
// <browser>. If it's some generic element, then we return the
|
|
||||||
// window itself.
|
|
||||||
redirectEventTarget(target) {
|
|
||||||
if (Cu.isCrossProcessWrapper(target)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDOMChromeWindow) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDOMXULElement) {
|
|
||||||
if (target.localName == "browser") {
|
|
||||||
return target;
|
|
||||||
} else if (target.localName == "tab") {
|
|
||||||
return target.linkedBrowser;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if |target| is somewhere on the path from the
|
|
||||||
// tabbrowser tabbox.
|
|
||||||
let window = target.ownerGlobal;
|
|
||||||
|
|
||||||
// Some non-browser windows define gBrowser globals which are not elements
|
|
||||||
// and can't be passed to target.contains().
|
|
||||||
if (window &&
|
|
||||||
window.gBrowser &&
|
|
||||||
window.gBrowser.tabbox &&
|
|
||||||
target.contains(window.gBrowser.tabbox)) {
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.ownerGlobal && target === target.ownerGlobal.gBrowser) {
|
|
||||||
return target.ownerGlobal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// When a given event fires in the child, we fire it on the
|
|
||||||
// <browser> element and the window since those are the two possible
|
|
||||||
// results of redirectEventTarget.
|
|
||||||
getTargets(browser) {
|
|
||||||
let window = browser.ownerGlobal;
|
|
||||||
return [browser, window];
|
|
||||||
},
|
|
||||||
|
|
||||||
addEventListener(addon, target, type, listener, useCapture, wantsUntrusted, delayedWarning) {
|
|
||||||
let newTarget = this.redirectEventTarget(target);
|
|
||||||
if (!newTarget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
useCapture = useCapture || false;
|
|
||||||
wantsUntrusted = wantsUntrusted || false;
|
|
||||||
|
|
||||||
NotificationTracker.add(["event", type, useCapture, addon]);
|
|
||||||
|
|
||||||
let listeners = this._listeners.get(newTarget);
|
|
||||||
if (!listeners) {
|
|
||||||
listeners = {};
|
|
||||||
this._listeners.set(newTarget, listeners);
|
|
||||||
}
|
|
||||||
let forType = setDefault(listeners, type, []);
|
|
||||||
|
|
||||||
// If there's already an identical listener, don't do anything.
|
|
||||||
for (let i = 0; i < forType.length; i++) {
|
|
||||||
if (forType[i].listener === listener &&
|
|
||||||
forType[i].target === target &&
|
|
||||||
forType[i].useCapture === useCapture &&
|
|
||||||
forType[i].wantsUntrusted === wantsUntrusted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forType.push({listener,
|
|
||||||
target,
|
|
||||||
wantsUntrusted,
|
|
||||||
useCapture,
|
|
||||||
delayedWarning});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeEventListener(addon, target, type, listener, useCapture) {
|
|
||||||
let newTarget = this.redirectEventTarget(target);
|
|
||||||
if (!newTarget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
useCapture = useCapture || false;
|
|
||||||
|
|
||||||
let listeners = this._listeners.get(newTarget);
|
|
||||||
if (!listeners) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let forType = setDefault(listeners, type, []);
|
|
||||||
|
|
||||||
for (let i = 0; i < forType.length; i++) {
|
|
||||||
if (forType[i].listener === listener &&
|
|
||||||
forType[i].target === target &&
|
|
||||||
forType[i].useCapture === useCapture) {
|
|
||||||
forType.splice(i, 1);
|
|
||||||
NotificationTracker.remove(["event", type, useCapture, addon]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage(msg) {
|
|
||||||
switch (msg.name) {
|
|
||||||
case "Addons:Event:Run":
|
|
||||||
this.dispatch(msg.target, msg.data.type, msg.data.capturing,
|
|
||||||
msg.data.isTrusted, msg.data.prefetched, msg.objects);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
dispatch(browser, type, capturing, isTrusted, prefetched, cpows) {
|
|
||||||
let event = cpows.event;
|
|
||||||
let eventTarget = cpows.eventTarget;
|
|
||||||
let targets = this.getTargets(browser);
|
|
||||||
for (let target of targets) {
|
|
||||||
let listeners = this._listeners.get(target);
|
|
||||||
if (!listeners) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let forType = setDefault(listeners, type, []);
|
|
||||||
|
|
||||||
// Make a copy in case they call removeEventListener in the listener.
|
|
||||||
let handlers = [];
|
|
||||||
for (let {listener, target, wantsUntrusted, useCapture, delayedWarning} of forType) {
|
|
||||||
if ((wantsUntrusted || isTrusted) && useCapture == capturing) {
|
|
||||||
// Issue a warning for this listener.
|
|
||||||
delayedWarning();
|
|
||||||
|
|
||||||
handlers.push([listener, target]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let [handler, target] of handlers) {
|
|
||||||
let EventProxy = {
|
|
||||||
get(knownProps, name) {
|
|
||||||
if (knownProps.hasOwnProperty(name))
|
|
||||||
return knownProps[name];
|
|
||||||
return event[name];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let proxyEvent = new Proxy({
|
|
||||||
currentTarget: target,
|
|
||||||
target: eventTarget,
|
|
||||||
type,
|
|
||||||
QueryInterface(iid) {
|
|
||||||
if (iid.equals(Ci.nsISupports) ||
|
|
||||||
iid.equals(Ci.nsIDOMEventTarget))
|
|
||||||
return proxyEvent;
|
|
||||||
// If event deson't support the interface this will throw. If it
|
|
||||||
// does we want to return the proxy
|
|
||||||
event.QueryInterface(iid);
|
|
||||||
return proxyEvent;
|
|
||||||
}
|
|
||||||
}, EventProxy);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Prefetcher.withPrefetching(prefetched, cpows, () => {
|
|
||||||
if ("handleEvent" in handler) {
|
|
||||||
handler.handleEvent(proxyEvent);
|
|
||||||
} else {
|
|
||||||
handler.call(eventTarget, proxyEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
EventTargetParent.init();
|
|
||||||
|
|
||||||
// This function returns a listener that will remove itself the first time
|
|
||||||
// it is fired.
|
|
||||||
var selfRemovingListeners = new WeakMap();
|
|
||||||
function makeSelfRemovingListener(addon, target, type, listener, useCapture) {
|
|
||||||
if (selfRemovingListeners.has(listener)) {
|
|
||||||
return selfRemovingListeners.get(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selfRemovingListener(event) {
|
|
||||||
EventTargetInterposition.methods.removeEventListener(addon, target, type,
|
|
||||||
listener, useCapture);
|
|
||||||
if ("handleEvent" in listener) {
|
|
||||||
listener.handleEvent(event);
|
|
||||||
} else {
|
|
||||||
listener.call(event.target, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selfRemovingListeners.set(listener, selfRemovingListener);
|
|
||||||
|
|
||||||
return selfRemovingListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function returns a listener that will not fire on events where
|
|
||||||
// the target is a remote xul:browser element itself. We'd rather let
|
|
||||||
// the child process handle the event and pass it up via
|
|
||||||
// EventTargetParent.
|
|
||||||
var filteringListeners = new WeakMap();
|
|
||||||
function makeFilteringListener(eventType, listener) {
|
|
||||||
// Some events are actually targeted at the <browser> element
|
|
||||||
// itself, so we only handle the ones where know that won't happen.
|
|
||||||
let eventTypes = ["mousedown", "mouseup", "click"];
|
|
||||||
if (!eventTypes.includes(eventType) || !listener ||
|
|
||||||
(typeof listener != "object" && typeof listener != "function")) {
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filteringListeners.has(listener)) {
|
|
||||||
return filteringListeners.get(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
function filter(event) {
|
|
||||||
let target = event.originalTarget;
|
|
||||||
if (target instanceof Ci.nsIDOMXULElement &&
|
|
||||||
target.localName == "browser" &&
|
|
||||||
target.isRemoteBrowser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("handleEvent" in listener) {
|
|
||||||
listener.handleEvent(event);
|
|
||||||
} else {
|
|
||||||
listener.call(event.target, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filteringListeners.set(listener, filter);
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This interposition redirects addEventListener and
|
|
||||||
// removeEventListener to EventTargetParent.
|
|
||||||
var EventTargetInterposition = new Interposition("EventTargetInterposition");
|
|
||||||
|
|
||||||
EventTargetInterposition.methods.addEventListener =
|
|
||||||
function(addon, target, type, listener, options, wantsUntrusted) {
|
|
||||||
let delayed = CompatWarning.delayedWarning(
|
|
||||||
`Registering a ${type} event listener on content DOM nodes` +
|
|
||||||
" needs to happen in the content process.",
|
|
||||||
addon, CompatWarning.warnings.DOM_events);
|
|
||||||
|
|
||||||
let useCapture =
|
|
||||||
options === true || (typeof options == "object" && options.capture) || false;
|
|
||||||
if (typeof options == "object" && options.once) {
|
|
||||||
listener = makeSelfRemovingListener(addon, target, type, listener, useCapture);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventTargetParent.addEventListener(addon, target, type, listener,
|
|
||||||
useCapture, wantsUntrusted, delayed);
|
|
||||||
target.addEventListener(type, makeFilteringListener(type, listener),
|
|
||||||
useCapture, wantsUntrusted);
|
|
||||||
};
|
|
||||||
|
|
||||||
EventTargetInterposition.methods.removeEventListener =
|
|
||||||
function(addon, target, type, listener, options) {
|
|
||||||
let useCapture =
|
|
||||||
options === true || (typeof options == "object" && options.capture) || false;
|
|
||||||
|
|
||||||
if (selfRemovingListeners.has(listener)) {
|
|
||||||
listener = selfRemovingListeners.get(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventTargetParent.removeEventListener(addon, target, type, listener, useCapture);
|
|
||||||
target.removeEventListener(type, makeFilteringListener(type, listener), useCapture);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This interposition intercepts accesses to |rootTreeItem| on a child
|
|
||||||
// process docshell. In the child, each docshell is its own
|
|
||||||
// root. However, add-ons expect the root to be the chrome docshell,
|
|
||||||
// so we make that happen here.
|
|
||||||
var ContentDocShellTreeItemInterposition = new Interposition("ContentDocShellTreeItemInterposition");
|
|
||||||
|
|
||||||
ContentDocShellTreeItemInterposition.getters.rootTreeItem =
|
|
||||||
function(addon, target) {
|
|
||||||
// The chrome global in the child.
|
|
||||||
let chromeGlobal = target.rootTreeItem
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
|
||||||
|
|
||||||
// Map it to a <browser> element and window.
|
|
||||||
let browser = RemoteAddonsParent.globalToBrowser.get(chromeGlobal);
|
|
||||||
if (!browser) {
|
|
||||||
// Somehow we have a CPOW from the child, but it hasn't sent us
|
|
||||||
// its global yet. That shouldn't happen, but return null just
|
|
||||||
// in case.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let chromeWin = browser.ownerGlobal;
|
|
||||||
|
|
||||||
// Return that window's docshell.
|
|
||||||
return chromeWin.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
|
||||||
.QueryInterface(Ci.nsIDocShellTreeItem);
|
|
||||||
};
|
|
||||||
|
|
||||||
function chromeGlobalForContentWindow(window) {
|
|
||||||
return window
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
|
||||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
|
||||||
.rootTreeItem
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This object manages sandboxes created with content principals in
|
|
||||||
// the parent. We actually create these sandboxes in the child process
|
|
||||||
// so that the code loaded into them runs there. The resulting sandbox
|
|
||||||
// object is a CPOW. This is primarly useful for Greasemonkey.
|
|
||||||
var SandboxParent = {
|
|
||||||
componentsMap: new WeakMap(),
|
|
||||||
|
|
||||||
makeContentSandbox(addon, chromeGlobal, principals, ...rest) {
|
|
||||||
CompatWarning.warn("This sandbox should be created from the child process.",
|
|
||||||
addon, CompatWarning.warnings.sandboxes);
|
|
||||||
if (rest.length) {
|
|
||||||
// Do a shallow copy of the options object into the child
|
|
||||||
// process. This way we don't have to access it through a Chrome
|
|
||||||
// object wrapper, which would not let us access any properties.
|
|
||||||
//
|
|
||||||
// The only object property here is sandboxPrototype. We assume
|
|
||||||
// it's a child process object (since that's what Greasemonkey
|
|
||||||
// does) and leave it alone.
|
|
||||||
let options = rest[0];
|
|
||||||
let optionsCopy = new chromeGlobal.Object();
|
|
||||||
for (let prop in options) {
|
|
||||||
optionsCopy[prop] = options[prop];
|
|
||||||
}
|
|
||||||
rest[0] = optionsCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a sandbox in the child.
|
|
||||||
let cu = chromeGlobal.Components.utils;
|
|
||||||
let sandbox = cu.Sandbox(principals, ...rest);
|
|
||||||
|
|
||||||
// We need to save the sandbox in the child so it won't get
|
|
||||||
// GCed. The child will drop this reference at the next
|
|
||||||
// navigation.
|
|
||||||
chromeGlobal.addSandbox(sandbox);
|
|
||||||
|
|
||||||
// The sandbox CPOW will be kept alive by whomever we return it
|
|
||||||
// to. Its lifetime is unrelated to that of the sandbox object in
|
|
||||||
// the child.
|
|
||||||
this.componentsMap.set(sandbox, cu);
|
|
||||||
return sandbox;
|
|
||||||
},
|
|
||||||
|
|
||||||
evalInSandbox(code, sandbox, ...rest) {
|
|
||||||
let cu = this.componentsMap.get(sandbox);
|
|
||||||
return cu.evalInSandbox(code, sandbox, ...rest);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This interposition redirects calls to Cu.Sandbox and
|
|
||||||
// Cu.evalInSandbox to SandboxParent if the principals are content
|
|
||||||
// principals.
|
|
||||||
var ComponentsUtilsInterposition = new Interposition("ComponentsUtilsInterposition");
|
|
||||||
|
|
||||||
ComponentsUtilsInterposition.methods.Sandbox =
|
|
||||||
function(addon, target, principals, ...rest) {
|
|
||||||
// principals can be a window object, a list of window objects, or
|
|
||||||
// something else (a string, for example).
|
|
||||||
if (principals &&
|
|
||||||
typeof(principals) == "object" &&
|
|
||||||
Cu.isCrossProcessWrapper(principals) &&
|
|
||||||
principals instanceof Ci.nsIDOMWindow) {
|
|
||||||
let chromeGlobal = chromeGlobalForContentWindow(principals);
|
|
||||||
return SandboxParent.makeContentSandbox(addon, chromeGlobal, principals, ...rest);
|
|
||||||
} else if (principals &&
|
|
||||||
typeof(principals) == "object" &&
|
|
||||||
"every" in principals &&
|
|
||||||
principals.length &&
|
|
||||||
principals.every(e => e instanceof Ci.nsIDOMWindow && Cu.isCrossProcessWrapper(e))) {
|
|
||||||
let chromeGlobal = chromeGlobalForContentWindow(principals[0]);
|
|
||||||
|
|
||||||
// The principals we pass to the content process must use an
|
|
||||||
// Array object from the content process.
|
|
||||||
let array = new chromeGlobal.Array();
|
|
||||||
for (let i = 0; i < principals.length; i++) {
|
|
||||||
array[i] = principals[i];
|
|
||||||
}
|
|
||||||
return SandboxParent.makeContentSandbox(addon, chromeGlobal, array, ...rest);
|
|
||||||
}
|
|
||||||
return Cu.Sandbox(principals, ...rest);
|
|
||||||
};
|
|
||||||
|
|
||||||
ComponentsUtilsInterposition.methods.evalInSandbox =
|
|
||||||
function(addon, target, code, sandbox, ...rest) {
|
|
||||||
if (sandbox && Cu.isCrossProcessWrapper(sandbox)) {
|
|
||||||
return SandboxParent.evalInSandbox(code, sandbox, ...rest);
|
|
||||||
}
|
|
||||||
return Cu.evalInSandbox(code, sandbox, ...rest);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This interposition handles cases where an add-on tries to import a
|
|
||||||
// chrome XUL node into a content document. It doesn't actually do the
|
|
||||||
// import, which we can't support. It just avoids throwing an
|
|
||||||
// exception.
|
|
||||||
var ContentDocumentInterposition = new Interposition("ContentDocumentInterposition");
|
|
||||||
|
|
||||||
ContentDocumentInterposition.methods.importNode =
|
|
||||||
function(addon, target, node, deep) {
|
|
||||||
if (!Cu.isCrossProcessWrapper(node)) {
|
|
||||||
// Trying to import a node from the parent process into the
|
|
||||||
// child process. We don't support this now. Video Download
|
|
||||||
// Helper does this in domhook-service.js to add a XUL
|
|
||||||
// popupmenu to content.
|
|
||||||
Cu.reportError("Calling contentDocument.importNode on a XUL node is not allowed.");
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return target.importNode(node, deep);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This interposition ensures that calling browser.docShell from an
|
|
||||||
// add-on returns a CPOW around the docshell.
|
|
||||||
var RemoteBrowserElementInterposition = new Interposition("RemoteBrowserElementInterposition",
|
|
||||||
EventTargetInterposition);
|
|
||||||
|
|
||||||
RemoteBrowserElementInterposition.getters.docShell = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to content docshell will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(target);
|
|
||||||
if (!remoteChromeGlobal) {
|
|
||||||
// We may not have any messages from this tab yet.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return remoteChromeGlobal.docShell;
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to browser.sessionHistory will no longer " +
|
|
||||||
"work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
return getSessionHistory(target);
|
|
||||||
};
|
|
||||||
|
|
||||||
// We use this in place of the real browser.contentWindow if we
|
|
||||||
// haven't yet received a CPOW for the child process's window. This
|
|
||||||
// happens if the tab has just started loading.
|
|
||||||
function makeDummyContentWindow(browser) {
|
|
||||||
let dummyContentWindow = {
|
|
||||||
set location(url) {
|
|
||||||
browser.loadURI(url, null, null);
|
|
||||||
},
|
|
||||||
document: {
|
|
||||||
readyState: "loading",
|
|
||||||
location: { href: "about:blank" }
|
|
||||||
},
|
|
||||||
frames: [],
|
|
||||||
};
|
|
||||||
dummyContentWindow.top = dummyContentWindow;
|
|
||||||
dummyContentWindow.document.defaultView = dummyContentWindow;
|
|
||||||
browser._contentWindow = dummyContentWindow;
|
|
||||||
return dummyContentWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteBrowserElementInterposition.getters.contentWindow = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to browser.contentWindow will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
// If we don't have a CPOW yet, just return something we can use for
|
|
||||||
// setting the location. This is useful for tests that create a tab
|
|
||||||
// and immediately set contentWindow.location.
|
|
||||||
if (!target.contentWindowAsCPOW) {
|
|
||||||
CompatWarning.warn("CPOW to the content window does not exist yet, dummy content window is created.");
|
|
||||||
return makeDummyContentWindow(target);
|
|
||||||
}
|
|
||||||
return target.contentWindowAsCPOW;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getContentDocument(addon, browser) {
|
|
||||||
if (!browser.contentWindowAsCPOW) {
|
|
||||||
return makeDummyContentWindow(browser).document;
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = Prefetcher.lookupInCache(addon, browser.contentWindowAsCPOW, "document");
|
|
||||||
if (doc) {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return browser.contentWindowAsCPOW.document;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSessionHistory(browser) {
|
|
||||||
let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(browser);
|
|
||||||
if (!remoteChromeGlobal) {
|
|
||||||
CompatWarning.warn("CPOW for the remote browser docShell hasn't been received yet.");
|
|
||||||
// We may not have any messages from this tab yet.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return remoteChromeGlobal.docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteBrowserElementInterposition.getters.contentDocument = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to browser.contentDocument will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
return getContentDocument(addon, target);
|
|
||||||
};
|
|
||||||
|
|
||||||
var TabBrowserElementInterposition = new Interposition("TabBrowserElementInterposition",
|
|
||||||
EventTargetInterposition);
|
|
||||||
|
|
||||||
TabBrowserElementInterposition.getters.contentWindow = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to gBrowser.contentWindow will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
if (!target.selectedBrowser.contentWindowAsCPOW) {
|
|
||||||
return makeDummyContentWindow(target.selectedBrowser);
|
|
||||||
}
|
|
||||||
return target.selectedBrowser.contentWindowAsCPOW;
|
|
||||||
};
|
|
||||||
|
|
||||||
TabBrowserElementInterposition.getters.contentDocument = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to gBrowser.contentDocument will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
let browser = target.selectedBrowser;
|
|
||||||
return getContentDocument(addon, browser);
|
|
||||||
};
|
|
||||||
|
|
||||||
TabBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to gBrowser.sessionHistory will no " +
|
|
||||||
"longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
let browser = target.selectedBrowser;
|
|
||||||
if (!browser.isRemoteBrowser) {
|
|
||||||
return browser.sessionHistory;
|
|
||||||
}
|
|
||||||
return getSessionHistory(browser);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function returns a wrapper around an
|
// This function returns a wrapper around an
|
||||||
// nsIWebProgressListener. When the wrapper is invoked, it calls the
|
// nsIWebProgressListener. When the wrapper is invoked, it calls the
|
||||||
@ -1001,61 +132,6 @@ TabBrowserElementInterposition.methods.removeProgressListener = function(addon,
|
|||||||
return target.removeProgressListener(wrapProgressListener("global", listener));
|
return target.removeProgressListener(wrapProgressListener("global", listener));
|
||||||
};
|
};
|
||||||
|
|
||||||
TabBrowserElementInterposition.methods.addTabsProgressListener = function(addon, target, listener) {
|
|
||||||
if (!target.ownerGlobal.gMultiProcessBrowser) {
|
|
||||||
return target.addTabsProgressListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationTracker.add(["web-progress", addon]);
|
|
||||||
return target.addTabsProgressListener(wrapProgressListener("tabs", listener));
|
|
||||||
};
|
|
||||||
|
|
||||||
TabBrowserElementInterposition.methods.removeTabsProgressListener = function(addon, target, listener) {
|
|
||||||
if (!target.ownerGlobal.gMultiProcessBrowser) {
|
|
||||||
return target.removeTabsProgressListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationTracker.remove(["web-progress", addon]);
|
|
||||||
return target.removeTabsProgressListener(wrapProgressListener("tabs", listener));
|
|
||||||
};
|
|
||||||
|
|
||||||
var ChromeWindowInterposition = new Interposition("ChromeWindowInterposition",
|
|
||||||
EventTargetInterposition);
|
|
||||||
|
|
||||||
ChromeWindowInterposition.getters.content = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to chromeWindow.content will no longer work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
let browser = target.gBrowser.selectedBrowser;
|
|
||||||
if (!browser.contentWindowAsCPOW) {
|
|
||||||
return makeDummyContentWindow(browser);
|
|
||||||
}
|
|
||||||
return browser.contentWindowAsCPOW;
|
|
||||||
};
|
|
||||||
|
|
||||||
var RemoteWebNavigationInterposition = new Interposition("RemoteWebNavigation");
|
|
||||||
|
|
||||||
RemoteWebNavigationInterposition.getters.sessionHistory = function(addon, target) {
|
|
||||||
CompatWarning.warn("Direct access to webNavigation.sessionHistory will no longer " +
|
|
||||||
"work in the chrome process.",
|
|
||||||
addon, CompatWarning.warnings.content);
|
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDocShell) {
|
|
||||||
// We must have a non-remote browser, so we can go ahead
|
|
||||||
// and just return the real sessionHistory.
|
|
||||||
return target.sessionHistory;
|
|
||||||
}
|
|
||||||
|
|
||||||
let impl = target.wrappedJSObject;
|
|
||||||
if (!impl) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let browser = impl._browser;
|
|
||||||
|
|
||||||
return getSessionHistory(browser);
|
|
||||||
};
|
|
||||||
|
|
||||||
var RemoteAddonsParent = {
|
var RemoteAddonsParent = {
|
||||||
init() {
|
init() {
|
||||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||||
@ -1072,36 +148,13 @@ var RemoteAddonsParent = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInterfaceInterpositions() {
|
getInterfaceInterpositions() {
|
||||||
let result = {};
|
return {};
|
||||||
|
|
||||||
function register(intf, interp) {
|
|
||||||
result[intf.number] = interp;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(Ci.nsICategoryManager, CategoryManagerInterposition);
|
|
||||||
register(Ci.nsIComponentRegistrar, ComponentRegistrarInterposition);
|
|
||||||
register(Ci.nsIObserverService, ObserverInterposition);
|
|
||||||
register(Ci.nsIXPCComponents_Utils, ComponentsUtilsInterposition);
|
|
||||||
register(Ci.nsIWebNavigation, RemoteWebNavigationInterposition);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTaggedInterpositions() {
|
getTaggedInterpositions() {
|
||||||
let result = {};
|
return {
|
||||||
|
TabBrowserElement: TabBrowserElementInterposition,
|
||||||
function register(tag, interp) {
|
};
|
||||||
result[tag] = interp;
|
|
||||||
}
|
|
||||||
|
|
||||||
register("EventTarget", EventTargetInterposition);
|
|
||||||
register("ContentDocShellTreeItem", ContentDocShellTreeItemInterposition);
|
|
||||||
register("ContentDocument", ContentDocumentInterposition);
|
|
||||||
register("RemoteBrowserElement", RemoteBrowserElementInterposition);
|
|
||||||
register("TabBrowserElement", TabBrowserElementInterposition);
|
|
||||||
register("ChromeWindow", ChromeWindowInterposition);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
receiveMessage(msg) {
|
receiveMessage(msg) {
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
with Files('**'):
|
with Files('**'):
|
||||||
BUG_COMPONENT = ('Firefox', 'Extension Compatibility')
|
BUG_COMPONENT = ('Firefox', 'Extension Compatibility')
|
||||||
|
|
||||||
TEST_DIRS += ['tests']
|
|
||||||
|
|
||||||
EXTRA_COMPONENTS += [
|
EXTRA_COMPONENTS += [
|
||||||
'addoncompat.manifest',
|
'addoncompat.manifest',
|
||||||
'defaultShims.js',
|
'defaultShims.js',
|
||||||
@ -16,7 +14,6 @@ EXTRA_COMPONENTS += [
|
|||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
'CompatWarning.jsm',
|
|
||||||
'Prefetcher.jsm',
|
'Prefetcher.jsm',
|
||||||
'RemoteAddonsChild.jsm',
|
'RemoteAddonsChild.jsm',
|
||||||
'RemoteAddonsParent.jsm',
|
'RemoteAddonsParent.jsm',
|
||||||
|
@ -109,24 +109,10 @@ AddonInterpositionService.prototype = {
|
|||||||
return Cu.getCrossProcessWrapperTag(target);
|
return Cu.getCrossProcessWrapperTag(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDOMXULElement) {
|
|
||||||
if (target.localName == "browser" && target.isRemoteBrowser) {
|
|
||||||
return "RemoteBrowserElement";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.ownerGlobal && target === target.ownerGlobal.gBrowser) {
|
if (target.ownerGlobal && target === target.ownerGlobal.gBrowser) {
|
||||||
return "TabBrowserElement";
|
return "TabBrowserElement";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDOMChromeWindow && target.gMultiProcessBrowser) {
|
|
||||||
return "ChromeWindow";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target instanceof Ci.nsIDOMEventTarget) {
|
|
||||||
return "EventTarget";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "generic";
|
return "generic";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,628 +0,0 @@
|
|||||||
// This file also defines a frame script.
|
|
||||||
/* eslint-env mozilla/frame-script */
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
|
|
||||||
const baseURL = "http://mochi.test:8888/browser/" +
|
|
||||||
"toolkit/components/addoncompat/tests/browser/";
|
|
||||||
|
|
||||||
var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
|
|
||||||
.getService(Ci.nsIContentSecurityManager);
|
|
||||||
|
|
||||||
function forEachWindow(f) {
|
|
||||||
let wins = Services.wm.getEnumerator("navigator:browser");
|
|
||||||
while (wins.hasMoreElements()) {
|
|
||||||
let win = wins.getNext();
|
|
||||||
f(win);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addLoadListener(target, listener) {
|
|
||||||
target.addEventListener("load", function(event) {
|
|
||||||
return listener(event);
|
|
||||||
}, {capture: true, once: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
var gWin;
|
|
||||||
var gBrowser;
|
|
||||||
var ok, is, info;
|
|
||||||
|
|
||||||
function removeTab(tab, done) {
|
|
||||||
// Remove the tab in a different turn of the event loop. This way
|
|
||||||
// the nested event loop in removeTab doesn't conflict with the
|
|
||||||
// event listener shims.
|
|
||||||
gWin.setTimeout(() => {
|
|
||||||
gBrowser.removeTab(tab);
|
|
||||||
done();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the shims for window.content, browser.contentWindow,
|
|
||||||
// and browser.contentDocument are working.
|
|
||||||
function testContentWindow() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
ok(gWin.content, "content is defined on chrome window");
|
|
||||||
ok(browser.contentWindow, "contentWindow is defined");
|
|
||||||
ok(browser.contentDocument, "contentWindow is defined");
|
|
||||||
is(gWin.content, browser.contentWindow, "content === contentWindow");
|
|
||||||
ok(browser.webNavigation.sessionHistory, "sessionHistory is defined");
|
|
||||||
|
|
||||||
ok(browser.contentDocument.getElementById("link"), "link present in document");
|
|
||||||
|
|
||||||
// FIXME: Waiting on bug 1073631.
|
|
||||||
// is(browser.contentWindow.wrappedJSObject.global, 3, "global available on document");
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for bug 1060046 and bug 1072607. We want to make sure that
|
|
||||||
// adding and removing listeners works as expected.
|
|
||||||
function testListeners() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url1 = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
const url2 = baseURL + "browser_addonShims_testpage2.html";
|
|
||||||
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url2); // eslint-disable-line no-undef
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
function dummyHandler() {}
|
|
||||||
|
|
||||||
// Test that a removed listener stays removed (bug
|
|
||||||
// 1072607). We're looking to make sure that adding and removing
|
|
||||||
// a listener here doesn't cause later listeners to fire more
|
|
||||||
// than once.
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
gBrowser.addEventListener("load", dummyHandler, true);
|
|
||||||
gBrowser.removeEventListener("load", dummyHandler, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We also want to make sure that this listener doesn't fire
|
|
||||||
// after it's removed.
|
|
||||||
let loadWithRemoveCount = 0;
|
|
||||||
addLoadListener(browser, function handler1(event) {
|
|
||||||
loadWithRemoveCount++;
|
|
||||||
is(event.target.documentURI, url1, "only fire for first url");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load url1 and then url2. We want to check that:
|
|
||||||
// 1. handler1 only fires for url1.
|
|
||||||
// 2. handler2 only fires once for url1 (so the second time it
|
|
||||||
// fires should be for url2).
|
|
||||||
let loadCount = 0;
|
|
||||||
browser.addEventListener("load", function handler2(event) {
|
|
||||||
loadCount++;
|
|
||||||
if (loadCount == 1) {
|
|
||||||
is(event.target.documentURI, url1, "first load is for first page loaded");
|
|
||||||
browser.loadURI(url2);
|
|
||||||
} else {
|
|
||||||
gBrowser.removeEventListener("load", handler2, true);
|
|
||||||
|
|
||||||
is(event.target.documentURI, url2, "second load is for second page loaded");
|
|
||||||
is(loadWithRemoveCount, 1, "load handler is only called once");
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
browser.loadURI(url1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for bug 1059207. We want to make sure that adding a capturing
|
|
||||||
// listener and a non-capturing listener to the same element works as
|
|
||||||
// expected.
|
|
||||||
function testCapturing() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
let capturingCount = 0;
|
|
||||||
let nonCapturingCount = 0;
|
|
||||||
|
|
||||||
function capturingHandler(event) {
|
|
||||||
is(capturingCount, 0, "capturing handler called once");
|
|
||||||
is(nonCapturingCount, 0, "capturing handler called before bubbling handler");
|
|
||||||
capturingCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function nonCapturingHandler(event) {
|
|
||||||
is(capturingCount, 1, "bubbling handler called after capturing handler");
|
|
||||||
is(nonCapturingCount, 0, "bubbling handler called once");
|
|
||||||
nonCapturingCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
gBrowser.addEventListener("mousedown", capturingHandler, true);
|
|
||||||
gBrowser.addEventListener("mousedown", nonCapturingHandler);
|
|
||||||
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
let win = browser.contentWindow;
|
|
||||||
let event = win.document.createEvent("MouseEvents");
|
|
||||||
event.initMouseEvent("mousedown", true, false, win, 1,
|
|
||||||
1, 0, 0, 0, // screenX, screenY, clientX, clientY
|
|
||||||
false, false, false, false, // ctrlKey, altKey, shiftKey, metaKey
|
|
||||||
0, null); // buttonCode, relatedTarget
|
|
||||||
|
|
||||||
let element = win.document.getElementById("output");
|
|
||||||
element.dispatchEvent(event);
|
|
||||||
|
|
||||||
is(capturingCount, 1, "capturing handler fired");
|
|
||||||
is(nonCapturingCount, 1, "bubbling handler fired");
|
|
||||||
|
|
||||||
gBrowser.removeEventListener("mousedown", capturingHandler, true);
|
|
||||||
gBrowser.removeEventListener("mousedown", nonCapturingHandler);
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we get observer notifications that normally fire in the
|
|
||||||
// child.
|
|
||||||
function testObserver() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
let observerFired = 0;
|
|
||||||
|
|
||||||
function observer(subject, topic, data) {
|
|
||||||
Services.obs.removeObserver(observer, "document-element-inserted");
|
|
||||||
observerFired++;
|
|
||||||
}
|
|
||||||
Services.obs.addObserver(observer, "document-element-inserted");
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
browser.addEventListener("load", function handler() {
|
|
||||||
count++;
|
|
||||||
if (count == 1) {
|
|
||||||
browser.reload();
|
|
||||||
} else {
|
|
||||||
browser.removeEventListener("load", handler);
|
|
||||||
|
|
||||||
is(observerFired, 1, "got observer notification");
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for bug 1072472. Make sure that creating a sandbox to run code
|
|
||||||
// in the content window works. This is essentially a test for
|
|
||||||
// Greasemonkey.
|
|
||||||
function testSandbox() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
browser.addEventListener("load", function() {
|
|
||||||
let sandbox = Cu.Sandbox(browser.contentWindow,
|
|
||||||
{sandboxPrototype: browser.contentWindow,
|
|
||||||
wantXrays: false});
|
|
||||||
Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
|
|
||||||
Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello';", sandbox);
|
|
||||||
|
|
||||||
is(browser.contentDocument.getElementById("output").innerHTML, "hello",
|
|
||||||
"sandbox code ran successfully");
|
|
||||||
|
|
||||||
// Now try a sandbox with expanded principals.
|
|
||||||
sandbox = Cu.Sandbox([browser.contentWindow],
|
|
||||||
{sandboxPrototype: browser.contentWindow,
|
|
||||||
wantXrays: false});
|
|
||||||
Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
|
|
||||||
Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello2';", sandbox);
|
|
||||||
|
|
||||||
is(browser.contentDocument.getElementById("output").innerHTML, "hello2",
|
|
||||||
"EP sandbox code ran successfully");
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
}, {capture: true, once: true});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for bug 1095305. We just want to make sure that loading some
|
|
||||||
// unprivileged content from an add-on package doesn't crash.
|
|
||||||
function testAddonContent() {
|
|
||||||
let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
|
||||||
.getService(Ci.nsIChromeRegistry);
|
|
||||||
let base = chromeRegistry.convertChromeURL(Services.io.newURI("chrome://addonshim1/content/"));
|
|
||||||
|
|
||||||
let res = Services.io.getProtocolHandler("resource")
|
|
||||||
.QueryInterface(Ci.nsIResProtocolHandler);
|
|
||||||
res.setSubstitution("addonshim1", base);
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = "resource://addonshim1/page.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
res.setSubstitution("addonshim1", null);
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Test for bug 1102410. We check that multiple nsIAboutModule's can be
|
|
||||||
// registered in the parent, and that the child can browse to each of
|
|
||||||
// the registered about: pages.
|
|
||||||
function testAboutModuleRegistration() {
|
|
||||||
let Registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
|
||||||
|
|
||||||
let modulesToUnregister = new Map();
|
|
||||||
|
|
||||||
function TestChannel(uri, aLoadInfo, aboutName) {
|
|
||||||
this.aboutName = aboutName;
|
|
||||||
this.loadInfo = aLoadInfo;
|
|
||||||
this.URI = this.originalURI = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
TestChannel.prototype = {
|
|
||||||
asyncOpen(listener, context) {
|
|
||||||
let stream = this.open();
|
|
||||||
let runnable = {
|
|
||||||
run: () => {
|
|
||||||
try {
|
|
||||||
listener.onStartRequest(this, context);
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
listener.onDataAvailable(this, context, stream, 0, stream.available());
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
listener.onStopRequest(this, context, Cr.NS_OK);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Services.tm.dispatchToMainThread(runnable);
|
|
||||||
},
|
|
||||||
|
|
||||||
asyncOpen2(listener) {
|
|
||||||
// throws an error if security checks fail
|
|
||||||
var outListener = contentSecManager.performSecurityCheck(this, listener);
|
|
||||||
return this.asyncOpen(outListener, null);
|
|
||||||
},
|
|
||||||
|
|
||||||
open() {
|
|
||||||
function getWindow(channel) {
|
|
||||||
try {
|
|
||||||
if (channel.notificationCallbacks)
|
|
||||||
return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
|
|
||||||
return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = `<html><h1>${this.aboutName}</h1></html>`;
|
|
||||||
let wnd = getWindow(this);
|
|
||||||
if (!wnd)
|
|
||||||
throw Cr.NS_ERROR_UNEXPECTED;
|
|
||||||
|
|
||||||
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
|
||||||
stream.setData(data, data.length);
|
|
||||||
return stream;
|
|
||||||
},
|
|
||||||
|
|
||||||
open2() {
|
|
||||||
// throws an error if security checks fail
|
|
||||||
contentSecManager.performSecurityCheck(this, null);
|
|
||||||
return this.open();
|
|
||||||
},
|
|
||||||
|
|
||||||
isPending() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
suspend() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
resume() {
|
|
||||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function creates a new nsIAboutModule and registers it. Callers
|
|
||||||
* should also call unregisterModules after using this function to clean
|
|
||||||
* up the nsIAboutModules at the end of this test.
|
|
||||||
*
|
|
||||||
* @param aboutName
|
|
||||||
* This will be the string after about: used to refer to this module.
|
|
||||||
* For example, if aboutName is foo, you can refer to this module by
|
|
||||||
* browsing to about:foo.
|
|
||||||
*
|
|
||||||
* @param uuid
|
|
||||||
* A unique identifer string for this module. For example,
|
|
||||||
* "5f3a921b-250f-4ac5-a61c-8f79372e6063"
|
|
||||||
*/
|
|
||||||
let createAndRegisterAboutModule = function(aboutName, uuid) {
|
|
||||||
|
|
||||||
let AboutModule = function() {};
|
|
||||||
|
|
||||||
AboutModule.prototype = {
|
|
||||||
classID: Components.ID(uuid),
|
|
||||||
classDescription: `Testing About Module for about:${aboutName}`,
|
|
||||||
contractID: `@mozilla.org/network/protocol/about;1?what=${aboutName}`,
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
|
|
||||||
|
|
||||||
newChannel: (aURI, aLoadInfo) => {
|
|
||||||
return new TestChannel(aURI, aLoadInfo, aboutName);
|
|
||||||
},
|
|
||||||
|
|
||||||
getURIFlags: (aURI) => {
|
|
||||||
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
|
||||||
Ci.nsIAboutModule.ALLOW_SCRIPT;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let factory = {
|
|
||||||
createInstance(outer, iid) {
|
|
||||||
if (outer) {
|
|
||||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
||||||
}
|
|
||||||
return new AboutModule();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Registrar.registerFactory(AboutModule.prototype.classID,
|
|
||||||
AboutModule.prototype.classDescription,
|
|
||||||
AboutModule.prototype.contractID,
|
|
||||||
factory);
|
|
||||||
|
|
||||||
modulesToUnregister.set(AboutModule.prototype.classID,
|
|
||||||
factory);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters any nsIAboutModules registered with
|
|
||||||
* createAndRegisterAboutModule.
|
|
||||||
*/
|
|
||||||
let unregisterModules = () => {
|
|
||||||
for (let [classID, factory] of modulesToUnregister) {
|
|
||||||
Registrar.unregisterFactory(classID, factory);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a browser, and sends it a framescript to attempt to
|
|
||||||
* load some about: pages. The frame script will send a test:result
|
|
||||||
* message on completion, passing back a data object with:
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* pass: true
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* on success, and:
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* pass: false,
|
|
||||||
* errorMsg: message,
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* on failure.
|
|
||||||
*
|
|
||||||
* @param browser
|
|
||||||
* The browser to send the framescript to.
|
|
||||||
*/
|
|
||||||
let testAboutModulesWork = (browser) => {
|
|
||||||
let testConnection = () => {
|
|
||||||
// This section is loaded into a frame script.
|
|
||||||
/* global content:false */
|
|
||||||
let request = new content.XMLHttpRequest();
|
|
||||||
try {
|
|
||||||
request.open("GET", "about:test1", false);
|
|
||||||
request.send(null);
|
|
||||||
if (request.status != 200) {
|
|
||||||
throw (`about:test1 response had status ${request.status} - expected 200`);
|
|
||||||
}
|
|
||||||
if (!request.responseText.includes("test1")) {
|
|
||||||
throw (`about:test1 response had result ${request.responseText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
request = new content.XMLHttpRequest();
|
|
||||||
request.open("GET", "about:test2", false);
|
|
||||||
request.send(null);
|
|
||||||
|
|
||||||
if (request.status != 200) {
|
|
||||||
throw (`about:test2 response had status ${request.status} - expected 200`);
|
|
||||||
}
|
|
||||||
if (!request.responseText.includes("test2")) {
|
|
||||||
throw (`about:test2 response had result ${request.responseText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAsyncMessage("test:result", {
|
|
||||||
pass: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
sendAsyncMessage("test:result", {
|
|
||||||
pass: false,
|
|
||||||
errorMsg: e.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let mm = browser.messageManager;
|
|
||||||
mm.addMessageListener("test:result", function onTestResult(message) {
|
|
||||||
mm.removeMessageListener("test:result", onTestResult);
|
|
||||||
if (message.data.pass) {
|
|
||||||
ok(true, "Connections to about: pages were successful");
|
|
||||||
} else {
|
|
||||||
ok(false, message.data.errorMsg);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
mm.loadFrameScript("data:,(" + testConnection.toString() + ")();", false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Here's where the actual test is performed.
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
createAndRegisterAboutModule("test1", "5f3a921b-250f-4ac5-a61c-8f79372e6063");
|
|
||||||
createAndRegisterAboutModule("test2", "d7ec0389-1d49-40fa-b55c-a1fc3a6dbf6f");
|
|
||||||
|
|
||||||
// This needs to be a chrome-privileged page that loads in the
|
|
||||||
// content process. It needs chrome privs because otherwise the
|
|
||||||
// XHRs for about:test[12] will fail with a privilege error
|
|
||||||
// despite the presence of URI_SAFE_FOR_UNTRUSTED_CONTENT.
|
|
||||||
let newTab = BrowserTestUtils.addTab(gBrowser, "chrome://addonshim1/content/page.html"); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = newTab;
|
|
||||||
let browser = newTab.linkedBrowser;
|
|
||||||
|
|
||||||
addLoadListener(browser, function() {
|
|
||||||
testAboutModulesWork(browser).then(() => {
|
|
||||||
unregisterModules();
|
|
||||||
removeTab(newTab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testProgressListener() {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
|
|
||||||
let sawGlobalLocChange = false;
|
|
||||||
let sawTabsLocChange = false;
|
|
||||||
|
|
||||||
let globalListener = {
|
|
||||||
onLocationChange(webProgress, request, uri) {
|
|
||||||
if (uri.spec == url) {
|
|
||||||
sawGlobalLocChange = true;
|
|
||||||
ok(request instanceof Ci.nsIHttpChannel, "Global listener channel is an HTTP channel");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let tabsListener = {
|
|
||||||
onLocationChange(browser, webProgress, request, uri) {
|
|
||||||
if (uri.spec == url) {
|
|
||||||
sawTabsLocChange = true;
|
|
||||||
ok(request instanceof Ci.nsIHttpChannel, "Tab listener channel is an HTTP channel");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
gBrowser.addProgressListener(globalListener);
|
|
||||||
gBrowser.addTabsProgressListener(tabsListener);
|
|
||||||
info("Added progress listeners");
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
addLoadListener(tab.linkedBrowser, function handler() {
|
|
||||||
ok(sawGlobalLocChange, "Saw global onLocationChange");
|
|
||||||
ok(sawTabsLocChange, "Saw tabs onLocationChange");
|
|
||||||
|
|
||||||
gBrowser.removeProgressListener(globalListener);
|
|
||||||
gBrowser.removeTabsProgressListener(tabsListener);
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testRootTreeItem() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
let win = browser.contentWindow;
|
|
||||||
|
|
||||||
// Add-ons love this crap.
|
|
||||||
let root = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
|
||||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
|
||||||
.rootTreeItem
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIDOMWindow);
|
|
||||||
is(root, gWin, "got correct chrome window");
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testImportNode() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, url); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
let node = gWin.document.createElement("div");
|
|
||||||
let doc = browser.contentDocument;
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = doc.importNode(node, false);
|
|
||||||
} catch (e) {
|
|
||||||
ok(false, "importing threw an exception");
|
|
||||||
}
|
|
||||||
if (browser.isRemoteBrowser) {
|
|
||||||
is(result, node, "got expected import result");
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTab(tab, resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTests(win, funcs) {
|
|
||||||
ok = funcs.ok;
|
|
||||||
is = funcs.is;
|
|
||||||
info = funcs.info;
|
|
||||||
|
|
||||||
gWin = win;
|
|
||||||
gBrowser = win.gBrowser;
|
|
||||||
|
|
||||||
return testContentWindow().
|
|
||||||
then(testListeners).
|
|
||||||
then(testCapturing).
|
|
||||||
then(testObserver).
|
|
||||||
then(testSandbox).
|
|
||||||
then(testAddonContent).
|
|
||||||
then(testAboutModuleRegistration).
|
|
||||||
then(testProgressListener).
|
|
||||||
then(testRootTreeItem).
|
|
||||||
then(testImportNode).
|
|
||||||
then(Promise.resolve());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
bootstrap.js API
|
|
||||||
*/
|
|
||||||
|
|
||||||
function startup(aData, aReason) {
|
|
||||||
forEachWindow(win => {
|
|
||||||
win.runAddonShimTests = (funcs) => runTests(win, funcs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shutdown(aData, aReason) {
|
|
||||||
forEachWindow(win => {
|
|
||||||
delete win.runAddonShimTests;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function install(aData, aReason) {
|
|
||||||
}
|
|
||||||
|
|
||||||
function uninstall(aData, aReason) {
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
content addonshim1 content/
|
|
@ -1,2 +0,0 @@
|
|||||||
<html>
|
|
||||||
</html>
|
|
@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
|
|
||||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
|
||||||
|
|
||||||
<Description about="urn:mozilla:install-manifest">
|
|
||||||
<em:id>test-addon-shim-1@tests.mozilla.org</em:id>
|
|
||||||
<em:version>1</em:version>
|
|
||||||
<em:type>2</em:type>
|
|
||||||
<em:bootstrap>true</em:bootstrap>
|
|
||||||
|
|
||||||
<!-- Front End MetaData -->
|
|
||||||
<em:name>Test addon shim 1</em:name>
|
|
||||||
<em:description>Test an add-on that needs multiprocess shims.</em:description>
|
|
||||||
<em:multiprocessCompatible>false</em:multiprocessCompatible>
|
|
||||||
|
|
||||||
<em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
|
|
||||||
<em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
|
|
||||||
<em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
|
|
||||||
|
|
||||||
<em:targetApplication>
|
|
||||||
<Description>
|
|
||||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
|
||||||
<em:minVersion>0.3</em:minVersion>
|
|
||||||
<em:maxVersion>*</em:maxVersion>
|
|
||||||
</Description>
|
|
||||||
</em:targetApplication>
|
|
||||||
|
|
||||||
<em:targetApplication>
|
|
||||||
<Description>
|
|
||||||
<em:id>toolkit@mozilla.org</em:id>
|
|
||||||
<em:minVersion>10.0</em:minVersion>
|
|
||||||
<em:maxVersion>*</em:maxVersion>
|
|
||||||
</Description>
|
|
||||||
</em:targetApplication>
|
|
||||||
</Description>
|
|
||||||
</RDF>
|
|
@ -1,7 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
"extends": [
|
|
||||||
"plugin:mozilla/browser-test"
|
|
||||||
]
|
|
||||||
};
|
|
Binary file not shown.
@ -1,9 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
tags = addons
|
|
||||||
support-files =
|
|
||||||
addon.xpi
|
|
||||||
browser_addonShims_testpage.html
|
|
||||||
browser_addonShims_testpage2.html
|
|
||||||
compat-addon.xpi
|
|
||||||
|
|
||||||
[browser_addonShims.js]
|
|
@ -1,62 +0,0 @@
|
|||||||
var {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});
|
|
||||||
var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
|
|
||||||
|
|
||||||
const ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/addon.xpi";
|
|
||||||
const COMPAT_ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/compat-addon.xpi";
|
|
||||||
|
|
||||||
// Install a test add-on that will exercise e10s shims.
|
|
||||||
// url: Location of the add-on.
|
|
||||||
function addAddon(url) {
|
|
||||||
info("Installing add-on: " + url);
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
AddonManager.getInstallForURL(url, installer => {
|
|
||||||
installer.install();
|
|
||||||
let listener = {
|
|
||||||
onInstallEnded(addon, addonInstall) {
|
|
||||||
installer.removeListener(listener);
|
|
||||||
|
|
||||||
// Wait for add-on's startup scripts to execute. See bug 997408
|
|
||||||
executeSoon(function() {
|
|
||||||
resolve(addonInstall);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
installer.addListener(listener);
|
|
||||||
}, "application/x-xpinstall");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uninstall a test add-on.
|
|
||||||
// addon: The addon reference returned from addAddon.
|
|
||||||
function removeAddon(addon) {
|
|
||||||
info("Removing addon.");
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
let listener = {
|
|
||||||
onUninstalled(uninstalledAddon) {
|
|
||||||
if (uninstalledAddon != addon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AddonManager.addAddonListener(listener);
|
|
||||||
addon.uninstall();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
add_task(async function test_addon_shims() {
|
|
||||||
await SpecialPowers.pushPrefEnv({set: [["dom.ipc.shims.enabledWarnings", true]]});
|
|
||||||
|
|
||||||
let addon = await addAddon(ADDON_URL);
|
|
||||||
await window.runAddonShimTests({ok, is, info});
|
|
||||||
await removeAddon(addon);
|
|
||||||
|
|
||||||
if (Services.appinfo.browserTabsRemoteAutostart) {
|
|
||||||
addon = await addAddon(COMPAT_ADDON_URL);
|
|
||||||
await window.runAddonTests({ok, is, info});
|
|
||||||
await removeAddon(addon);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,17 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>shim test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
Hello!
|
|
||||||
|
|
||||||
<a href="browser_addonShims_testpage2.html" id="link">Link</a>
|
|
||||||
<div id="output"></div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var global = 3;
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,16 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>shim test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
Hello!
|
|
||||||
|
|
||||||
<a href="browser_addonShims_testpage.html" id="link">Link</a>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var global = 5;
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Binary file not shown.
@ -1,88 +0,0 @@
|
|||||||
// This file defines a frame script.
|
|
||||||
/* eslint-env mozilla/frame-script */
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
|
|
||||||
const baseURL = "http://mochi.test:8888/browser/" +
|
|
||||||
"toolkit/components/addoncompat/tests/browser/";
|
|
||||||
|
|
||||||
function forEachWindow(f) {
|
|
||||||
let wins = Services.wm.getEnumerator("navigator:browser");
|
|
||||||
while (wins.hasMoreElements()) {
|
|
||||||
let win = wins.getNext();
|
|
||||||
f(win);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addLoadListener(target, listener) {
|
|
||||||
function frameScript() {
|
|
||||||
addEventListener("load", function handler(event) {
|
|
||||||
removeEventListener("load", handler, true);
|
|
||||||
sendAsyncMessage("compat-test:loaded");
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
target.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")()", false);
|
|
||||||
target.messageManager.addMessageListener("compat-test:loaded", function handler() {
|
|
||||||
target.messageManager.removeMessageListener("compat-test:loaded", handler);
|
|
||||||
listener();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var gWin;
|
|
||||||
var gBrowser;
|
|
||||||
var ok, is, info;
|
|
||||||
|
|
||||||
// Make sure that the shims for window.content, browser.contentWindow,
|
|
||||||
// and browser.contentDocument are working.
|
|
||||||
function testContentWindow() {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
const url = baseURL + "browser_addonShims_testpage.html";
|
|
||||||
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); // eslint-disable-line no-undef
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
addLoadListener(browser, function handler() {
|
|
||||||
ok(!gWin.content, "content is defined on chrome window");
|
|
||||||
ok(!browser.contentWindow, "contentWindow is defined");
|
|
||||||
ok(!browser.contentDocument, "contentWindow is defined");
|
|
||||||
|
|
||||||
gBrowser.removeTab(tab);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
browser.loadURI(url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTests(win, funcs) {
|
|
||||||
ok = funcs.ok;
|
|
||||||
is = funcs.is;
|
|
||||||
info = funcs.info;
|
|
||||||
|
|
||||||
gWin = win;
|
|
||||||
gBrowser = win.gBrowser;
|
|
||||||
|
|
||||||
return testContentWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
bootstrap.js API
|
|
||||||
*/
|
|
||||||
|
|
||||||
function startup(aData, aReason) {
|
|
||||||
forEachWindow(win => {
|
|
||||||
win.runAddonTests = (funcs) => runTests(win, funcs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shutdown(aData, aReason) {
|
|
||||||
forEachWindow(win => {
|
|
||||||
delete win.runAddonTests;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function install(aData, aReason) {
|
|
||||||
}
|
|
||||||
|
|
||||||
function uninstall(aData, aReason) {
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
|
|
||||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
|
||||||
|
|
||||||
<Description about="urn:mozilla:install-manifest">
|
|
||||||
<em:id>test-addon-shim-2@tests.mozilla.org</em:id>
|
|
||||||
<em:version>1</em:version>
|
|
||||||
<em:type>2</em:type>
|
|
||||||
<em:bootstrap>true</em:bootstrap>
|
|
||||||
|
|
||||||
<!-- Front End MetaData -->
|
|
||||||
<em:name>Test addon shims 2</em:name>
|
|
||||||
<em:description>Test an add-on that doesn't need multiprocess shims.</em:description>
|
|
||||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
|
||||||
|
|
||||||
<em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
|
|
||||||
<em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
|
|
||||||
<em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
|
|
||||||
|
|
||||||
<em:targetApplication>
|
|
||||||
<Description>
|
|
||||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
|
||||||
<em:minVersion>0.3</em:minVersion>
|
|
||||||
<em:maxVersion>*</em:maxVersion>
|
|
||||||
</Description>
|
|
||||||
</em:targetApplication>
|
|
||||||
|
|
||||||
<em:targetApplication>
|
|
||||||
<Description>
|
|
||||||
<em:id>toolkit@mozilla.org</em:id>
|
|
||||||
<em:minVersion>10.0</em:minVersion>
|
|
||||||
<em:maxVersion>*</em:maxVersion>
|
|
||||||
</Description>
|
|
||||||
</em:targetApplication>
|
|
||||||
</Description>
|
|
||||||
</RDF>
|
|
@ -1,7 +0,0 @@
|
|||||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
||||||
# vim: set filetype=python:
|
|
||||||
# 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/.
|
|
||||||
|
|
||||||
BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
|
|
Loading…
Reference in New Issue
Block a user