aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.12/lib/sdk/content
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.12/lib/sdk/content')
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js870
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content-worker.js308
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content.js15
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/loader.js204
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/symbiont.js197
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js46
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/worker.js582
7 files changed, 2222 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js b/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js
new file mode 100644
index 0000000..76df7f6
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js
@@ -0,0 +1,870 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* Trick the linker in order to avoid error on `Components.interfaces` usage.
+ We are tricking the linker with `require('./content-proxy.js')` from
+ worjer.js in order to ensure shipping this file! But then the linker think
+ that this file is going to be used as a CommonJS module where we forbid usage
+ of `Components`.
+*/
+let Ci = Components['interfaces'];
+
+/**
+ * Access key that allows privileged code to unwrap proxy wrappers through
+ * valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ * This key should only be used by proxy unit test.
+ */
+ const UNWRAP_ACCESS_KEY = {};
+
+
+ /**
+ * Returns a closure that wraps arguments before calling the given function,
+ * which can be given to native functions that accept a function, such that when
+ * the closure is called, the given function is called with wrapped arguments.
+ *
+ * @param fun {Function}
+ * the function for which to create a closure wrapping its arguments
+ * @param obj {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `obj`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * function contentScriptListener(event) {}
+ * let wrapper = ContentScriptFunctionWrapper(contentScriptListener);
+ * xray.addEventListener("...", wrapper, false);
+ * -> Allow to `event` to be wrapped
+ */
+function ContentScriptFunctionWrapper(fun, obj, name) {
+ if ("___proxy" in fun && typeof fun.___proxy == "function")
+ return fun.___proxy;
+
+ let wrappedFun = function () {
+ let args = [];
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push(wrap(arguments[i]));
+
+ //console.log("Called from native :"+obj+"."+name);
+ //console.log(">args "+arguments.length);
+ //console.log(fun);
+
+ // Native code can execute this callback with `this` being the wrapped
+ // function. For example, window.mozRequestAnimationFrame.
+ if (this == wrappedFun)
+ return fun.apply(fun, args);
+
+ return fun.apply(wrap(this), args);
+ };
+
+ Object.defineProperty(fun, "___proxy", {value : wrappedFun,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return wrappedFun;
+}
+
+/**
+ * Returns a closure that unwraps arguments before calling the `fun` function,
+ * which can be used to build a wrapper for a native function that accepts
+ * wrapped arguments, since native function only accept unwrapped arguments.
+ *
+ * @param fun {Function}
+ * the function to wrap
+ * @param originalObject {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `originalObject`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * wrapper.appendChild = NativeFunctionWrapper(xray.appendChild, xray);
+ * wrapper.appendChild(anotherWrapper);
+ * -> Allow to call xray.appendChild with unwrapped version of anotherWrapper
+ */
+function NativeFunctionWrapper(fun, originalObject, name) {
+ return function () {
+ let args = [];
+ let obj = this && typeof this.valueOf == "function" ?
+ this.valueOf(UNWRAP_ACCESS_KEY) : this;
+
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push( unwrap(arguments[i], obj, name) );
+
+ //if (name != "toString")
+ //console.log(">>calling native ["+(name?name:'#closure#')+"]: \n"+fun.apply+"\n"+obj+"\n("+args.join(', ')+")\nthis :"+obj+"from:"+originalObject+"\n");
+
+ // Need to use Function.prototype.apply.apply because XMLHttpRequest
+ // is a function (typeof return 'function') and fun.apply is null :/
+ let unwrapResult = Function.prototype.apply.apply(fun, [obj, args]);
+ let result = wrap(unwrapResult, obj, name);
+
+ //console.log("<< "+rr+" -> "+r);
+
+ return result;
+ };
+}
+
+/*
+ * Unwrap a JS value that comes from the content script.
+ * Mainly converts proxy wrapper to XPCNativeWrapper.
+ */
+function unwrap(value, obj, name) {
+ //console.log("unwrap : "+value+" ("+name+")");
+ if (!value)
+ return value;
+ let type = typeof value;
+
+ // In case of proxy, unwrap them recursively
+ // (it should not be recursive, just in case of)
+ if (["object", "function"].indexOf(type) !== -1 &&
+ "__isWrappedProxy" in value) {
+ while("__isWrappedProxy" in value)
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ return value;
+ }
+
+ // In case of functions we need to return a wrapper that converts native
+ // arguments applied to this function into proxies.
+ if (type == "function")
+ return ContentScriptFunctionWrapper(value, obj, name);
+
+ // We must wrap objects coming from content script too, as they may have
+ // a function that will be called by a native method.
+ // For example:
+ // addEventListener(..., { handleEvent: function(event) {} }, ...);
+ if (type == "object")
+ return ContentScriptObjectWrapper(value);
+
+ if (["string", "number", "boolean"].indexOf(type) !== -1)
+ return value;
+ //console.log("return non-wrapped to native : "+typeof value+" -- "+value);
+ return value;
+}
+
+/**
+ * Returns an XrayWrapper proxy object that allow to wrap any of its function
+ * though `ContentScriptFunctionWrapper`. These proxies are given to
+ * XrayWrappers in order to automatically wrap values when they call a method
+ * of these proxies. So that they are only used internaly and content script,
+ * nor web page have ever access to them. As a conclusion, we can consider
+ * this code as being safe regarding web pages overload.
+ *
+ *
+ * @param obj {Object}
+ * object coming from content script context to wrap
+ *
+ * Example:
+ * let myListener = { handleEvent: function (event) {} };
+ * node.addEventListener("click", myListener, false);
+ * `event` has to be wrapped, so handleEvent has to be wrapped using
+ * `ContentScriptFunctionWrapper` function.
+ * In order to do so, we build this new kind of proxies.
+ */
+function ContentScriptObjectWrapper(obj) {
+ if ("___proxy" in obj && typeof obj.___proxy == "object")
+ return obj.___proxy;
+
+ function valueOf(key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ }
+
+ let proxy = Proxy.create({
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ return delete obj[name];
+ },
+ // derived traps
+ has: function(name) {
+ return name === "__isXrayWrapperProxy" ||
+ name in obj;
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "valueOf")
+ return valueOf;
+ let value = obj[name];
+ if (!value)
+ return value;
+
+ return unwrap(value);
+ },
+ set: function(receiver, name, val) {
+ obj[name] = val;
+ return true;
+ },
+ enumerate: function() {
+ var result = [];
+ for each (let name in obj) {
+ result.push(name);
+ };
+ return result;
+ },
+ keys: function() {
+ return Object.keys(obj);
+ }
+ });
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+// List of all existing typed arrays.
+// Can be found here:
+// http://mxr.mozilla.org/mozilla-central/source/js/src/jsapi.cpp#1790
+const typedArraysCtor = [
+ ArrayBuffer,
+ Int8Array,
+ Uint8Array,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ Uint8ClampedArray
+];
+
+/*
+ * Wrap a JS value coming from the document by building a proxy wrapper.
+ */
+function wrap(value, obj, name, debug) {
+ if (!value)
+ return value;
+ let type = typeof value;
+ if (type == "object") {
+ // Bug 671016: Typed arrays don't need to be proxified.
+ // We avoid checking the whole constructor list on all objects
+ // by doing this check only on non-extensible objects:
+ if (!Object.isExtensible(value) &&
+ typedArraysCtor.indexOf(value.constructor) !== -1)
+ return value;
+
+ // Bug 715755: do not proxify COW wrappers
+ // These wrappers throw an exception when trying to access
+ // any attribute that is not in a white list
+ try {
+ ("nonExistantAttribute" in value);
+ }
+ catch(e) {
+ if (e.message.indexOf("Permission denied to access property") !== -1)
+ return value;
+ }
+
+ // We may have a XrayWrapper proxy.
+ // For example:
+ // let myListener = { handleEvent: function () {} };
+ // node.addEventListener("click", myListener, false);
+ // When native code want to call handleEvent,
+ // we go though ContentScriptFunctionWrapper that calls `wrap(this)`
+ // `this` is the XrayWrapper proxy of myListener.
+ // We return this object without building a CS proxy as it is already
+ // a value coming from the CS.
+ if ("__isXrayWrapperProxy" in value)
+ return value.valueOf(UNWRAP_ACCESS_KEY);
+
+ // Unwrap object before wrapping it.
+ // It should not happen with CS proxy objects.
+ while("__isWrappedProxy" in value) {
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ }
+
+ if (XPCNativeWrapper.unwrap(value) !== value)
+ return getProxyForObject(value);
+ // In case of Event, HTMLCollection or NodeList or ???
+ // XPCNativeWrapper.unwrap(value) === value
+ // but it's still a XrayWrapper so let's build a proxy
+ return getProxyForObject(value);
+ }
+ if (type == "function") {
+ if (XPCNativeWrapper.unwrap(value) !== value
+ || (typeof value.toString === "function" &&
+ value.toString().match(/\[native code\]/))) {
+ return getProxyForFunction(value, NativeFunctionWrapper(value, obj, name));
+ }
+ return value;
+ }
+ if (type == "string")
+ return value;
+ if (type == "number")
+ return value;
+ if (type == "boolean")
+ return value;
+ //console.log("return non-wrapped to wrapped : "+value);
+ return value;
+}
+
+/*
+ * Wrap an object from the document to a proxy wrapper
+ */
+function getProxyForObject(obj) {
+ if (typeof obj != "object") {
+ let msg = "tried to proxify something other than an object: " + typeof obj;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in obj) {
+ return obj;
+ }
+ // Check if there is a proxy cached on this wrapper,
+ // but take care of prototype ___proxy attribute inheritance!
+ if (obj && obj.___proxy && obj.___proxy.valueOf(UNWRAP_ACCESS_KEY) === obj) {
+ return obj.___proxy;
+ }
+
+ let proxy = Proxy.create(handlerMaker(obj));
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+ return proxy;
+}
+
+/*
+ * Wrap a function from the document to a proxy wrapper
+ */
+function getProxyForFunction(fun, callTrap) {
+ if (typeof fun != "function") {
+ let msg = "tried to proxify something other than a function: " + typeof fun;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in fun)
+ return obj;
+ if ("___proxy" in fun)
+ return fun.___proxy;
+
+ let proxy = Proxy.createFunction(handlerMaker(fun), callTrap);
+
+ Object.defineProperty(fun, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+/*
+ * Check if a DOM attribute name is an event name.
+ */
+function isEventName(id) {
+ if (id.indexOf("on") != 0 || id.length == 2)
+ return false;
+ // Taken from:
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#7616
+ switch (id[2]) {
+ case 'a' :
+ return (id == "onabort" ||
+ id == "onafterscriptexecute" ||
+ id == "onafterprint");
+ case 'b' :
+ return (id == "onbeforeunload" ||
+ id == "onbeforescriptexecute" ||
+ id == "onblur" ||
+ id == "onbeforeprint");
+ case 'c' :
+ return (id == "onchange" ||
+ id == "onclick" ||
+ id == "oncontextmenu" ||
+ id == "oncopy" ||
+ id == "oncut" ||
+ id == "oncanplay" ||
+ id == "oncanplaythrough");
+ case 'd' :
+ return (id == "ondblclick" ||
+ id == "ondrag" ||
+ id == "ondragend" ||
+ id == "ondragenter" ||
+ id == "ondragleave" ||
+ id == "ondragover" ||
+ id == "ondragstart" ||
+ id == "ondrop" ||
+ id == "ondurationchange");
+ case 'e' :
+ return (id == "onerror" ||
+ id == "onemptied" ||
+ id == "onended");
+ case 'f' :
+ return id == "onfocus";
+ case 'h' :
+ return id == "onhashchange";
+ case 'i' :
+ return (id == "oninput" ||
+ id == "oninvalid");
+ case 'k' :
+ return (id == "onkeydown" ||
+ id == "onkeypress" ||
+ id == "onkeyup");
+ case 'l' :
+ return (id == "onload" ||
+ id == "onloadeddata" ||
+ id == "onloadedmetadata" ||
+ id == "onloadstart");
+ case 'm' :
+ return (id == "onmousemove" ||
+ id == "onmouseout" ||
+ id == "onmouseover" ||
+ id == "onmouseup" ||
+ id == "onmousedown" ||
+ id == "onmessage");
+ case 'p' :
+ return (id == "onpaint" ||
+ id == "onpageshow" ||
+ id == "onpagehide" ||
+ id == "onpaste" ||
+ id == "onpopstate" ||
+ id == "onpause" ||
+ id == "onplay" ||
+ id == "onplaying" ||
+ id == "onprogress");
+ case 'r' :
+ return (id == "onreadystatechange" ||
+ id == "onreset" ||
+ id == "onresize" ||
+ id == "onratechange");
+ case 's' :
+ return (id == "onscroll" ||
+ id == "onselect" ||
+ id == "onsubmit" ||
+ id == "onseeked" ||
+ id == "onseeking" ||
+ id == "onstalled" ||
+ id == "onsuspend");
+ case 't':
+ return id == "ontimeupdate"
+ /*
+ // TODO: Make it work for mobile version
+ ||
+ (nsDOMTouchEvent::PrefEnabled() &&
+ (id == "ontouchstart" ||
+ id == "ontouchend" ||
+ id == "ontouchmove" ||
+ id == "ontouchenter" ||
+ id == "ontouchleave" ||
+ id == "ontouchcancel"))*/;
+
+ case 'u' :
+ return id == "onunload";
+ case 'v':
+ return id == "onvolumechange";
+ case 'w':
+ return id == "onwaiting";
+ }
+
+ return false;
+}
+
+// XrayWrappers miss some attributes.
+// Here is a list of functions that return a value when it detects a miss:
+const NODES_INDEXED_BY_NAME = ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"];
+const xRayWrappersMissFixes = [
+
+ // Fix bug with XPCNativeWrapper on HTMLCollection
+ // We can only access array item once, then it's undefined :o
+ function (obj, name) {
+ let i = parseInt(name);
+ if (obj.toString().match(/HTMLCollection|NodeList/) &&
+ i >= 0 && i < obj.length) {
+ return wrap(XPCNativeWrapper(obj.wrappedJSObject[name]), obj, name);
+ }
+ return null;
+ },
+
+ // Trap access to document["form name"]
+ // that may refer to an existing form node
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
+ function (obj, name) {
+ if ("nodeType" in obj && obj.nodeType == 9) {
+ let node = obj.wrappedJSObject[name];
+ // List of supported tag:
+ // http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
+ if (node && NODES_INDEXED_BY_NAME.indexOf(node.tagName) != -1)
+ return wrap(XPCNativeWrapper(node));
+ }
+ return null;
+ },
+
+ // Trap access to window["frame name"] and window.frames[i]
+ // that refer to an (i)frame internal window object
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
+ function (obj, name) {
+ if (typeof obj == "object" && "document" in obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+
+ // Integer case:
+ let i = parseInt(name);
+ if (i >= 0 && i in obj) {
+ return wrap(XPCNativeWrapper(obj[i]));
+ }
+
+ // String name case:
+ if (name in obj.wrappedJSObject) {
+ let win = obj.wrappedJSObject[name];
+ let nodes = obj.document.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if ("contentWindow" in node && node.contentWindow == win)
+ return wrap(node.contentWindow);
+ }
+ }
+ }
+ return null;
+ },
+
+ // Trap access to form["node name"]
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
+ function (obj, name) {
+ if (typeof obj == "object" && "tagName" in obj && obj.tagName == "FORM") {
+ let match = obj.wrappedJSObject[name];
+ let nodes = obj.ownerDocument.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if (node == match)
+ return wrap(node);
+ }
+ }
+ return null;
+ }
+
+];
+
+// XrayWrappers have some buggy methods.
+// Here is the list of them with functions returning some replacement
+// for a given object `obj`:
+const xRayWrappersMethodsFixes = {
+ // postMessage method is checking the Javascript global
+ // and it expects it to be a window object.
+ // But in our case, the global object is our sandbox global object.
+ // See nsGlobalWindow::CallerInnerWindow():
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsGlobalWindow.cpp#5893
+ // nsCOMPtr<nsPIDOMWindow> win = do_QueryWrappedNative(wrapper);
+ // win is null
+ postMessage: function (obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+
+ // Create a wrapper that is going to call `postMessage` through `eval`
+ let f = function postMessage(message, targetOrigin) {
+ let jscode = "this.postMessage(";
+ if (typeof message != "string")
+ jscode += JSON.stringify(message);
+ else
+ jscode += "'" + message.toString().replace(/['\\]/g,"\\$&") + "'";
+
+ targetOrigin = targetOrigin.toString().replace(/['\\]/g,"\\$&");
+
+ jscode += ", '" + targetOrigin + "')";
+ return this.wrappedJSObject.eval(jscode);
+ };
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Fix mozMatchesSelector uses that is broken on XrayWrappers
+ // when we use document.documentElement.mozMatchesSelector.call(node, expr)
+ // It's only working if we call mozMatchesSelector on the node itself.
+ // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
+ mozMatchesSelector: function (obj) {
+ // Ensure that we are on an object to expose this buggy method
+ try {
+ // Bug 707576 removed nsIDOMNSElement.
+ // Can be simplified as soon as Firefox 11 become the minversion
+ obj.QueryInterface("nsIDOMElement" in Ci ? Ci.nsIDOMElement :
+ Ci.nsIDOMNSElement);
+ }
+ catch(e) {
+ return null;
+ }
+ // We can't use `wrap` function as `f` is not a native function,
+ // so wrap it manually:
+ let f = function mozMatchesSelector(selectors) {
+ return this.mozMatchesSelector(selectors);
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Bug 679054: History API doesn't work with Proxy objects. We have to pass
+ // regular JS objects on `pushState` and `replaceState` methods.
+ // In addition, the first argument has to come from the same compartment.
+ pushState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.pushState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+ replaceState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.replaceState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Bug 769006: nsIDOMMutationObserver.observe fails with proxy as options
+ // attributes
+ observe: function observe(obj) {
+ // Ensure that we are on a DOMMutation object
+ try {
+ // nsIDOMMutationObserver starts with FF14
+ if ("nsIDOMMutationObserver" in Ci)
+ obj.QueryInterface(Ci.nsIDOMMutationObserver);
+ else
+ return null;
+ }
+ catch(e) {
+ return null;
+ }
+ return function nsIDOMMutationObserverObserveFix(target, options) {
+ // Gets native/unwrapped this
+ let self = this && typeof this.valueOf == "function" ?
+ this.valueOf(UNWRAP_ACCESS_KEY) : this;
+ // Unwrap the xraywrapper target out of JS proxy
+ let targetXray = unwrap(target);
+ // But do not wrap `options` through ContentScriptObjectWrapper
+ let result = wrap(self.observe(targetXray, options));
+ // Finally wrap result into JS proxies
+ return wrap(result);
+ };
+ }
+};
+
+/*
+ * Generate handler for proxy wrapper
+ */
+function handlerMaker(obj) {
+ // Overloaded attributes dictionary
+ let overload = {};
+ // Expando attributes dictionary (i.e. onclick, onfocus, on* ...)
+ let expando = {};
+ // Cache of methods overloaded to fix XrayWrapper bug
+ let methodFixes = {};
+ return {
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ delete expando[name];
+ delete overload[name];
+ return delete obj[name];
+ },
+
+ // derived traps
+ has: function(name) {
+ if (name == "___proxy") return false;
+ if (isEventName(name)) {
+ // XrayWrappers throw exception when we try to access expando attributes
+ // even on "name in wrapper". So avoid doing it!
+ return name in expando;
+ }
+ return name in obj || name in overload || name == "__isWrappedProxy" ||
+ undefined !== this.get(null, name);
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "___proxy")
+ return undefined;
+
+ // Overload toString in order to avoid returning "[XrayWrapper [object HTMLElement]]"
+ // or "[object Function]" for function's Proxy
+ if (name == "toString") {
+ // Bug 714778: we should not pass obj.wrappedJSObject.toString
+ // in order to avoid sharing its proxy between two contents scripts.
+ // (not that `unwrappedObj` can be equal to `obj` when `obj` isn't
+ // an xraywrapper)
+ let unwrappedObj = XPCNativeWrapper.unwrap(obj);
+ return wrap(function () {
+ return unwrappedObj.toString.call(
+ this.valueOf(UNWRAP_ACCESS_KEY), arguments);
+ }, obj, name);
+ }
+
+ // Offer a way to retrieve XrayWrapper from a proxified node through `valueOf`
+ if (name == "valueOf")
+ return function (key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ };
+
+ // Return overloaded value if there is one.
+ // It allows to overload native methods like addEventListener that
+ // are not saved, even on the wrapper itself.
+ // (And avoid some methods like toSource from being returned here! [__proto__ test])
+ if (name in overload &&
+ overload[name] != Object.getPrototypeOf(overload)[name] &&
+ name != "__proto__") {
+ return overload[name];
+ }
+
+ // Catch exceptions thrown by XrayWrappers when we try to access on*
+ // attributes like onclick, onfocus, ...
+ if (isEventName(name)) {
+ //console.log("expando:"+obj+" - "+obj.nodeType);
+ return name in expando ? expando[name].original : undefined;
+ }
+
+ // Overload some XrayWrappers method in order to fix its bugs
+ if (name in methodFixes &&
+ methodFixes[name] != Object.getPrototypeOf(methodFixes)[name] &&
+ name != "__proto__")
+ return methodFixes[name];
+ if (Object.keys(xRayWrappersMethodsFixes).indexOf(name) !== -1) {
+ let fix = xRayWrappersMethodsFixes[name](obj);
+ if (fix)
+ return methodFixes[name] = fix;
+ }
+
+ let o = obj[name];
+
+ // XrayWrapper miss some attributes, try to catch these and return a value
+ if (!o) {
+ for each(let atttributeFixer in xRayWrappersMissFixes) {
+ let fix = atttributeFixer(obj, name);
+ if (fix)
+ return fix;
+ }
+ }
+
+ // Generic case
+ return wrap(o, obj, name);
+
+ },
+
+ set: function(receiver, name, val) {
+
+ if (isEventName(name)) {
+ //console.log("SET on* attribute : " + name + " / " + val + "/" + obj);
+ let shortName = name.replace(/^on/,"");
+
+ // Unregister previously set listener
+ if (expando[name]) {
+ obj.removeEventListener(shortName, expando[name], true);
+ delete expando[name];
+ }
+
+ // Only accept functions
+ if (typeof val != "function")
+ return false;
+
+ // Register a new listener
+ let original = val;
+ val = ContentScriptFunctionWrapper(val);
+ expando[name] = val;
+ val.original = original;
+ obj.addEventListener(name.replace(/^on/, ""), val, true);
+ return true;
+ }
+
+ obj[name] = val;
+
+ // Handle native method not overloaded on XrayWrappers:
+ // obj.addEventListener = val; -> obj.addEventlistener = native method
+ // And, XPCNativeWrapper bug where nested values appear to be wrapped:
+ // obj.customNestedAttribute = val -> obj.customNestedAttribute !== val
+ // obj.customNestedAttribute = "waive wrapper something"
+ // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
+ // TODO: check that DOM can't be updated by the document itself and so overloaded value becomes wrong
+ // but I think such behavior is limited to primitive type
+ if ((typeof val == "function" || typeof val == "object") && name) {
+ overload[name] = val;
+ }
+
+ return true;
+ },
+
+ enumerate: function() {
+ var result = [];
+ for each (let name in Object.keys(obj)) {
+ result.push(name);
+ };
+ return result;
+ },
+
+ keys: function() {
+ return Object.keys(obj);
+ }
+ };
+};
+
+
+/*
+ * Wrap an object from the document to a proxy wrapper.
+ */
+function create(object) {
+ if ("wrappedJSObject" in object)
+ object = object.wrappedJSObject;
+ let xpcWrapper = XPCNativeWrapper(object);
+ // If we can't build an XPCNativeWrapper, it doesn't make sense to build
+ // a proxy. All proxy code is based on having such wrapper that store
+ // different JS attributes set.
+ // (we can't build XPCNativeWrapper when object is from the same
+ // principal/domain)
+ if (object === xpcWrapper) {
+ return object;
+ }
+ return getProxyForObject(xpcWrapper);
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js b/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js
new file mode 100644
index 0000000..8cf9c5c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js
@@ -0,0 +1,308 @@
+/* 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/. */
+
+const ContentWorker = Object.freeze({
+ // TODO: Bug 727854 Use same implementation than common JS modules,
+ // i.e. EventEmitter module
+
+ /**
+ * Create an EventEmitter instance.
+ */
+ createEventEmitter: function createEventEmitter(emit) {
+ let listeners = Object.create(null);
+ let eventEmitter = Object.freeze({
+ emit: emit,
+ on: function on(name, callback) {
+ if (typeof callback !== "function")
+ return this;
+ if (!(name in listeners))
+ listeners[name] = [];
+ listeners[name].push(callback);
+ return this;
+ },
+ once: function once(name, callback) {
+ eventEmitter.on(name, function onceCallback() {
+ eventEmitter.removeListener(name, onceCallback);
+ callback.apply(callback, arguments);
+ });
+ },
+ removeListener: function removeListener(name, callback) {
+ if (!(name in listeners))
+ return;
+ let index = listeners[name].indexOf(callback);
+ if (index == -1)
+ return;
+ listeners[name].splice(index, 1);
+ }
+ });
+ function onEvent(name) {
+ if (!(name in listeners))
+ return [];
+ let args = Array.slice(arguments, 1);
+ let results = [];
+ for each (let callback in listeners[name]) {
+ results.push(callback.apply(null, args));
+ }
+ return results;
+ }
+ function hasListenerFor(name) {
+ if (!(name in listeners))
+ return false;
+ return listeners[name].length > 0;
+ }
+ return {
+ eventEmitter: eventEmitter,
+ emit: onEvent,
+ hasListenerFor: hasListenerFor
+ };
+ },
+
+ /**
+ * Create an EventEmitter instance to communicate with chrome module
+ * by passing only strings between compartments.
+ * This function expects `emitToChrome` function, that allows to send
+ * events to the chrome module. It returns the EventEmitter as `pipe`
+ * attribute, and, `onChromeEvent` a function that allows chrome module
+ * to send event into the EventEmitter.
+ *
+ * pipe.emit --> emitToChrome
+ * onChromeEvent --> callback registered through pipe.on
+ */
+ createPipe: function createPipe(emitToChrome) {
+ function onEvent() {
+ // Convert to real array
+ let args = Array.slice(arguments);
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ let str = JSON.stringify(args, replacer);
+ emitToChrome(str);
+ }
+
+ let { eventEmitter, emit, hasListenerFor } =
+ ContentWorker.createEventEmitter(onEvent);
+
+ return {
+ pipe: eventEmitter,
+ onChromeEvent: function onChromeEvent(array) {
+ // We either receive a stringified array, or a real array.
+ // We still allow to pass an array of objects, in WorkerSandbox.emitSync
+ // in order to allow sending DOM node reference between content script
+ // and modules (only used for context-menu API)
+ let args = typeof array == "string" ? JSON.parse(array) : array;
+ return emit.apply(null, args);
+ },
+ hasListenerFor: hasListenerFor
+ };
+ },
+
+ injectConsole: function injectConsole(exports, pipe) {
+ exports.console = Object.freeze({
+ log: pipe.emit.bind(null, "console", "log"),
+ info: pipe.emit.bind(null, "console", "info"),
+ warn: pipe.emit.bind(null, "console", "warn"),
+ error: pipe.emit.bind(null, "console", "error"),
+ debug: pipe.emit.bind(null, "console", "debug"),
+ exception: pipe.emit.bind(null, "console", "exception"),
+ trace: pipe.emit.bind(null, "console", "trace")
+ });
+ },
+
+ injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
+ // wrapped functions from `'timer'` module.
+ // Wrapper adds `try catch` blocks to the callbacks in order to
+ // emit `error` event on a symbiont if exception is thrown in
+ // the Worker global scope.
+ // @see http://www.w3.org/TR/workers/#workerutils
+
+ // List of all living timeouts/intervals
+ let _timers = Object.create(null);
+
+ // Keep a reference to original timeout functions
+ let {
+ setTimeout: chromeSetTimeout,
+ setInterval: chromeSetInterval,
+ clearTimeout: chromeClearTimeout,
+ clearInterval: chromeClearInterval
+ } = chromeAPI.timers;
+
+ function registerTimer(timer) {
+ let registerMethod = null;
+ if (timer.kind == "timeout")
+ registerMethod = chromeSetTimeout;
+ else if (timer.kind == "interval")
+ registerMethod = chromeSetInterval;
+ else
+ throw new Error("Unknown timer kind: " + timer.kind);
+ let id = registerMethod(onFire, timer.delay);
+ function onFire() {
+ try {
+ if (timer.kind == "timeout")
+ delete _timers[id];
+ timer.fun.apply(null, timer.args);
+ } catch(e) {
+ console.exception(e);
+ }
+ }
+ _timers[id] = timer;
+ return id;
+ }
+
+ function unregisterTimer(id) {
+ if (!(id in _timers))
+ return;
+ let { kind } = _timers[id];
+ delete _timers[id];
+ if (kind == "timeout")
+ chromeClearTimeout(id);
+ else if (kind == "interval")
+ chromeClearInterval(id);
+ else
+ throw new Error("Unknown timer kind: " + kind);
+ }
+
+ function disableAllTimers() {
+ Object.keys(_timers).forEach(unregisterTimer);
+ }
+
+ exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
+ return registerTimer({
+ kind: "timeout",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearTimeout = function ContentScriptClearTimeout(id) {
+ unregisterTimer(id);
+ };
+
+ exports.setInterval = function ContentScriptSetInterval(callback, delay) {
+ return registerTimer({
+ kind: "interval",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearInterval = function ContentScriptClearInterval(id) {
+ unregisterTimer(id);
+ };
+
+ // On page-hide, save a list of all existing timers before disabling them,
+ // in order to be able to restore them on page-show.
+ // These events are fired when the page goes in/out of bfcache.
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ let frozenTimers = [];
+ pipe.on("pageshow", function onPageShow() {
+ frozenTimers.forEach(registerTimer);
+ });
+ pipe.on("pagehide", function onPageHide() {
+ frozenTimers = [];
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ // Some other pagehide listeners may register some timers that won't be
+ // frozen as this particular pagehide listener is called first.
+ // So freeze these timers on next cycle.
+ chromeSetTimeout(function () {
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ }, 0);
+ });
+
+ // Unregister all timers when the page is destroyed
+ // (i.e. when it is removed from bfcache)
+ pipe.on("detach", function clearTimeouts() {
+ disableAllTimers();
+ _timers = {};
+ frozenTimers = [];
+ });
+ },
+
+ injectMessageAPI: function injectMessageAPI(exports, pipe) {
+
+ let { eventEmitter: port, emit : portEmit } =
+ ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
+ pipe.on("event", portEmit);
+
+ let self = {
+ port: port,
+ postMessage: pipe.emit.bind(null, "message"),
+ on: pipe.on.bind(null),
+ once: pipe.once.bind(null),
+ removeListener: pipe.removeListener.bind(null),
+ };
+ Object.defineProperty(exports, "self", {
+ value: self
+ });
+
+ // Deprecated use of on/postMessage from globals
+ exports.postMessage = function deprecatedPostMessage() {
+ console.error("DEPRECATED: The global `postMessage()` function in " +
+ "content scripts is deprecated in favor of the " +
+ "`self.postMessage()` function, which works the same. " +
+ "Replace calls to `postMessage()` with calls to " +
+ "`self.postMessage()`." +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ return self.postMessage.apply(null, arguments);
+ };
+ exports.on = function deprecatedOn() {
+ console.error("DEPRECATED: The global `on()` function in content " +
+ "scripts is deprecated in favor of the `self.on()` " +
+ "function, which works the same. Replace calls to `on()` " +
+ "with calls to `self.on()`" +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ return self.on.apply(null, arguments);
+ };
+
+ // Deprecated use of `onMessage` from globals
+ let onMessage = null;
+ Object.defineProperty(exports, "onMessage", {
+ get: function () onMessage,
+ set: function (v) {
+ if (onMessage)
+ self.removeListener("message", onMessage);
+ console.error("DEPRECATED: The global `onMessage` function in content" +
+ "scripts is deprecated in favor of the `self.on()` " +
+ "function. Replace `onMessage = function (data){}` " +
+ "definitions with calls to `self.on('message', " +
+ "function (data){})`. " +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ onMessage = v;
+ if (typeof onMessage == "function")
+ self.on("message", onMessage);
+ }
+ });
+ },
+
+ injectOptions: function (exports, options) {
+ Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
+ },
+
+ inject: function (exports, chromeAPI, emitToChrome, options) {
+ let { pipe, onChromeEvent, hasListenerFor } =
+ ContentWorker.createPipe(emitToChrome);
+
+ ContentWorker.injectConsole(exports, pipe);
+ ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
+ ContentWorker.injectMessageAPI(exports, pipe);
+ if ( options !== undefined ) {
+ ContentWorker.injectOptions(exports, options);
+ }
+
+ Object.freeze( exports.self );
+
+ return {
+ emitToContent: onChromeEvent,
+ hasListenerFor: hasListenerFor
+ };
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content.js b/tools/addon-sdk-1.12/lib/sdk/content/content.js
new file mode 100644
index 0000000..4bffd45
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content.js
@@ -0,0 +1,15 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+exports.Loader = require('./loader').Loader;
+exports.Symbiont = require('./symbiont').Symbiont;
+exports.Worker = require('./worker').Worker;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/loader.js b/tools/addon-sdk-1.12/lib/sdk/content/loader.js
new file mode 100644
index 0000000..b01675c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/loader.js
@@ -0,0 +1,204 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { EventEmitter } = require('../deprecated/events');
+const { validateOptions } = require('../deprecated/api-utils');
+const { URL } = require('../url');
+const file = require('../io/file');
+
+const LOCAL_URI_SCHEMES = ['resource', 'data'];
+
+// Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
+function ensureNull(value) {
+ return value == null ? null : value;
+}
+
+// map of property validations
+const valid = {
+ contentURL: {
+ ok: function (value) {
+ try {
+ URL(value);
+ }
+ catch(e) {
+ return false;
+ }
+ return true;
+ },
+ msg: 'The `contentURL` option must be a valid URL.'
+ },
+ contentScriptFile: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ if (value === null)
+ return true;
+
+ value = [].concat(value);
+
+ // Make sure every item is a local file URL.
+ return value.every(function (item) {
+ try {
+ return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
+ }
+ catch(e) {
+ return false;
+ }
+ });
+
+ },
+ msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
+ },
+ contentScript: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ return !Array.isArray(value) || value.every(
+ function(item) { return typeof item === 'string' }
+ );
+ },
+ msg: 'The `contentScript` option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: ['string'],
+ ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) },
+ map: function(value) {
+ return value || 'end';
+ },
+ msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
+ },
+ contentScriptOptions: {
+ ok: function(value) {
+ if ( value === undefined ) { return true; }
+ try { JSON.parse( JSON.stringify( value ) ); } catch(e) { return false; }
+ return true;
+ },
+ map: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ msg: 'The contentScriptOptions should be a jsonable value.'
+ }
+};
+exports.validationAttributes = valid;
+
+/**
+ * Shortcut function to validate property with validation.
+ * @param {Object|Number|String} suspect
+ * value to validate
+ * @param {Object} validation
+ * validation rule passed to `api-utils`
+ */
+function validate(suspect, validation) validateOptions(
+ { $: suspect },
+ { $: validation }
+).$
+
+function Allow(script) ({
+ get script() script,
+ set script(value) script = !!value
+})
+
+/**
+ * Trait is intended to be used in some composition. It provides set of core
+ * properties and bounded validations to them. Trait is useful for all the
+ * compositions providing high level APIs for interaction with content.
+ * Property changes emit `"propertyChange"` events on instances.
+ */
+const Loader = EventEmitter.compose({
+ /**
+ * Permissions for the content, with the following keys:
+ * @property {Object} [allow = { script: true }]
+ * @property {Boolean} [allow.script = true]
+ * Whether or not to execute script in the content. Defaults to true.
+ */
+ get allow() this._allow || (this._allow = Allow(true)),
+ set allow(value) this.allow.script = value && value.script,
+ _allow: null,
+ /**
+ * The content to load. Either a string of HTML or a URL.
+ * @type {String}
+ */
+ get contentURL() this._contentURL,
+ set contentURL(value) {
+ value = validate(value, valid.contentURL);
+ if (this._contentURL != value) {
+ this._emit('propertyChange', {
+ contentURL: this._contentURL = value
+ });
+ }
+ },
+ _contentURL: null,
+ /**
+ * When to load the content scripts.
+ * Possible values are "end" (default), which loads them once all page
+ * contents have been loaded, "ready", which loads them once DOM nodes are
+ * ready (ie like DOMContentLoaded event), and "start", which loads them once
+ * the `window` object for the page has been created, but before any scripts
+ * specified by the page have been loaded.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {'start'|'ready'|'end'}
+ */
+ get contentScriptWhen() this._contentScriptWhen,
+ set contentScriptWhen(value) {
+ value = validate(value, valid.contentScriptWhen);
+ if (value !== this._contentScriptWhen) {
+ this._emit('propertyChange', {
+ contentScriptWhen: this._contentScriptWhen = value
+ });
+ }
+ },
+ _contentScriptWhen: 'end',
+ /**
+ * Options avalaible from the content script as `self.options`.
+ * The value of options can be of any type (object, array, string, etc.)
+ * but only jsonable values will be available as frozen objects from the
+ * content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {Object}
+ */
+ get contentScriptOptions() this._contentScriptOptions,
+ set contentScriptOptions(value) this._contentScriptOptions = value,
+ _contentScriptOptions: null,
+ /**
+ * The URLs of content scripts.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String[]}
+ */
+ get contentScriptFile() this._contentScriptFile,
+ set contentScriptFile(value) {
+ value = validate(value, valid.contentScriptFile);
+ if (value != this._contentScriptFile) {
+ this._emit('propertyChange', {
+ contentScriptFile: this._contentScriptFile = value
+ });
+ }
+ },
+ _contentScriptFile: null,
+ /**
+ * The texts of content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String|undefined}
+ */
+ get contentScript() this._contentScript,
+ set contentScript(value) {
+ value = validate(value, valid.contentScript);
+ if (value != this._contentScript) {
+ this._emit('propertyChange', {
+ contentScript: this._contentScript = value
+ });
+ }
+ },
+ _contentScript: null
+});
+exports.Loader = Loader;
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js b/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js
new file mode 100644
index 0000000..186e9d3
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Worker } = require('./worker');
+const { Loader } = require('./loader');
+const hiddenFrames = require('../frame/hidden-frame');
+const observers = require('../deprecated/observer-service');
+const unload = require('../system/unload');
+
+const assetsURI = require('../self').data.url();
+
+/**
+ * This trait is layered on top of `Worker` and in contrast to symbiont
+ * Worker constructor requires `content` option that represents content
+ * that will be loaded in the provided frame, if frame is not provided
+ * Worker will create hidden one.
+ */
+const Symbiont = Worker.resolve({
+ constructor: '_initWorker',
+ destroy: '_workerDestroy'
+ }).compose(Loader, {
+
+ /**
+ * The constructor requires all the options that are required by
+ * `require('content').Worker` with the difference that the `frame` option
+ * is optional. If `frame` is not provided, `contentURL` is expected.
+ * @param {Object} options
+ * @param {String} options.contentURL
+ * URL of a content to load into `this._frame` and create worker for.
+ * @param {Element} [options.frame]
+ * iframe element that is used to load `options.contentURL` into.
+ * if frame is not provided hidden iframe will be created.
+ */
+ constructor: function Symbiont(options) {
+ options = options || {};
+
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('frame' in options) {
+ this._initFrame(options.frame);
+ }
+ else {
+ let self = this;
+ this._hiddenFrame = hiddenFrames.HiddenFrame({
+ onReady: function onFrame() {
+ self._initFrame(this.element);
+ },
+ onUnload: function onUnload() {
+ // Bug 751211: Remove reference to _frame when hidden frame is
+ // automatically removed on unload, otherwise we are going to face
+ // "dead object" exception
+ self.destroy();
+ }
+ });
+ hiddenFrames.add(this._hiddenFrame);
+ }
+
+ unload.ensure(this._public, "destroy");
+ },
+
+ destroy: function destroy() {
+ this._workerDestroy();
+ this._unregisterListener();
+ this._frame = null;
+ if (this._hiddenFrame) {
+ hiddenFrames.remove(this._hiddenFrame);
+ this._hiddenFrame = null;
+ }
+ },
+
+ /**
+ * XUL iframe or browser elements with attribute `type` being `content`.
+ * Used to create `ContentSymbiont` from.
+ * @type {nsIFrame|nsIBrowser}
+ */
+ _frame: null,
+
+ /**
+ * Listener to the `'frameReady"` event (emitted when `iframe` is ready).
+ * Removes listener, sets right permissions to the frame and loads content.
+ */
+ _initFrame: function _initFrame(frame) {
+ if (this._loadListener)
+ this._unregisterListener();
+
+ this._frame = frame;
+ frame.docShell.allowJavascript = this.allow.script;
+ frame.setAttribute("src", this._contentURL);
+
+ // Inject `addon` object in document if we load a document from
+ // one of our addon folder and if no content script are defined. bug 612726
+ let isDataResource =
+ typeof this._contentURL == "string" &&
+ this._contentURL.indexOf(assetsURI) == 0;
+ let hasContentScript =
+ (Array.isArray(this.contentScript) ? this.contentScript.length > 0
+ : !!this.contentScript) ||
+ (Array.isArray(this.contentScriptFile) ? this.contentScriptFile.length > 0
+ : !!this.contentScriptFile);
+ // If we have to inject `addon` we have to do it before document
+ // script execution, so during `start`:
+ this._injectInDocument = isDataResource && !hasContentScript;
+ if (this._injectInDocument)
+ this.contentScriptWhen = "start";
+
+ if ((frame.contentDocument.readyState == "complete" ||
+ (frame.contentDocument.readyState == "interactive" &&
+ this.contentScriptWhen != 'end' )) &&
+ frame.contentDocument.location == this._contentURL) {
+ // In some cases src doesn't change and document is already ready
+ // (for ex: when the user moves a widget while customizing toolbars.)
+ this._onInit();
+ return;
+ }
+
+ let self = this;
+
+ if ('start' == this.contentScriptWhen) {
+ this._loadEvent = 'start';
+ observers.add('document-element-inserted',
+ this._loadListener = function onStart(doc) {
+
+ let window = doc.defaultView;
+ if (window && window == frame.contentWindow) {
+ self._unregisterListener();
+ self._onInit();
+ }
+
+ });
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ this._loadEvent = eventName;
+ frame.addEventListener(eventName,
+ this._loadListener = function _onReady(event) {
+
+ if (event.target != frame.contentDocument)
+ return;
+ self._unregisterListener();
+
+ self._onInit();
+
+ }, true);
+
+ },
+
+ /**
+ * Unregister listener that watchs for document being ready to be injected.
+ * This listener is registered in `Symbiont._initFrame`.
+ */
+ _unregisterListener: function _unregisterListener() {
+ if (!this._loadListener)
+ return;
+ if (this._loadEvent == "start") {
+ observers.remove('document-element-inserted', this._loadListener);
+ }
+ else {
+ this._frame.removeEventListener(this._loadEvent, this._loadListener,
+ true);
+ }
+ this._loadListener = null;
+ },
+
+ /**
+ * Called by Symbiont itself when the frame is ready to load
+ * content scripts according to contentScriptWhen. Overloaded by Panel.
+ */
+ _onInit: function () {
+ this._initWorker({ window: this._frame.contentWindow });
+ }
+
+});
+exports.Symbiont = Symbiont;
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js b/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js
new file mode 100644
index 0000000..9e57274
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const AppShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+const NS = 'http://www.w3.org/1999/xhtml';
+const COLOR = 'rgb(255,255,255)';
+
+/**
+ * Creates canvas element with a thumbnail of the passed window.
+ * @param {Window} window
+ * @returns {Element}
+ */
+function getThumbnailCanvasForWindow(window) {
+ let aspectRatio = 0.5625; // 16:9
+ let thumbnail = AppShellService.hiddenDOMWindow.document
+ .createElementNS(NS, 'canvas');
+ thumbnail.mozOpaque = true;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ let ctx = thumbnail.getContext('2d');
+ let snippetWidth = window.innerWidth * .6;
+ let scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
+ snippetWidth * aspectRatio, COLOR);
+ return thumbnail;
+}
+exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
+
+/**
+ * Creates Base64 encoded data URI of the thumbnail for the passed window.
+ * @param {Window} window
+ * @returns {String}
+ */
+exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
+ return getThumbnailCanvasForWindow(window).toDataURL()
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/worker.js b/tools/addon-sdk-1.12/lib/sdk/content/worker.js
new file mode 100644
index 0000000..b2f204c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/worker.js
@@ -0,0 +1,582 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Trait } = require('../deprecated/traits');
+const { EventEmitter, EventEmitterTrait } = require('../deprecated/events');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timers');
+const { URL } = require('../url');
+const unload = require('../system/unload');
+const observers = require('../deprecated/observer-service');
+const { Cortex } = require('../deprecated/cortex');
+const { sandbox, evaluate, load } = require("../loader/sandbox");
+const { merge } = require('../util/object');
+const xulApp = require("../system/xul-app");
+const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
+ "17.0a2", "*");
+const { getTabForWindow } = require('../tabs/helpers');
+
+/* Trick the linker in order to ensure shipping these files in the XPI.
+ require('./content-proxy.js');
+ require('./content-worker.js');
+ Then, retrieve URL of these files in the XPI:
+*/
+let prefix = module.uri.split('worker.js')[0];
+const CONTENT_PROXY_URL = prefix + 'content-proxy.js';
+const CONTENT_WORKER_URL = prefix + 'content-worker.js';
+
+const JS_VERSION = '1.8';
+
+const ERR_DESTROYED =
+ "Couldn't find the worker to receive this message. " +
+ "The script may not be initialized yet, or may already have been unloaded.";
+
+const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
+ "until it is visible again.";
+
+/**
+ * This key is not exported and should only be used for proxy tests.
+ * The following `PRIVATE_KEY` is used in addon module scope in order to tell
+ * Worker API to expose `UNWRAP_ACCESS_KEY` in content script.
+ * This key allows test-content-proxy.js to unwrap proxy with valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ */
+const PRIVATE_KEY = {};
+
+
+const WorkerSandbox = EventEmitter.compose({
+
+ /**
+ * Emit a message to the worker content sandbox
+ */
+ emit: function emit() {
+ // First ensure having a regular array
+ // (otherwise, `arguments` would be mapped to an object by `stringify`)
+ let array = Array.slice(arguments);
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ // Ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ self._emitToContent(JSON.stringify(array, replacer));
+ }, 0);
+ },
+
+ /**
+ * Synchronous version of `emit`.
+ * /!\ Should only be used when it is strictly mandatory /!\
+ * Doesn't ensure passing only JSON values.
+ * Mainly used by context-menu in order to avoid breaking it.
+ */
+ emitSync: function emitSync() {
+ let args = Array.slice(arguments);
+ // Bug 732716: Ensure wrapping xrays sent to the content script
+ // otherwise it will have access to raw xraywrappers and content script
+ // will assume it is an user object coming from the content script sandbox
+ if ("_wrap" in this)
+ args = args.map(this._wrap);
+ return this._emitToContent(args);
+ },
+
+ /**
+ * Tells if content script has at least one listener registered for one event,
+ * through `self.on('xxx', ...)`.
+ * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
+ */
+ hasListenerFor: function hasListenerFor(name) {
+ return this._hasListenerFor(name);
+ },
+
+ /**
+ * Method called by the worker sandbox when it needs to send a message
+ */
+ _onContentEvent: function onContentEvent(args) {
+ // As `emit`, we ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ // We emit event to chrome/addon listeners
+ self._emit.apply(self, JSON.parse(args));
+ }, 0);
+ },
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ constructor: function WorkerSandbox(worker) {
+ this._addonWorker = worker;
+
+ // Ensure that `emit` has always the right `this`
+ this.emit = this.emit.bind(this);
+ this.emitSync = this.emitSync.bind(this);
+
+ // We receive a wrapped window, that may be an xraywrapper if it's content
+ let window = worker._window;
+ let proto = window;
+
+ // Instantiate trusted code in another Sandbox in order to prevent content
+ // script from messing with standard classes used by proxy and API code.
+ let apiSandbox = sandbox(window, { wantXrays: true });
+
+ // Build content proxies only if the document has a non-system principal
+ // And only on old firefox versions that doesn't ship bug 738244
+ if (USE_JS_PROXIES && XPCNativeWrapper.unwrap(window) !== window) {
+ apiSandbox.console = console;
+ // Execute the proxy code
+ load(apiSandbox, CONTENT_PROXY_URL);
+ // Get a reference of the window's proxy
+ proto = apiSandbox.create(window);
+ // Keep a reference to `wrap` function for `emitSync` usage
+ this._wrap = apiSandbox.wrap;
+ }
+
+ // Create the sandbox and bind it to window in order for content scripts to
+ // have access to all standard globals (window, document, ...)
+ let content = this._sandbox = sandbox(window, {
+ sandboxPrototype: proto,
+ wantXrays: true
+ });
+ // We have to ensure that window.top and window.parent are the exact same
+ // object than window object, i.e. the sandbox global object. But not
+ // always, in case of iframes, top and parent are another window object.
+ let top = window.top === window ? content : content.top;
+ let parent = window.parent === window ? content : content.parent;
+ merge(content, {
+ // We need "this === window === top" to be true in toplevel scope:
+ get window() content,
+ get top() top,
+ get parent() parent,
+ // Use the Greasemonkey naming convention to provide access to the
+ // unwrapped window object so the content script can access document
+ // JavaScript values.
+ // NOTE: this functionality is experimental and may change or go away
+ // at any time!
+ get unsafeWindow() window.wrappedJSObject
+ });
+
+ // Load trusted code that will inject content script API.
+ // We need to expose JS objects defined in same principal in order to
+ // avoid having any kind of wrapper.
+ load(apiSandbox, CONTENT_WORKER_URL);
+
+ // prepare a clean `self.options`
+ let options = 'contentScriptOptions' in worker ?
+ JSON.stringify( worker.contentScriptOptions ) :
+ undefined;
+
+ // Then call `inject` method and communicate with this script
+ // by trading two methods that allow to send events to the other side:
+ // - `onEvent` called by content script
+ // - `result.emitToContent` called by addon script
+ // Bug 758203: We have to explicitely define `__exposedProps__` in order
+ // to allow access to these chrome object attributes from this sandbox with
+ // content priviledges
+ // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
+ let chromeAPI = {
+ timers: {
+ setTimeout: timer.setTimeout,
+ setInterval: timer.setInterval,
+ clearTimeout: timer.clearTimeout,
+ clearInterval: timer.clearInterval,
+ __exposedProps__: {
+ setTimeout: 'r',
+ setInterval: 'r',
+ clearTimeout: 'r',
+ clearInterval: 'r'
+ }
+ },
+ __exposedProps__: {
+ timers: 'r'
+ }
+ };
+ let onEvent = this._onContentEvent.bind(this);
+ // `ContentWorker` is defined in CONTENT_WORKER_URL file
+ let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
+ this._emitToContent = result.emitToContent;
+ this._hasListenerFor = result.hasListenerFor;
+
+ // Handle messages send by this script:
+ let self = this;
+ // console.xxx calls
+ this.on("console", function consoleListener(kind) {
+ console[kind].apply(console, Array.slice(arguments, 1));
+ });
+
+ // self.postMessage calls
+ this.on("message", function postMessage(data) {
+ // destroyed?
+ if (self._addonWorker)
+ self._addonWorker._emit('message', data);
+ });
+
+ // self.port.emit calls
+ this.on("event", function portEmit(name, args) {
+ // destroyed?
+ if (self._addonWorker)
+ self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
+ });
+
+ // Internal feature that is only used by SDK tests:
+ // Expose unlock key to content script context.
+ // See `PRIVATE_KEY` definition for more information.
+ if (apiSandbox && worker._expose_key)
+ content.UNWRAP_ACCESS_KEY = apiSandbox.UNWRAP_ACCESS_KEY;
+
+ // Inject `addon` global into target document if document is trusted,
+ // `addon` in document is equivalent to `self` in content script.
+ if (worker._injectInDocument) {
+ let win = window.wrappedJSObject ? window.wrappedJSObject : window;
+ Object.defineProperty(win, "addon", {
+ value: content.self
+ }
+ );
+ }
+
+ // The order of `contentScriptFile` and `contentScript` evaluation is
+ // intentional, so programs can load libraries like jQuery from script URLs
+ // and use them in scripts.
+ let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
+ : null,
+ contentScript = ('contentScript' in worker) ? worker.contentScript : null;
+
+ if (contentScriptFile) {
+ if (Array.isArray(contentScriptFile))
+ this._importScripts.apply(this, contentScriptFile);
+ else
+ this._importScripts(contentScriptFile);
+ }
+ if (contentScript) {
+ this._evaluate(
+ Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
+ );
+ }
+ },
+ destroy: function destroy() {
+ this.emitSync("detach");
+ this._sandbox = null;
+ this._addonWorker = null;
+ this._wrap = null;
+ },
+
+ /**
+ * JavaScript sandbox where all the content scripts are evaluated.
+ * {Sandbox}
+ */
+ _sandbox: null,
+
+ /**
+ * Reference to the addon side of the worker.
+ * @type {Worker}
+ */
+ _addonWorker: null,
+
+ /**
+ * Evaluates code in the sandbox.
+ * @param {String} code
+ * JavaScript source to evaluate.
+ * @param {String} [filename='javascript:' + code]
+ * Name of the file
+ */
+ _evaluate: function(code, filename) {
+ try {
+ evaluate(this._sandbox, code, filename || 'javascript:' + code);
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ },
+ /**
+ * Imports scripts to the sandbox by reading files under urls and
+ * evaluating its source. If exception occurs during evaluation
+ * `"error"` event is emitted on the worker.
+ * This is actually an analog to the `importScript` method in web
+ * workers but in our case it's not exposed even though content
+ * scripts may be able to do it synchronously since IO operation
+ * takes place in the UI process.
+ */
+ _importScripts: function _importScripts(url) {
+ let urls = Array.slice(arguments, 0);
+ for each (let contentScriptFile in urls) {
+ try {
+ let uri = URL(contentScriptFile);
+ if (uri.scheme === 'resource')
+ load(this._sandbox, String(uri));
+ else
+ throw Error("Unsupported `contentScriptFile` url: " + String(uri));
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ }
+ }
+});
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker
+ */
+const Worker = EventEmitter.compose({
+ on: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ /**
+ * Sends a message to the worker's global scope. Method takes single
+ * argument, which represents data to be sent to the worker. The data may
+ * be any primitive type value or `JSON`. Call of this method asynchronously
+ * emits `message` event with data value in the global scope of this
+ * symbiont.
+ *
+ * `message` event listeners can be set either by calling
+ * `self.on` with a first argument string `"message"` or by
+ * implementing `onMessage` function in the global scope of this worker.
+ * @param {Number|String|JSON} data
+ */
+ postMessage: function postMessage(data) {
+ if (!this._contentWorker)
+ throw new Error(ERR_DESTROYED);
+ if (this._frozen)
+ throw new Error(ERR_FROZEN);
+
+ this._contentWorker.emit("message", data);
+ },
+
+ /**
+ * EventEmitter, that behaves (calls listeners) asynchronously.
+ * A way to send customized messages to / from the worker.
+ * Events from in the worker can be observed / emitted via
+ * worker.on / worker.emit.
+ */
+ get port() {
+ // We generate dynamically this attribute as it needs to be accessible
+ // before Worker.constructor gets called. (For ex: Panel)
+
+ // create an event emitter that receive and send events from/to the worker
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () self._emitEventToContent(Array.slice(arguments))
+ });
+
+ // expose wrapped port, that exposes only public properties:
+ // We need to destroy this getter in order to be able to set the
+ // final value. We need to update only public port attribute as we never
+ // try to access port attribute from private API.
+ delete this._public.port;
+ this._public.port = Cortex(this._port);
+ // Replicate public port to the private object
+ delete this.port;
+ this.port = this._public.port;
+
+ return this._port;
+ },
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _emit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+ _emitEventToContent: function _emitEventToContent(args) {
+ // We need to save events that are emitted before the worker is
+ // initialized
+ if (!this._inited) {
+ this._earlyEvents.push(args);
+ return;
+ }
+
+ if (this._frozen)
+ throw new Error(ERR_FROZEN);
+
+ // We throw exception when the worker has been destroyed
+ if (!this._contentWorker) {
+ throw new Error(ERR_DESTROYED);
+ }
+
+ // Forward the event to the WorkerSandbox object
+ this._contentWorker.emit.apply(null, ["event"].concat(args));
+ },
+
+ // Is worker connected to the content worker sandbox ?
+ _inited: false,
+
+ // Is worker being frozen? i.e related document is frozen in bfcache.
+ // Content script should not be reachable if frozen.
+ _frozen: true,
+
+ // List of custom events fired before worker is initialized
+ get _earlyEvents() {
+ delete this._earlyEvents;
+ this._earlyEvents = [];
+ return this._earlyEvents;
+ },
+
+ constructor: function Worker(options) {
+ options = options || {};
+
+ if ('window' in options)
+ this._window = options.window;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('onDetach' in options)
+ this.on('detach', options.onDetach);
+
+ // Internal feature that is only used by SDK unit tests.
+ // See `PRIVATE_KEY` definition for more information.
+ if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY)
+ this._expose_key = true;
+
+ // Track document unload to destroy this worker.
+ // We can't watch for unload event on page's window object as it
+ // prevents bfcache from working:
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ this._windowID = this._window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ currentInnerWindowID;
+ observers.add("inner-window-destroyed",
+ this._documentUnload = this._documentUnload.bind(this));
+
+ // Listen to pagehide event in order to freeze the content script
+ // while the document is frozen in bfcache:
+ this._window.addEventListener("pageshow",
+ this._pageShow = this._pageShow.bind(this),
+ true);
+ this._window.addEventListener("pagehide",
+ this._pageHide = this._pageHide.bind(this),
+ true);
+
+ unload.ensure(this._public, "destroy");
+
+ // Ensure that worker._port is initialized for contentWorker to be able
+ // to send use event during WorkerSandbox(this)
+ this.port;
+
+ // will set this._contentWorker pointing to the private API:
+ this._contentWorker = WorkerSandbox(this);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ this._inited = true;
+ this._frozen = false;
+
+ // Flush all events that have been fired before the worker is initialized.
+ this._earlyEvents.forEach((function (args) this._emitEventToContent(args)).
+ bind(this));
+ },
+
+ _documentUnload: function _documentUnload(subject, topic, data) {
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != this._windowID) return false;
+ this._workerCleanup();
+ return true;
+ },
+
+ _pageShow: function _pageShow() {
+ this._contentWorker.emitSync("pageshow");
+ this._emit("pageshow");
+ this._frozen = false;
+ },
+
+ _pageHide: function _pageHide() {
+ this._contentWorker.emitSync("pagehide");
+ this._emit("pagehide");
+ this._frozen = true;
+ },
+
+ get url() {
+ // this._window will be null after detach
+ return this._window ? this._window.document.location.href : null;
+ },
+
+ get tab() {
+ // this._window will be null after detach
+ if (this._window)
+ return getTabForWindow(this._window);
+ return null;
+ },
+
+ /**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+ destroy: function destroy() {
+ this._workerCleanup();
+ this._removeAllListeners();
+ },
+
+ /**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+ _workerCleanup: function _workerCleanup() {
+ // maybe unloaded before content side is created
+ // As Symbiont call worker.constructor on document load
+ if (this._contentWorker)
+ this._contentWorker.destroy();
+ this._contentWorker = null;
+ if (this._window) {
+ this._window.removeEventListener("pageshow", this._pageShow, true);
+ this._window.removeEventListener("pagehide", this._pageHide, true);
+ }
+ this._window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (this._windowID) {
+ this._windowID = null;
+ observers.remove("inner-window-destroyed", this._documentUnload);
+ this._earlyEvents.slice(0, this._earlyEvents.length);
+ this._emit("detach");
+ }
+ },
+
+ /**
+ * Receive an event from the content script that need to be sent to
+ * worker.port. Provide a way for composed object to catch all events.
+ */
+ _onContentScriptEvent: function _onContentScriptEvent() {
+ this._port._emit.apply(this._port, arguments);
+ },
+
+ /**
+ * Reference to the content side of the worker.
+ * @type {WorkerGlobalScope}
+ */
+ _contentWorker: null,
+
+ /**
+ * Reference to the window that is accessible from
+ * the content scripts.
+ * @type {Object}
+ */
+ _window: null,
+
+ /**
+ * Flag to enable `addon` object injection in document. (bug 612726)
+ * @type {Boolean}
+ */
+ _injectInDocument: false
+});
+exports.Worker = Worker;