зеркало из https://github.com/mozilla/gecko-dev.git
Bug 880558 - Uplift addon-sdk to firefox r=me
This commit is contained in:
Родитель
207b857630
Коммит
6ca3643cbc
|
@ -4,7 +4,7 @@
|
|||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'experimental'
|
||||
'stability': 'deprecated'
|
||||
};
|
||||
|
||||
const { WindowTracker } = require('./deprecated/window-utils');
|
||||
|
@ -18,6 +18,12 @@ const addonURL = data.url('index.html');
|
|||
|
||||
const windows = ns();
|
||||
|
||||
require("./util/deprecate").deprecateUsage(
|
||||
"The addon-page module is deprecated." +
|
||||
"In the new Firefox UI design all pages will include navigational elements;" +
|
||||
"once the new design ships, using the addon-page module will not have any effect."
|
||||
);
|
||||
|
||||
WindowTracker({
|
||||
onTrack: function onTrack(window) {
|
||||
if (!isXULBrowser(window) || windows(window).hideChromeForLocation)
|
||||
|
|
|
@ -1,870 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* 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);
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* 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": "experimental"
|
||||
};
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const { open } = require("../event/dom");
|
||||
const { observe } = require("../event/chrome");
|
||||
const { filter, merge, map, expand } = require("../event/utils");
|
||||
const { windows } = require("../window/utils");
|
||||
const { events: windowEvents } = require("sdk/window/events");
|
||||
|
||||
// Note: Please note that even though pagehide event is included
|
||||
// it's not observable reliably since it's not always triggered
|
||||
// when closing tabs. Implementation can be imrpoved once that
|
||||
// event will be necessary.
|
||||
let TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"];
|
||||
|
||||
let insert = observe("document-element-inserted");
|
||||
let windowCreate = merge([
|
||||
observe("content-document-global-created"),
|
||||
observe("chrome-document-global-created")
|
||||
]);
|
||||
let create = map(windowCreate, function({target, data, type}) {
|
||||
return { target: target.document, type: type, data: data }
|
||||
});
|
||||
|
||||
function readStates({document}) {
|
||||
// Map supported event types to a streams of those events on the given
|
||||
// `window` for the inserted document and than merge these streams into
|
||||
// single form stream off all window state change events.
|
||||
let stateChanges = TYPES.map(function(type) {
|
||||
return open(document, type, { capture: true });
|
||||
});
|
||||
|
||||
// Since load events on document occur for every loded resource
|
||||
return filter(merge(stateChanges), function({target}) {
|
||||
return target instanceof Ci.nsIDOMDocument
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let opened = windows(null, { includePrivate: true });
|
||||
let state = merge(opened.map(readStates));
|
||||
|
||||
|
||||
let futureReady = filter(windowEvents, function({type})
|
||||
type === "DOMContentLoaded");
|
||||
let futureWindows = map(futureReady, function({target}) target);
|
||||
let futureState = expand(futureWindows, readStates);
|
||||
|
||||
exports.events = merge([insert, create, state, futureState]);
|
|
@ -27,14 +27,19 @@ const { getTabForWindow } = require('../tabs/helpers');
|
|||
const { getTabForContentWindow } = require('../tabs/utils');
|
||||
|
||||
/* 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';
|
||||
|
||||
// Fetch additional list of domains to authorize access to for each content
|
||||
// script. It is stored in manifest `metadata` field which contains
|
||||
// package.json data. This list is originaly defined by authors in
|
||||
// `permissions` attribute of their package.json addon file.
|
||||
const permissions = require('@loader/options').metadata['permissions'] || {};
|
||||
const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];
|
||||
|
||||
const JS_VERSION = '1.8';
|
||||
|
||||
const ERR_DESTROYED =
|
||||
|
@ -44,15 +49,6 @@ const ERR_DESTROYED =
|
|||
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({
|
||||
|
||||
|
@ -83,11 +79,6 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
|
@ -128,27 +119,39 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
let window = worker._window;
|
||||
let proto = window;
|
||||
|
||||
// Eventually use expanded principal sandbox feature, if some are given.
|
||||
//
|
||||
// But prevent it when the Worker isn't used for a content script but for
|
||||
// injecting `addon` object into a Panel, Widget, ... scope.
|
||||
// That's because:
|
||||
// 1/ It is useless to use multiple domains as the worker is only used
|
||||
// to communicate with the addon,
|
||||
// 2/ By using it it would prevent the document to have access to any JS
|
||||
// value of the worker. As JS values coming from multiple domain principals
|
||||
// can't be accessed by "mono-principals" (principal with only one domain).
|
||||
// Even if this principal is for a domain that is specified in the multiple
|
||||
// domain principal.
|
||||
let principals = window;
|
||||
let wantXHRConstructor = false;
|
||||
if (EXPANDED_PRINCIPALS.length > 0 && !worker._injectInDocument) {
|
||||
principals = EXPANDED_PRINCIPALS.concat(window);
|
||||
// We have to replace XHR constructor of the content document
|
||||
// with a custom cross origin one, automagically added by platform code:
|
||||
delete proto.XMLHttpRequest;
|
||||
wantXHRConstructor = true;
|
||||
}
|
||||
|
||||
// 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, sameZoneAs: window });
|
||||
let apiSandbox = sandbox(principals, { wantXrays: true, sameZoneAs: window });
|
||||
apiSandbox.console = console;
|
||||
|
||||
// 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) {
|
||||
// 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, {
|
||||
let content = this._sandbox = sandbox(principals, {
|
||||
sandboxPrototype: proto,
|
||||
wantXrays: true,
|
||||
wantXHRConstructor: wantXHRConstructor,
|
||||
sameZoneAs: window
|
||||
});
|
||||
// We have to ensure that window.top and window.parent are the exact same
|
||||
|
@ -231,12 +234,6 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
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) {
|
||||
|
@ -314,7 +311,6 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
this.emitSync("detach");
|
||||
this._sandbox = null;
|
||||
this._addonWorker = null;
|
||||
this._wrap = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -473,11 +469,6 @@ const Worker = EventEmitter.compose({
|
|||
|
||||
this._setListeners(options);
|
||||
|
||||
// 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;
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
|
||||
// Ensure that worker._port is initialized for contentWorker to be able
|
||||
|
|
|
@ -84,9 +84,9 @@ exports.publicConstructor = function publicConstructor(privateCtor) {
|
|||
exports.validateOptions = function validateOptions(options, requirements) {
|
||||
options = options || {};
|
||||
let validatedOptions = {};
|
||||
let mapThrew = false;
|
||||
|
||||
for (let key in requirements) {
|
||||
let mapThrew = false;
|
||||
let req = requirements[key];
|
||||
let [optsVal, keyInOpts] = (key in options) ?
|
||||
[options[key], true] :
|
||||
|
|
|
@ -111,8 +111,15 @@ const List = Trait.resolve({ toString: null }).compose({
|
|||
__iterator__: function __iterator__(onKeys, onKeyValue) {
|
||||
let array = this._keyValueMap.slice(0),
|
||||
i = -1;
|
||||
for each(let element in array)
|
||||
for (let element of array)
|
||||
yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
|
||||
},
|
||||
iterator: function iterator() {
|
||||
let array = this._keyValueMap.slice(0);
|
||||
|
||||
for (let element of array)
|
||||
yield element;
|
||||
}
|
||||
|
||||
});
|
||||
exports.List = List;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* 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, Cr } = require("chrome");
|
||||
const { emit, on, off } = require("./core");
|
||||
const { addObserver } = Cc['@mozilla.org/observer-service;1'].
|
||||
getService(Ci.nsIObserverService);
|
||||
|
||||
// Simple class that can be used to instantiate event channel that
|
||||
// implements `nsIObserver` interface. It's will is used by `observe`
|
||||
// function as observer + event target. It basically proxies observer
|
||||
// notifications as to it's registered listeners.
|
||||
function ObserverChannel() {}
|
||||
Object.freeze(Object.defineProperties(ObserverChannel.prototype, {
|
||||
QueryInterface: {
|
||||
value: function(iid) {
|
||||
if (!iid.equals(Ci.nsIObserver) &&
|
||||
!iid.equals(Ci.nsISupportsWeakReference) &&
|
||||
!iid.equals(Ci.nsISupports))
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
},
|
||||
observe: {
|
||||
value: function(subject, topic, data) {
|
||||
emit(this, "data", {
|
||||
type: topic,
|
||||
target: subject,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
function observe(topic) {
|
||||
let observerChannel = new ObserverChannel();
|
||||
|
||||
// Note: `nsIObserverService` will not hold a weak reference to a
|
||||
// observerChannel (since third argument is `true`). There for if it
|
||||
// will be GC-ed with all it's event listeners once no other references
|
||||
// will be held.
|
||||
addObserver(observerChannel, topic, true);
|
||||
|
||||
return observerChannel;
|
||||
}
|
||||
|
||||
exports.observe = observe;
|
|
@ -74,10 +74,40 @@ function partial(fn) {
|
|||
}
|
||||
exports.partial = partial;
|
||||
|
||||
exports.curry = deprecateFunction(partial,
|
||||
'curry is deprecated, ' +
|
||||
'please use require("sdk/lang/functional").partial instead.'
|
||||
);
|
||||
/**
|
||||
* Returns function with implicit currying, which will continue currying until
|
||||
* expected number of argument is collected. Expected number of arguments is
|
||||
* determined by `fn.length`. Using this with variadic functions is stupid,
|
||||
* so don't do it.
|
||||
*
|
||||
* @examples
|
||||
*
|
||||
* var sum = curry(function(a, b) {
|
||||
* return a + b
|
||||
* })
|
||||
* console.log(sum(2, 2)) // 4
|
||||
* console.log(sum(2)(4)) // 6
|
||||
*/
|
||||
var curry = new function() {
|
||||
function currier(fn, arity, params) {
|
||||
// Function either continues to curry arguments or executes function
|
||||
// if desired arguments have being collected.
|
||||
return function curried() {
|
||||
var input = Array.slice(arguments);
|
||||
// Prepend all curried arguments to the given arguments.
|
||||
if (params) input.unshift.apply(input, params);
|
||||
// If expected number of arguments has being collected invoke fn,
|
||||
// othrewise return curried version Otherwise continue curried.
|
||||
return (input.length >= arity) ? fn.apply(this, input) :
|
||||
currier(fn, arity, input);
|
||||
};
|
||||
}
|
||||
|
||||
return function curry(fn) {
|
||||
return currier(fn, fn.length);
|
||||
}
|
||||
};
|
||||
exports.curry = curry;
|
||||
|
||||
/**
|
||||
* Returns the composition of a list of functions, where each function consumes
|
||||
|
|
|
@ -7,149 +7,30 @@ module.metadata = {
|
|||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { deprecateFunction } = require("../util/deprecate");
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const memory = require('../deprecated/memory');
|
||||
const { when: unload } = require("../system/unload");
|
||||
const XMLHttpRequest = require("../addon/window").window.XMLHttpRequest;
|
||||
|
||||
// ## Implementation Notes ##
|
||||
//
|
||||
// Making `XMLHttpRequest` objects available to Jetpack code involves a
|
||||
// few key principles universal to all low-level module implementations:
|
||||
//
|
||||
// * **Unloadability**. A Jetpack-based extension using this module can be
|
||||
// asked to unload itself at any time, e.g. because the user decides to
|
||||
// uninstall or disable the extension. This means we need to keep track of
|
||||
// all in-progress reqests and abort them on unload.
|
||||
//
|
||||
// * **Developer-Ergonomic Tracebacks**. Whenever an exception is raised
|
||||
// by a Jetpack-based extension, we want it to be logged in a
|
||||
// place that is specific to that extension--so that a developer
|
||||
// can distinguish it from an error on a web page or in another
|
||||
// extension, for instance. We also want it to be logged with a
|
||||
// full stack traceback, which the Mozilla platform doesn't usually
|
||||
// do.
|
||||
//
|
||||
// Because of this, we don't actually want to give the Mozilla
|
||||
// platform's "real" XHR implementation to clients, but instead provide
|
||||
// a simple wrapper that trivially delegates to the implementation in
|
||||
// all cases except where callbacks are involved: whenever Mozilla
|
||||
// platform code calls into the extension, such as during the XHR's
|
||||
// `onreadystatechange` callback, we want to wrap the client's callback
|
||||
// in a try-catch clause that traps any exceptions raised by the
|
||||
// callback and logs them via console.exception() instead of allowing
|
||||
// them to propagate back into Mozilla platform code.
|
||||
|
||||
// This is a private list of all active requests, so we know what to
|
||||
// abort if we're asked to unload.
|
||||
var requests = [];
|
||||
|
||||
// Events on XHRs that we should listen for, so we know when to remove
|
||||
// a request from our private list.
|
||||
const TERMINATE_EVENTS = ["load", "error", "abort"];
|
||||
|
||||
// Read-only properties of XMLHttpRequest objects that we want to
|
||||
// directly delegate to.
|
||||
const READ_ONLY_PROPS = ["readyState", "responseText", "responseXML",
|
||||
"status", "statusText"];
|
||||
|
||||
// Methods of XMLHttpRequest that we want to directly delegate to.
|
||||
const DELEGATED_METHODS = ["abort", "getAllResponseHeaders",
|
||||
"getResponseHeader", "overrideMimeType",
|
||||
"send", "sendAsBinary", "setRequestHeader",
|
||||
"open"];
|
||||
|
||||
var getRequestCount = exports.getRequestCount = function getRequestCount() {
|
||||
return requests.length;
|
||||
};
|
||||
|
||||
var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
|
||||
let self = this;
|
||||
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
// For the sake of simplicity, don't tie this request to any UI.
|
||||
req.mozBackgroundRequest = true;
|
||||
|
||||
memory.track(req, "XMLHttpRequest");
|
||||
|
||||
this._req = req;
|
||||
this._orsc = null;
|
||||
this._cleanup = this._cleanup.bind(this);
|
||||
|
||||
requests.push(this);
|
||||
|
||||
TERMINATE_EVENTS.forEach(function(name) {
|
||||
self._req.addEventListener(name, self._cleanup, false);
|
||||
});
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype = {
|
||||
_cleanup: function _cleanup() {
|
||||
this.onreadystatechange = null;
|
||||
|
||||
let index = requests.indexOf(this);
|
||||
if (index != -1) {
|
||||
let self = this;
|
||||
TERMINATE_EVENTS.forEach(function(name) {
|
||||
self._req.removeEventListener(name, self._cleanup, false);
|
||||
});
|
||||
requests.splice(index, 1);
|
||||
}
|
||||
Object.defineProperties(XMLHttpRequest.prototype, {
|
||||
mozBackgroundRequest: {
|
||||
value: true,
|
||||
},
|
||||
_unload: function _unload() {
|
||||
this._req.abort();
|
||||
this._cleanup();
|
||||
},
|
||||
addEventListener: function addEventListener(name, func) {
|
||||
this._req.addEventListener(name, func);
|
||||
},
|
||||
removeEventListener: function removeEventListener(name, func) {
|
||||
this._req.removeEventListener(name, func);
|
||||
},
|
||||
set upload(newValue) {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
forceAllowThirdPartyCookie: function forceAllowThirdPartyCookie() {
|
||||
if (this._req.channel instanceof Ci.nsIHttpChannelInternal)
|
||||
this._req.channel.forceAllowThirdPartyCookie = true;
|
||||
},
|
||||
get onreadystatechange() {
|
||||
return this._orsc;
|
||||
},
|
||||
set onreadystatechange(cb) {
|
||||
this._orsc = cb;
|
||||
if (cb) {
|
||||
var self = this;
|
||||
this._req.onreadystatechange = function() {
|
||||
try {
|
||||
self._orsc.apply(self, arguments);
|
||||
}
|
||||
catch (e) {
|
||||
console.exception(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
this._req.onreadystatechange = null;
|
||||
}
|
||||
forceAllowThirdPartyCookie: {
|
||||
configurable: true,
|
||||
value: deprecateFunction(function() {
|
||||
forceAllowThirdPartyCookie(this);
|
||||
|
||||
}, "`xhr.forceAllowThirdPartyCookie()` is deprecated, please use" +
|
||||
"`require('sdk/net/xhr').forceAllowThirdPartyCookie(request)` instead")
|
||||
}
|
||||
};
|
||||
|
||||
READ_ONLY_PROPS.forEach(
|
||||
function(name) {
|
||||
XMLHttpRequest.prototype.__defineGetter__(
|
||||
name,
|
||||
function() {
|
||||
return this._req[name];
|
||||
});
|
||||
});
|
||||
|
||||
DELEGATED_METHODS.forEach(
|
||||
function(name) {
|
||||
XMLHttpRequest.prototype[name] = function() {
|
||||
return this._req[name].apply(this._req, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
unload(function() {
|
||||
requests.slice().forEach(function(request) { request._unload(); });
|
||||
});
|
||||
exports.XMLHttpRequest = XMLHttpRequest;
|
||||
|
||||
function forceAllowThirdPartyCookie(xhr) {
|
||||
if (xhr.channel instanceof Ci.nsIHttpChannelInternal)
|
||||
xhr.channel.forceAllowThirdPartyCookie = true;
|
||||
}
|
||||
exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie;
|
||||
|
||||
// No need to handle add-on unloads as addon/window is closed at unload
|
||||
// and it will take down all the associated requests.
|
|
@ -14,7 +14,7 @@ const { merge } = require("./util/object");
|
|||
const { stringify } = require("./querystring");
|
||||
const { EventTarget } = require("./event/target");
|
||||
const { Class } = require("./core/heritage");
|
||||
const { XMLHttpRequest } = require("./net/xhr");
|
||||
const { XMLHttpRequest, forceAllowThirdPartyCookie } = require("./net/xhr");
|
||||
const apiUtils = require("./deprecated/api-utils");
|
||||
const { isValidURI } = require("./url.js");
|
||||
|
||||
|
@ -75,7 +75,8 @@ function runRequest(mode, target) {
|
|||
// open the request
|
||||
xhr.open(mode, url);
|
||||
|
||||
xhr.forceAllowThirdPartyCookie();
|
||||
|
||||
forceAllowThirdPartyCookie(xhr);
|
||||
|
||||
// request header must be set after open, but before send
|
||||
xhr.setRequestHeader("Content-Type", contentType);
|
||||
|
|
|
@ -125,7 +125,7 @@ const TabTrait = Trait.compose(EventEmitter, {
|
|||
* listeners.
|
||||
*/
|
||||
_onEvent: function _onEvent(type, tab) {
|
||||
if (tab == this._public)
|
||||
if (viewNS(tab).tab == this._tab)
|
||||
this._emit(type, tab);
|
||||
},
|
||||
/**
|
||||
|
@ -248,8 +248,12 @@ const TabTrait = Trait.compose(EventEmitter, {
|
|||
* Close the tab
|
||||
*/
|
||||
close: function close(callback) {
|
||||
if (!this._tab)
|
||||
// Bug 699450: the tab may already have been detached
|
||||
if (!this._tab || !this._tab.parentNode) {
|
||||
if (callback)
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (callback)
|
||||
this.once(EVENTS.close.name, callback);
|
||||
this._window.gBrowser.removeTab(this._tab);
|
||||
|
|
|
@ -133,13 +133,21 @@ exports.isTabOpen = isTabOpen;
|
|||
function closeTab(tab) {
|
||||
let gBrowser = getTabBrowserForTab(tab);
|
||||
// normal case?
|
||||
if (gBrowser)
|
||||
if (gBrowser) {
|
||||
// Bug 699450: the tab may already have been detached
|
||||
if (!tab.parentNode)
|
||||
return;
|
||||
return gBrowser.removeTab(tab);
|
||||
}
|
||||
|
||||
let window = getWindowHoldingTab(tab);
|
||||
// fennec?
|
||||
if (window && window.BrowserApp)
|
||||
if (window && window.BrowserApp) {
|
||||
// Bug 699450: the tab may already have been detached
|
||||
if (!tab.browser)
|
||||
return;
|
||||
return window.BrowserApp.closeTab(tab);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
exports.closeTab = closeTab;
|
||||
|
@ -205,14 +213,20 @@ exports.getAllTabContentWindows = getAllTabContentWindows;
|
|||
function getTabForContentWindow(window) {
|
||||
// Retrieve the topmost frame container. It can be either <xul:browser>,
|
||||
// <xul:iframe/> or <html:iframe/>. But in our case, it should be xul:browser.
|
||||
let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
let browser;
|
||||
try {
|
||||
browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
} catch(e) {
|
||||
// Bug 699450: The tab may already have been detached so that `window` is
|
||||
// in a almost destroyed state and can't be queryinterfaced anymore.
|
||||
}
|
||||
|
||||
// Is null for toplevel documents
|
||||
if (!browser) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Retrieve the owner window, should be browser.xul one
|
||||
|
|
|
@ -2696,7 +2696,7 @@ ServerHandler.prototype =
|
|||
throw e;
|
||||
}
|
||||
|
||||
function writeMore()
|
||||
let writeMore = function writeMore()
|
||||
{
|
||||
gThreadManager.currentThread
|
||||
.dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
|
|
|
@ -45,6 +45,13 @@ const List = Class({
|
|||
i = -1;
|
||||
for each(let element in array)
|
||||
yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
|
||||
},
|
||||
iterator: function iterator() {
|
||||
let array = listNS(this).keyValueMap.slice(0),
|
||||
i = -1;
|
||||
|
||||
for (let element of array)
|
||||
yield element;
|
||||
}
|
||||
});
|
||||
exports.List = List;
|
||||
|
|
|
@ -8,73 +8,39 @@ module.metadata = {
|
|||
};
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const events = require("../system/events");
|
||||
const { on, off, emit } = require("../event/core");
|
||||
const { observe } = require("../event/chrome");
|
||||
const { open } = require("../event/dom");
|
||||
const { windows } = require("../window/utils");
|
||||
|
||||
// Object represents event channel on which all top level window events
|
||||
// will be dispatched, allowing users to react to those evens.
|
||||
const channel = {};
|
||||
exports.events = channel;
|
||||
|
||||
const types = {
|
||||
domwindowopened: "open",
|
||||
domwindowclosed: "close",
|
||||
}
|
||||
|
||||
// Utility function to query observer notification subject to get DOM window.
|
||||
function nsIDOMWindow($) $.QueryInterface(Ci.nsIDOMWindow);
|
||||
|
||||
// Utility function used as system event listener that is invoked every time
|
||||
// top level window is open. This function does two things:
|
||||
// 1. Registers event listeners to track when document becomes interactive and
|
||||
// when it's done loading. This will become obsolete once Bug 843910 is
|
||||
// fixed.
|
||||
// 2. Forwards event to an exported event stream.
|
||||
function onOpen(event) {
|
||||
observe(nsIDOMWindow(event.subject));
|
||||
dispatch(event);
|
||||
}
|
||||
const { filter, merge, map, expand } = require("../event/utils");
|
||||
|
||||
// Function registers single shot event listeners for relevant window events
|
||||
// that forward events to exported event stream.
|
||||
function observe(window) {
|
||||
function listener(event) {
|
||||
if (event.target === window.document) {
|
||||
window.removeEventListener(event.type, listener, true);
|
||||
emit(channel, "data", { type: event.type, target: window });
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we do not remove listeners on unload since on add-on unload we
|
||||
// nuke add-on sandbox that should allow GC-ing listeners. This also has
|
||||
// positive effects on add-on / firefox unloads.
|
||||
window.addEventListener("DOMContentLoaded", listener, true);
|
||||
window.addEventListener("load", listener, true);
|
||||
// TODO: Also add focus event listener so that can be forwarded to event
|
||||
// stream. It can be part of Bug 854982.
|
||||
}
|
||||
|
||||
// Utility function that takes system notification event and forwards it to a
|
||||
// channel in restructured form.
|
||||
function dispatch({ type: topic, subject }) {
|
||||
emit(channel, "data", {
|
||||
topic: topic,
|
||||
type: types[topic],
|
||||
target: nsIDOMWindow(subject)
|
||||
function eventsFor(window) {
|
||||
let interactive = open(window, "DOMContentLoaded", { capture: true });
|
||||
let complete = open(window, "load", { capture: true });
|
||||
let states = merge([interactive, complete]);
|
||||
let changes = filter(states, function({target}) target === window.document);
|
||||
return map(changes, function({type, target}) {
|
||||
return { type: type, target: target.defaultView }
|
||||
});
|
||||
}
|
||||
|
||||
// In addition to observing windows that are open we also observe windows
|
||||
// that are already already opened in case they're in process of loading.
|
||||
let opened = windows(null, { includePrivate: true });
|
||||
opened.forEach(observe);
|
||||
let currentEvents = merge(opened.map(eventsFor));
|
||||
|
||||
// Register system event listeners to forward messages on exported event
|
||||
// stream. Note that by default only weak refs are kept by system events
|
||||
// module so they will be GC-ed once add-on unloads and no manual cleanup
|
||||
// is required. Also note that listeners are intentionally not inlined since
|
||||
// to avoid premature GC-ing. Currently refs are kept by module scope and there
|
||||
// for they remain alive.
|
||||
events.on("domwindowopened", onOpen);
|
||||
events.on("domwindowclosed", dispatch);
|
||||
// Register system event listeners for top level window open / close.
|
||||
function rename({type, target, data}) {
|
||||
return { type: rename[type], target: target, data: data }
|
||||
}
|
||||
rename.domwindowopened = "open";
|
||||
rename.domwindowclosed = "close";
|
||||
|
||||
let openEvents = map(observe("domwindowopened"), rename);
|
||||
let closeEvents = map(observe("domwindowclosed"), rename);
|
||||
let futureEvents = expand(openEvents, function({target}) eventsFor(target));
|
||||
|
||||
let channel = merge([currentEvents, futureEvents,
|
||||
openEvents, closeEvents]);
|
||||
exports.events = channel;
|
||||
|
|
|
@ -22,7 +22,7 @@ const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
|
|||
const BROWSER = 'navigator:browser',
|
||||
URI_BROWSER = 'chrome://browser/content/browser.xul',
|
||||
NAME = '_blank',
|
||||
FEATURES = 'chrome,all,dialog=no';
|
||||
FEATURES = 'chrome,all,dialog=no,non-private';
|
||||
|
||||
function isWindowPrivate(win) {
|
||||
if (!win)
|
||||
|
@ -243,9 +243,21 @@ function openDialog(options) {
|
|||
options = options || {};
|
||||
|
||||
let features = options.features || FEATURES;
|
||||
if (!!options.private &&
|
||||
!array.has(features.toLowerCase().split(','), 'private')) {
|
||||
features = features.split(',').concat('private').join(',');
|
||||
let featureAry = features.toLowerCase().split(',');
|
||||
|
||||
if (!!options.private) {
|
||||
// add private flag if private window is desired
|
||||
if (!array.has(featureAry, 'private')) {
|
||||
featureAry.push('private');
|
||||
}
|
||||
|
||||
// remove the non-private flag ig a private window is desired
|
||||
let nonPrivateIndex = featureAry.indexOf('non-private');
|
||||
if (nonPrivateIndex >= 0) {
|
||||
featureAry.splice(nonPrivateIndex, 1);
|
||||
}
|
||||
|
||||
features = featureAry.join(',');
|
||||
}
|
||||
|
||||
let browser = getMostRecentBrowserWindow();
|
||||
|
|
|
@ -39,7 +39,9 @@ const WindowLoader = Trait.compose({
|
|||
*/
|
||||
_onUnload: Trait.required,
|
||||
_load: function _load() {
|
||||
if (this.__window) return;
|
||||
if (this.__window)
|
||||
return;
|
||||
|
||||
this._window = openDialog({
|
||||
private: this._isPrivate,
|
||||
args: this._tabOptions.map(function(options) options.url).join("|")
|
||||
|
|
|
@ -113,6 +113,12 @@ def resolve_dirs(pkg_cfg, dirnames):
|
|||
def resolve_dir(pkg_cfg, dirname):
|
||||
return os.path.join(pkg_cfg.root_dir, dirname)
|
||||
|
||||
def validate_permissions(perms):
|
||||
if (perms.get('cross-domain-content') and
|
||||
not isinstance(perms.get('cross-domain-content'), list)):
|
||||
raise ValueError("Error: `cross-domain-content` permissions in \
|
||||
package.json file must be an array of strings:\n %s" % perms)
|
||||
|
||||
def get_metadata(pkg_cfg, deps):
|
||||
metadata = Bunch()
|
||||
for pkg_name in deps:
|
||||
|
@ -120,6 +126,8 @@ def get_metadata(pkg_cfg, deps):
|
|||
metadata[pkg_name] = Bunch()
|
||||
for prop in METADATA_PROPS:
|
||||
if cfg.get(prop):
|
||||
if prop == 'permissions':
|
||||
validate_permissions(cfg[prop])
|
||||
metadata[pkg_name][prop] = cfg[prop]
|
||||
return metadata
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/* 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";
|
||||
|
||||
const xulApp = require("xul-app");
|
||||
const { PageMod } = require("page-mod");
|
||||
const tabs = require("tabs");
|
||||
|
||||
exports.testCrossDomainIframe = function(assert, done) {
|
||||
let serverPort = 8099;
|
||||
let server = require("sdk/test/httpd").startServerAsync(serverPort);
|
||||
server.registerPathHandler("/iframe", function handle(request, response) {
|
||||
response.write("<html><body>foo</body></html>");
|
||||
});
|
||||
|
||||
let pageMod = PageMod({
|
||||
include: "about:*",
|
||||
contentScript: "new " + function ContentScriptScope() {
|
||||
self.on("message", function (url) {
|
||||
let iframe = document.createElement("iframe");
|
||||
iframe.addEventListener("load", function onload() {
|
||||
iframe.removeEventListener("load", onload, false);
|
||||
self.postMessage(iframe.contentWindow.document.body.innerHTML);
|
||||
}, false);
|
||||
iframe.setAttribute("src", url);
|
||||
document.documentElement.appendChild(iframe);
|
||||
});
|
||||
},
|
||||
onAttach: function(w) {
|
||||
w.on("message", function (body) {
|
||||
assert.equal(body, "foo", "received iframe html content");
|
||||
pageMod.destroy();
|
||||
w.tab.close();
|
||||
server.stop(done);
|
||||
});
|
||||
w.postMessage("http://localhost:8099/iframe");
|
||||
}
|
||||
});
|
||||
|
||||
tabs.open("about:credits");
|
||||
};
|
||||
|
||||
exports.testCrossDomainXHR = function(assert, done) {
|
||||
let serverPort = 8099;
|
||||
let server = require("sdk/test/httpd").startServerAsync(serverPort);
|
||||
server.registerPathHandler("/xhr", function handle(request, response) {
|
||||
response.write("foo");
|
||||
});
|
||||
|
||||
let pageMod = PageMod({
|
||||
include: "about:*",
|
||||
contentScript: "new " + function ContentScriptScope() {
|
||||
self.on("message", function (url) {
|
||||
let request = new XMLHttpRequest();
|
||||
request.overrideMimeType("text/plain");
|
||||
request.open("GET", url, true);
|
||||
request.onload = function () {
|
||||
self.postMessage(request.responseText);
|
||||
};
|
||||
request.send(null);
|
||||
});
|
||||
},
|
||||
onAttach: function(w) {
|
||||
w.on("message", function (body) {
|
||||
assert.equal(body, "foo", "received XHR content");
|
||||
pageMod.destroy();
|
||||
w.tab.close();
|
||||
server.stop(done);
|
||||
});
|
||||
w.postMessage("http://localhost:8099/xhr");
|
||||
}
|
||||
});
|
||||
|
||||
tabs.open("about:credits");
|
||||
};
|
||||
|
||||
if (!xulApp.versionInRange(xulApp.platformVersion, "17.0a2", "*")) {
|
||||
module.exports = {
|
||||
"test Unsupported Application": function Unsupported (assert) {
|
||||
assert.pass("This firefox version doesn't support cross-domain-content permission.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
require("sdk/test/runner").runTestsFromModule(module);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "content-permissions",
|
||||
"permissions": {
|
||||
"cross-domain-content": ["http://localhost:8099"]
|
||||
}
|
||||
}
|
|
@ -4,10 +4,11 @@
|
|||
'use strict';
|
||||
|
||||
const { pb, pbUtils } = require('./helper');
|
||||
const { openDialog, open } = require('sdk/window/utils');
|
||||
const { promise, close } = require('sdk/window/helpers');
|
||||
const { onFocus, openDialog, open } = require('sdk/window/utils');
|
||||
const { open: openPromise, close, focus, promise } = require('sdk/window/helpers');
|
||||
const { isPrivate } = require('sdk/private-browsing');
|
||||
const { browserWindows: windows } = require('sdk/windows');
|
||||
const { defer } = require('sdk/core/promise');
|
||||
|
||||
// test openDialog() from window/utils with private option
|
||||
// test isActive state in pwpb case
|
||||
|
@ -53,7 +54,7 @@ exports.testPerWindowPrivateBrowsingGetter = function(assert, done) {
|
|||
});
|
||||
}
|
||||
|
||||
exports.testIsPrivateOnWindowOn = function(assert, done) {
|
||||
exports.testIsPrivateOnWindowOpen = function(assert, done) {
|
||||
windows.open({
|
||||
isPrivate: true,
|
||||
onOpen: function(window) {
|
||||
|
@ -64,6 +65,33 @@ exports.testIsPrivateOnWindowOn = function(assert, done) {
|
|||
});
|
||||
}
|
||||
|
||||
exports.testIsPrivateOnWindowOpenFromPrivate = function(assert, done) {
|
||||
// open a private window
|
||||
openPromise(null, {
|
||||
features: {
|
||||
private: true,
|
||||
chrome: true,
|
||||
titlebar: true,
|
||||
toolbar: true
|
||||
}
|
||||
}).then(focus).then(function(window) {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
assert.equal(isPrivate(window), true, 'the only open window is private');
|
||||
|
||||
windows.open({
|
||||
url: 'about:blank',
|
||||
onOpen: function(w) {
|
||||
assert.equal(isPrivate(w), false, 'new test window is not private');
|
||||
w.close(function() resolve(window));
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}).then(close).
|
||||
then(done, assert.fail);
|
||||
}
|
||||
|
||||
exports.testIsPrivateOnWindowOff = function(assert, done) {
|
||||
windows.open({
|
||||
onOpen: function(window) {
|
||||
|
|
|
@ -287,12 +287,25 @@ exports.testTabClose = function(test) {
|
|||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
|
||||
let secondOnCloseCalled = false;
|
||||
tab.close(function() {
|
||||
closeBrowserWindow(window, function() {
|
||||
test.assert(secondOnCloseCalled,
|
||||
"The immediate second call to tab.close gots its callback fired");
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
test.done()
|
||||
});
|
||||
});
|
||||
|
||||
// Bug 699450: Multiple calls to tab should not throw
|
||||
try {
|
||||
tab.close(function () {
|
||||
secondOnCloseCalled = true;
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
test.fail("second call to tab.close() thrown an exception: " + e);
|
||||
}
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
});
|
||||
|
||||
|
@ -439,7 +452,7 @@ exports.testOpenInNewWindow = function(test) {
|
|||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
|
||||
test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
|
||||
for (var i in cache) cache[i] = null;
|
||||
for (let i in cache) cache[i] = null;
|
||||
wt.unload();
|
||||
closeBrowserWindow(newWindow, function() {
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
|
@ -449,6 +462,44 @@ exports.testOpenInNewWindow = function(test) {
|
|||
});
|
||||
};
|
||||
|
||||
// Test tab.open inNewWindow + onOpen combination
|
||||
exports.testOpenInNewWindowOnOpen = function(test) {
|
||||
test.waitUntilDone();
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let cache = [];
|
||||
let windowUtils = require("sdk/deprecated/window-utils");
|
||||
let wt = new windowUtils.WindowTracker({
|
||||
onTrack: function(win) {
|
||||
cache.push(win);
|
||||
},
|
||||
onUntrack: function(win) {
|
||||
cache.splice(cache.indexOf(win), 1)
|
||||
}
|
||||
});
|
||||
let startWindowCount = cache.length;
|
||||
|
||||
let url = "data:text/html;charset=utf-8,newwindow";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inNewWindow: true,
|
||||
onOpen: function(tab) {
|
||||
let newWindow = cache[cache.length - 1];
|
||||
test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
|
||||
test.assertEqual(activeWindow, newWindow, "new window is active");
|
||||
|
||||
for (let i in cache) cache[i] = null;
|
||||
wt.unload();
|
||||
|
||||
closeBrowserWindow(newWindow, function() {
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: onOpen event handler
|
||||
exports.testTabsEvent_onOpen = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
const { isTabOpen, activateTab, openTab,
|
||||
closeTab, getURI } = require('sdk/tabs/utils');
|
||||
const windows = require('sdk/deprecated/window-utils');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const { LoaderWithHookedConsole } = require('sdk/test/loader');
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
const { is } = require('sdk/system/xul-app');
|
||||
const tabs = require('sdk/tabs');
|
||||
const isAustralis = "gCustomizeMode" in windows.activeBrowserWindow;
|
||||
|
||||
let uri = require('sdk/self').data.url('index.html');
|
||||
|
||||
|
@ -19,8 +20,23 @@ function isChromeVisible(window) {
|
|||
return x !== 'true';
|
||||
}
|
||||
|
||||
exports['test add-on page deprecation message'] = function(assert) {
|
||||
let { loader, messages } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
assert.equal(messages.length, 1, "only one error is dispatched");
|
||||
assert.equal(messages[0].type, "error", "the console message is an error");
|
||||
|
||||
let msg = messages[0].msg;
|
||||
|
||||
assert.ok(msg.indexOf("DEPRECATED") === 0,
|
||||
"The message is deprecation message");
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports['test that add-on page has no chrome'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
let window = windows.activeBrowserWindow;
|
||||
|
@ -33,7 +49,8 @@ exports['test that add-on page has no chrome'] = function(assert, done) {
|
|||
setTimeout(function() {
|
||||
activateTab(tab);
|
||||
|
||||
assert.equal(isChromeVisible(window), is('Fennec'), 'chrome is not visible for addon page');
|
||||
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
|
||||
'chrome is not visible for addon page');
|
||||
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
|
@ -43,7 +60,7 @@ exports['test that add-on page has no chrome'] = function(assert, done) {
|
|||
};
|
||||
|
||||
exports['test that add-on page with hash has no chrome'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
let window = windows.activeBrowserWindow;
|
||||
|
@ -56,7 +73,8 @@ exports['test that add-on page with hash has no chrome'] = function(assert, done
|
|||
setTimeout(function() {
|
||||
activateTab(tab);
|
||||
|
||||
assert.equal(isChromeVisible(window), is('Fennec'), 'chrome is not visible for addon page');
|
||||
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
|
||||
'chrome is not visible for addon page');
|
||||
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
|
@ -66,7 +84,7 @@ exports['test that add-on page with hash has no chrome'] = function(assert, done
|
|||
};
|
||||
|
||||
exports['test that add-on page with querystring has no chrome'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
let window = windows.activeBrowserWindow;
|
||||
|
@ -79,7 +97,8 @@ exports['test that add-on page with querystring has no chrome'] = function(asser
|
|||
setTimeout(function() {
|
||||
activateTab(tab);
|
||||
|
||||
assert.equal(isChromeVisible(window), is('Fennec'), 'chrome is not visible for addon page');
|
||||
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
|
||||
'chrome is not visible for addon page');
|
||||
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
|
@ -89,7 +108,7 @@ exports['test that add-on page with querystring has no chrome'] = function(asser
|
|||
};
|
||||
|
||||
exports['test that add-on page with hash and querystring has no chrome'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
let window = windows.activeBrowserWindow;
|
||||
|
@ -102,7 +121,8 @@ exports['test that add-on page with hash and querystring has no chrome'] = funct
|
|||
setTimeout(function() {
|
||||
activateTab(tab);
|
||||
|
||||
assert.equal(isChromeVisible(window), is('Fennec'), 'chrome is not visible for addon page');
|
||||
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
|
||||
'chrome is not visible for addon page');
|
||||
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
|
@ -112,7 +132,7 @@ exports['test that add-on page with hash and querystring has no chrome'] = funct
|
|||
};
|
||||
|
||||
exports['test that malformed uri is not an addon-page'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
let window = windows.activeBrowserWindow;
|
||||
|
@ -132,7 +152,7 @@ exports['test that malformed uri is not an addon-page'] = function(assert, done)
|
|||
};
|
||||
|
||||
exports['test that add-on pages are closed on unload'] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
tabs.open({
|
||||
|
|
|
@ -175,6 +175,18 @@ exports.testValidateMapWithMissingKey = function (test) {
|
|||
assertObjsEqual(test, val, { });
|
||||
};
|
||||
|
||||
exports.testValidateMapWithMissingKeyAndThrown = function (test) {
|
||||
let val = apiUtils.validateOptions({}, {
|
||||
bar: {
|
||||
map: function(v) { throw "bar" }
|
||||
},
|
||||
baz: {
|
||||
map: function(v) "foo"
|
||||
}
|
||||
});
|
||||
assertObjsEqual(test, val, { baz: "foo" });
|
||||
};
|
||||
|
||||
exports.testAddIterator = function testAddIterator(test) {
|
||||
let obj = {};
|
||||
let keys = ["foo", "bar", "baz"];
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/* 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";
|
||||
|
||||
const { Loader } = require("sdk/test/loader");
|
||||
const { getMostRecentBrowserWindow, getInnerId } = require("sdk/window/utils");
|
||||
const { openTab, closeTab, getBrowserForTab } = require("sdk/tabs/utils");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const { curry, identity, partial } = require("sdk/lang/functional");
|
||||
|
||||
let when = curry(function(options, tab) {
|
||||
let type = options.type || options;
|
||||
let capture = options.captuer || false;
|
||||
let target = getBrowserForTab(tab);
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
target.addEventListener(type, function handler(event) {
|
||||
if (!event.target.defaultView.frameElement) {
|
||||
target.removeEventListener(type, handler, capture);
|
||||
resolve(tab);
|
||||
}
|
||||
}, capture);
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
let use = function(value) function() value;
|
||||
|
||||
|
||||
let open = curry(function(url, window) openTab(window, url));
|
||||
let close = function(tab) {
|
||||
let promise = when("pagehide", tab);
|
||||
closeTab(tab);
|
||||
return promise;
|
||||
}
|
||||
|
||||
exports["test multiple tabs"] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { events } = loader.require("sdk/content/events");
|
||||
let { on, off } = loader.require("sdk/event/core");
|
||||
let actual = [];
|
||||
on(events, "data", function({type, target, timeStamp}) {
|
||||
// ignore about:blank pages.
|
||||
if (target.URL !== "about:blank") actual.push(type + " -> " + target.URL)
|
||||
});
|
||||
|
||||
let window = getMostRecentBrowserWindow();
|
||||
let firstTab = open("data:text/html,first-tab", window);
|
||||
|
||||
when("pageshow", firstTab).
|
||||
then(close).
|
||||
then(use(window)).
|
||||
then(open("data:text/html,second-tab")).
|
||||
then(when("pageshow")).
|
||||
then(close).
|
||||
then(function() {
|
||||
assert.deepEqual(actual, [
|
||||
"document-element-inserted -> data:text/html,first-tab",
|
||||
"DOMContentLoaded -> data:text/html,first-tab",
|
||||
"load -> data:text/html,first-tab",
|
||||
"pageshow -> data:text/html,first-tab",
|
||||
"document-element-inserted -> data:text/html,second-tab",
|
||||
"DOMContentLoaded -> data:text/html,second-tab",
|
||||
"load -> data:text/html,second-tab",
|
||||
"pageshow -> data:text/html,second-tab"
|
||||
], "all events dispatche as expeced")
|
||||
}, function(reason) {
|
||||
assert.fail(Error(reason));
|
||||
}).then(function() {
|
||||
loader.unload();
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
exports["test nested frames"] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let { events } = loader.require("sdk/content/events");
|
||||
let { on, off } = loader.require("sdk/event/core");
|
||||
let actual = [];
|
||||
on(events, "data", function({type, target, timeStamp}) {
|
||||
// ignore about:blank pages.
|
||||
if (target.URL !== "about:blank") actual.push(type + " -> " + target.URL)
|
||||
});
|
||||
|
||||
let window = getMostRecentBrowserWindow();
|
||||
let uri = encodeURI("data:text/html,<iframe src='data:text/html,iframe'>");
|
||||
let tab = open(uri, window);
|
||||
|
||||
when("pageshow", tab).
|
||||
then(close).
|
||||
then(function() {
|
||||
assert.deepEqual(actual, [
|
||||
"document-element-inserted -> " + uri,
|
||||
"content-document-global-created -> data:text/html,iframe",
|
||||
"DOMContentLoaded -> " + uri,
|
||||
"document-element-inserted -> data:text/html,iframe",
|
||||
"DOMContentLoaded -> data:text/html,iframe",
|
||||
"load -> data:text/html,iframe",
|
||||
"pageshow -> data:text/html,iframe",
|
||||
"load -> " + uri,
|
||||
"pageshow -> " + uri
|
||||
], "events where dispatched")
|
||||
}, function(reason) {
|
||||
assert.fail(Error(reason))
|
||||
}).then(function() {
|
||||
loader.unload();
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
require("test").run(exports);
|
|
@ -3,10 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const hiddenFrames = require("sdk/frame/hidden-frame");
|
||||
const xulApp = require("sdk/system/xul-app");
|
||||
|
||||
const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
|
||||
"17.0a2", "*");
|
||||
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
|
||||
|
@ -55,14 +51,9 @@ function createProxyTest(html, callback) {
|
|||
}
|
||||
|
||||
function createWorker(assert, xrayWindow, contentScript, done) {
|
||||
// We have to use Sandboxed loader in order to get access to the private
|
||||
// unlock key `PRIVATE_KEY`. This key should not be used anywhere else.
|
||||
// See `PRIVATE_KEY` definition in worker.js
|
||||
let loader = Loader(module);
|
||||
let Worker = loader.require("sdk/content/worker").Worker;
|
||||
let key = loader.sandbox("sdk/content/worker").PRIVATE_KEY;
|
||||
let worker = Worker({
|
||||
exposeUnlockKey : USE_JS_PROXIES ? key : null,
|
||||
window: xrayWindow,
|
||||
contentScript: [
|
||||
'new ' + function () {
|
||||
|
@ -131,22 +122,6 @@ exports["test Create Proxy Test With Events"] = createProxyTest("", function (he
|
|||
|
||||
});
|
||||
|
||||
if (USE_JS_PROXIES) {
|
||||
// Verify that the attribute `exposeUnlockKey`, that allow this test
|
||||
// to identify proxies, works correctly.
|
||||
// See `PRIVATE_KEY` definition in worker.js
|
||||
exports["test Key Access"] = createProxyTest("", function(helper) {
|
||||
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`");
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Bug 714778: There was some issue around `toString` functions
|
||||
// that ended up being shared between content scripts
|
||||
exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
|
||||
|
@ -158,10 +133,7 @@ exports["test Shared To String Proxies"] = createProxyTest("", function(helper)
|
|||
// It only applies to JS proxies, there isn't any such issue with xrays.
|
||||
//document.location.toString = function foo() {};
|
||||
document.location.toString.foo = "bar";
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert(!("foo" in document.location.toString), "document.location.toString can't be modified");
|
||||
else
|
||||
assert("foo" in document.location.toString, "document.location.toString can be modified");
|
||||
assert("foo" in document.location.toString, "document.location.toString can be modified");
|
||||
assert(document.location.toString() == "data:text/html;charset=utf-8,",
|
||||
"First document.location.toString()");
|
||||
self.postMessage("next");
|
||||
|
@ -190,17 +162,11 @@ exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
|
|||
ifWindow.addEventListener("message", function listener(event) {
|
||||
ifWindow.removeEventListener("message", listener, false);
|
||||
// As we are in system principal, event is an XrayWrapper
|
||||
if (USE_JS_PROXIES) {
|
||||
assert.equal(event.source, ifWindow,
|
||||
"event.source is the iframe window");
|
||||
}
|
||||
else {
|
||||
// JS proxies had different behavior than xrays, xrays use current
|
||||
// compartments when calling postMessage method. Whereas js proxies
|
||||
// was using postMessage method compartment, not the caller one.
|
||||
assert.equal(event.source, helper.xrayWindow,
|
||||
"event.source is the top window");
|
||||
}
|
||||
// xrays use current compartments when calling postMessage method.
|
||||
// Whereas js proxies was using postMessage method compartment,
|
||||
// not the caller one.
|
||||
assert.equal(event.source, helper.xrayWindow,
|
||||
"event.source is the top window");
|
||||
assert.equal(event.origin, "null", "origin is null");
|
||||
|
||||
assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
|
||||
|
@ -234,8 +200,6 @@ exports["test Object Listener"] = createProxyTest(html, function (helper) {
|
|||
assert(this === myClickListener, "`this` is the original object");
|
||||
assert(!this.called, "called only once");
|
||||
this.called = true;
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
|
||||
assert(event.target, input, "event.target is the wrapped window");
|
||||
done();
|
||||
}
|
||||
|
@ -262,8 +226,6 @@ exports["test Object Listener 2"] = createProxyTest("", function (helper) {
|
|||
assert(this == myMessageListener, "`this` is the original object");
|
||||
assert(!this.called, "called only once");
|
||||
this.called = true;
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
|
||||
assert(event.target == document.defaultView, "event.target is the wrapped window");
|
||||
assert(event.source == document.defaultView, "event.source is the wrapped window");
|
||||
assert(event.origin == "null", "origin is null");
|
||||
|
@ -454,8 +416,6 @@ exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (he
|
|||
// Setting a custom object to a proxy attribute is not wrapped when we get it afterward
|
||||
let object = {custom: true, enumerable: false};
|
||||
body.customAttribute = object;
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
|
||||
assert(object === body.customAttribute, "custom JS attributes are not wrapped");
|
||||
done();
|
||||
}
|
||||
|
@ -587,14 +547,7 @@ exports["test Collections 2"] = createProxyTest(html, function (helper) {
|
|||
for(let i in body.childNodes) {
|
||||
count++;
|
||||
}
|
||||
// JS proxies were broken, we can iterate over some other items:
|
||||
// length, item and iterator
|
||||
let expectedCount;
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
expectedCount = 3;
|
||||
else
|
||||
expectedCount = 6;
|
||||
assert(count == expectedCount, "body.childNodes is iterable");
|
||||
assert(count == 6, "body.childNodes is iterable");
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
@ -603,19 +556,6 @@ exports["test Collections 2"] = createProxyTest(html, function (helper) {
|
|||
|
||||
exports["test valueOf"] = createProxyTest("", function (helper) {
|
||||
|
||||
if (USE_JS_PROXIES) {
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
// Check internal use of valueOf() for JS proxies API
|
||||
assert(/\[object Window.*\]/.test(window.valueOf().toString()),
|
||||
"proxy.valueOf() returns the wrapped version");
|
||||
assert(/\[object Window.*\]/.test(window.valueOf({}).toString()),
|
||||
"proxy.valueOf({}) returns the wrapped version");
|
||||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
// Bug 787013: Until this bug is fixed, we are missing some methods
|
||||
|
@ -625,7 +565,6 @@ exports["test valueOf"] = createProxyTest("", function (helper) {
|
|||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -744,8 +683,6 @@ exports["test Listeners"] = createProxyTest(html, function (helper) {
|
|||
addEventListenerCalled = true;
|
||||
|
||||
assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert("__isWrappedProxy" in event.target, "event object is a proxy");
|
||||
|
||||
let input2 = document.getElementById("input2");
|
||||
|
||||
|
@ -756,8 +693,6 @@ exports["test Listeners"] = createProxyTest(html, function (helper) {
|
|||
expandoCalled = true;
|
||||
|
||||
assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
assert("__isWrappedProxy" in event.target, "event object is a proxy");
|
||||
|
||||
setTimeout(function () {
|
||||
input.click();
|
||||
|
@ -802,30 +737,6 @@ exports["testGlobalScope"] = createProxyTest("", function (helper) {
|
|||
|
||||
});
|
||||
|
||||
if (USE_JS_PROXIES) {
|
||||
// Bug 671016: Typed arrays should not be proxified
|
||||
exports["test Typed ArraysX"] = createProxyTest("", function (helper) {
|
||||
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
let canvas = document.createElement("canvas");
|
||||
let context = canvas.getContext("2d");
|
||||
let imageData = context.getImageData(0,0, 1, 1);
|
||||
let unwrappedData;
|
||||
if ('UNWRAP_ACCESS_KEY' in window)
|
||||
unwrappedData = imageData.valueOf(UNWRAP_ACCESS_KEY).data
|
||||
else
|
||||
unwrappedData = imageData.wrappedJSObject.data;
|
||||
let data = imageData.data;
|
||||
dump(unwrappedData+" === "+data+"\n");
|
||||
assert(unwrappedData === data, "Typed array isn't proxified")
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Bug 715755: proxy code throw an exception on COW
|
||||
// Create an http server in order to simulate real cross domain documents
|
||||
exports["test Cross Domain Iframe"] = createProxyTest("", function (helper) {
|
|
@ -50,6 +50,16 @@ exports['test:test for each'] = function(test) {
|
|||
}
|
||||
};
|
||||
|
||||
exports['test:test for of'] = function(test) {
|
||||
let fixture = new List(3, 2, 1);
|
||||
|
||||
test.assertEqual(3, fixture.length, 'length is 3');
|
||||
let i = 3;
|
||||
for (let value of fixture) {
|
||||
test.assertEqual(i--, value, 'value should match');
|
||||
}
|
||||
};
|
||||
|
||||
exports['test:test toString'] = function(test) {
|
||||
let fixture = List(3, 2, 1);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
const utils = require('sdk/lang/functional');
|
||||
const { invoke, defer, partial, compose, memoize, once, delay, wrap } = utils;
|
||||
const { invoke, defer, partial, compose, memoize, once, delay, wrap, curry } = utils;
|
||||
const { LoaderWithHookedConsole } = require('sdk/test/loader');
|
||||
|
||||
exports['test forwardApply'] = function(assert) {
|
||||
|
@ -44,20 +44,15 @@ exports['test partial function'] = function(assert) {
|
|||
assert.equal(foo.sum8and4(), 17, 'partial both arguments works');
|
||||
};
|
||||
|
||||
exports['test curry function'] = function(assert) {
|
||||
let { loader, messages } = LoaderWithHookedConsole(module);
|
||||
let { curry } = loader.require('sdk/lang/functional');
|
||||
exports["test curry defined numeber of arguments"] = function(assert) {
|
||||
var sum = curry(function(a, b, c) {
|
||||
return a + b + c;
|
||||
});
|
||||
|
||||
function sum(b, c) this.a + b + c;
|
||||
|
||||
let foo = { a : 5 };
|
||||
|
||||
foo.sum7 = curry(sum, 7);
|
||||
|
||||
assert.equal(messages.length, 1, "only one error is dispatched");
|
||||
assert.ok(messages[0].msg.indexOf('curry is deprecated') > -1);
|
||||
|
||||
loader.unload();
|
||||
assert.equal(sum(2, 2, 1), 5, "sum(2, 2, 1) => 5");
|
||||
assert.equal(sum(2, 4)(1), 7, "sum(2, 4)(1) => 7");
|
||||
assert.equal(sum(2)(4, 2), 8, "sum(2)(4, 2) => 8");
|
||||
assert.equal(sum(2)(4)(3), 9, "sum(2)(4)(3) => 9");
|
||||
};
|
||||
|
||||
exports['test compose'] = function(assert) {
|
||||
|
|
|
@ -21,6 +21,12 @@ exports.testList = function(test) {
|
|||
test.assertEqual(++count, 1, 'count is correct');
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (let ele of list) {
|
||||
test.assertEqual(ele, 1, 'ele is correct');
|
||||
test.assertEqual(++count, 1, 'count is correct');
|
||||
}
|
||||
|
||||
removeListItem(list, 1);
|
||||
test.assertEqual(list.length, 0, 'remove worked');
|
||||
};
|
||||
|
@ -34,9 +40,16 @@ exports.testImplementsList = function(test) {
|
|||
});
|
||||
let list2 = List2();
|
||||
let count = 0;
|
||||
|
||||
for each (let ele in list2) {
|
||||
test.assertEqual(ele, count++, 'ele is correct');
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (let ele of list2) {
|
||||
test.assertEqual(ele, count++, 'ele is correct');
|
||||
}
|
||||
|
||||
addListItem(list2, 3);
|
||||
test.assertEqual(list2.length, 4, '3 was added');
|
||||
test.assertEqual(list2[list2.length-1], 3, '3 was added');
|
||||
|
|
|
@ -1137,3 +1137,29 @@ exports["test page-mod on private tab in global pb"] = function (test) {
|
|||
});
|
||||
pb.activate();
|
||||
}
|
||||
|
||||
// Bug 699450: Calling worker.tab.close() should not lead to exception
|
||||
exports.testWorkerTabClose = function(test) {
|
||||
let callbackDone;
|
||||
testPageMod(test, "about:", [{
|
||||
include: "about:",
|
||||
contentScript: '',
|
||||
onAttach: function(worker) {
|
||||
console.log("call close");
|
||||
worker.tab.close(function () {
|
||||
// On Fennec, tab is completely destroyed right after close event is
|
||||
// dispatch, so we need to wait for the next event loop cycle to
|
||||
// check for tab nulliness.
|
||||
timer.setTimeout(function () {
|
||||
test.assert(!worker.tab,
|
||||
"worker.tab should be null right after tab.close()");
|
||||
callbackDone();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}],
|
||||
function(win, done) {
|
||||
callbackDone = done;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,23 +3,41 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict'
|
||||
|
||||
const xhr = require('sdk/net/xhr');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const xulApp = require('sdk/system/xul-app');
|
||||
const { XMLHttpRequest } = require('sdk/net/xhr');
|
||||
const { LoaderWithHookedConsole } = require('sdk/test/loader');
|
||||
const { data } = require('sdk/self');
|
||||
|
||||
// TODO: rewrite test below
|
||||
/* Test is intentionally disabled until platform bug 707256 is fixed.
|
||||
exports.testAbortedXhr = function(test) {
|
||||
var req = new xhr.XMLHttpRequest();
|
||||
test.assertEqual(xhr.getRequestCount(), 1);
|
||||
req.abort();
|
||||
test.assertEqual(xhr.getRequestCount(), 0);
|
||||
|
||||
exports.testAPIExtension = function(assert) {
|
||||
let { loader, messages } = LoaderWithHookedConsole(module);
|
||||
let { XMLHttpRequest } = loader.require("sdk/net/xhr");
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
assert.equal(typeof(xhr.forceAllowThirdPartyCookie), "function",
|
||||
"forceAllowThirdPartyCookie is defined");
|
||||
assert.equal(xhr.forceAllowThirdPartyCookie(), undefined,
|
||||
"function can be called");
|
||||
|
||||
assert.ok(messages[0].msg.indexOf("`xhr.forceAllowThirdPartyCookie()` is deprecated") >= 0,
|
||||
"deprecation warning was dumped");
|
||||
assert.ok(xhr.mozBackgroundRequest, "is background request");
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports.testAbortedXhr = function(assert, done) {
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('GET', data.url('testLocalXhr.json'));
|
||||
req.addEventListener("abort", function() {
|
||||
assert.pass("request was aborted");
|
||||
done();
|
||||
});
|
||||
req.send(null);
|
||||
req.abort();
|
||||
};
|
||||
*/
|
||||
|
||||
exports.testLocalXhr = function(assert, done) {
|
||||
var req = new xhr.XMLHttpRequest();
|
||||
let req = new XMLHttpRequest();
|
||||
let ready = false;
|
||||
|
||||
req.overrideMimeType('text/plain');
|
||||
|
@ -34,59 +52,35 @@ exports.testLocalXhr = function(assert, done) {
|
|||
req.removeEventListener('load', onload);
|
||||
assert.pass('addEventListener for load event worked');
|
||||
assert.ok(ready, 'onreadystatechange listener worked');
|
||||
assert.equal(xhr.getRequestCount(), 0, 'request count is 0');
|
||||
done();
|
||||
});
|
||||
req.send(null);
|
||||
|
||||
assert.equal(xhr.getRequestCount(), 1, 'request count is 1');
|
||||
};
|
||||
|
||||
exports.testUnload = function(assert) {
|
||||
var loader = Loader(module);
|
||||
var sbxhr = loader.require('sdk/net/xhr');
|
||||
var req = new sbxhr.XMLHttpRequest();
|
||||
|
||||
req.overrideMimeType('text/plain');
|
||||
req.open("GET", module.uri);
|
||||
req.send(null);
|
||||
|
||||
assert.equal(sbxhr.getRequestCount(), 1, 'request count is 1');
|
||||
loader.unload();
|
||||
assert.equal(sbxhr.getRequestCount(), 0, 'request count is 0');
|
||||
};
|
||||
|
||||
exports.testResponseHeaders = function(assert, done) {
|
||||
var req = new xhr.XMLHttpRequest();
|
||||
let req = new XMLHttpRequest();
|
||||
|
||||
req.overrideMimeType('text/plain');
|
||||
req.open('GET', module.uri);
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState == 4 && (req.status == 0 || req.status == 200)) {
|
||||
var headers = req.getAllResponseHeaders();
|
||||
if (xulApp.satisfiesVersion(xulApp.platformVersion, '>=13.0a1')) {
|
||||
headers = headers.split("\r\n");
|
||||
if (headers.length == 1) {
|
||||
headers = headers[0].split("\n");
|
||||
}
|
||||
for (let i in headers) {
|
||||
if (headers[i] && headers[i].search('Content-Type') >= 0) {
|
||||
assert.equal(headers[i], 'Content-Type: text/plain',
|
||||
'XHR\'s headers are valid');
|
||||
}
|
||||
}
|
||||
headers = headers.split("\r\n");
|
||||
if (headers.length == 1) {
|
||||
headers = headers[0].split("\n");
|
||||
}
|
||||
else {
|
||||
assert.ok(headers === null || headers === '',
|
||||
'XHR\'s headers are empty');
|
||||
for (let i in headers) {
|
||||
if (headers[i] && headers[i].search('Content-Type') >= 0) {
|
||||
assert.equal(headers[i], 'Content-Type: text/plain',
|
||||
'XHR\'s headers are valid');
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
};
|
||||
req.send(null);
|
||||
|
||||
assert.equal(xhr.getRequestCount(), 1, 'request count is 1');
|
||||
}
|
||||
|
||||
require('test').run(exports);
|
||||
|
|
Загрузка…
Ссылка в новой задаче