Bug 880558 - Uplift addon-sdk to firefox r=me

This commit is contained in:
Wes Kocher 2013-06-07 09:43:59 -07:00
Родитель 207b857630
Коммит 6ca3643cbc
31 изменённых файлов: 740 добавлений и 1305 удалений

Просмотреть файл

@ -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);