mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 960771 - Uplift Add-on SDK
This commit is contained in:
parent
1e38ac2acc
commit
e1390cbc10
@ -8,6 +8,6 @@ module.metadata = {
|
||||
};
|
||||
|
||||
exports.Loader = require('./loader').Loader;
|
||||
exports.Symbiont = require('./symbiont').Symbiont;
|
||||
exports.Symbiont = require('../deprecated/symbiont').Symbiont;
|
||||
exports.Worker = require('./worker').Worker;
|
||||
|
||||
|
404
addon-sdk/source/lib/sdk/content/sandbox.js
Normal file
404
addon-sdk/source/lib/sdk/content/sandbox.js
Normal file
@ -0,0 +1,404 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
const { Class } = require('../core/heritage');
|
||||
const { EventTarget } = require('../event/target');
|
||||
const { on, off, emit } = require('../event/core');
|
||||
const {
|
||||
requiresAddonGlobal,
|
||||
attach, detach, destroy
|
||||
} = require('./utils');
|
||||
const { delay: async } = require('../lang/functional');
|
||||
const { Ci, Cu, Cc } = require('chrome');
|
||||
const timer = require('../timers');
|
||||
const { URL } = require('../url');
|
||||
const { sandbox, evaluate, load } = require('../loader/sandbox');
|
||||
const { merge } = require('../util/object');
|
||||
const xulApp = require('../system/xul-app');
|
||||
const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
|
||||
'17.0a2', '*');
|
||||
const { getTabForContentWindow } = require('../tabs/utils');
|
||||
|
||||
// WeakMap of sandboxes so we can access private values
|
||||
const sandboxes = new WeakMap();
|
||||
|
||||
/* Trick the linker in order to ensure shipping these files in the XPI.
|
||||
require('./content-worker.js');
|
||||
Then, retrieve URL of these files in the XPI:
|
||||
*/
|
||||
let prefix = module.uri.split('sandbox.js')[0];
|
||||
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 WorkerSandbox = Class({
|
||||
|
||||
implements: [
|
||||
EventTarget
|
||||
],
|
||||
|
||||
/**
|
||||
* Emit a message to the worker content sandbox
|
||||
*/
|
||||
emit: function emit(...args) {
|
||||
// Ensure having an asynchronous behavior
|
||||
let self = this;
|
||||
async(function () {
|
||||
emitToContent(self, JSON.stringify(args, replacer));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronous version of `emit`.
|
||||
* /!\ Should only be used when it is strictly mandatory /!\
|
||||
* Doesn't ensure passing only JSON values.
|
||||
* Mainly used by context-menu in order to avoid breaking it.
|
||||
*/
|
||||
emitSync: function emitSync(...args) {
|
||||
return emitToContent(this, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells if content script has at least one listener registered for one event,
|
||||
* through `self.on('xxx', ...)`.
|
||||
* /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
|
||||
*/
|
||||
hasListenerFor: function hasListenerFor(name) {
|
||||
return modelFor(this).hasListenerFor(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures sandbox and loads content scripts into it.
|
||||
* @param {Worker} worker
|
||||
* content worker
|
||||
*/
|
||||
initialize: function WorkerSandbox(worker, window) {
|
||||
let model = {};
|
||||
sandboxes.set(this, model);
|
||||
model.worker = worker;
|
||||
// We receive a wrapped window, that may be an xraywrapper if it's content
|
||||
let proto = window;
|
||||
|
||||
// TODO necessary?
|
||||
// Ensure that `emit` has always the right `this`
|
||||
this.emit = this.emit.bind(this);
|
||||
this.emitSync = this.emitSync.bind(this);
|
||||
|
||||
// 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 wantGlobalProperties = [];
|
||||
if (EXPANDED_PRINCIPALS.length > 0 && !requiresAddonGlobal(worker)) {
|
||||
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;
|
||||
wantGlobalProperties.push('XMLHttpRequest');
|
||||
}
|
||||
|
||||
// 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(principals, { wantXrays: true, sameZoneAs: window });
|
||||
apiSandbox.console = console;
|
||||
|
||||
// Create the sandbox and bind it to window in order for content scripts to
|
||||
// have access to all standard globals (window, document, ...)
|
||||
let content = sandbox(principals, {
|
||||
sandboxPrototype: proto,
|
||||
wantXrays: true,
|
||||
wantGlobalProperties: wantGlobalProperties,
|
||||
sameZoneAs: window,
|
||||
metadata: { SDKContentScript: true }
|
||||
});
|
||||
model.sandbox = content;
|
||||
|
||||
// We have to ensure that window.top and window.parent are the exact same
|
||||
// object than window object, i.e. the sandbox global object. But not
|
||||
// always, in case of iframes, top and parent are another window object.
|
||||
let top = window.top === window ? content : content.top;
|
||||
let parent = window.parent === window ? content : content.parent;
|
||||
merge(content, {
|
||||
// We need 'this === window === top' to be true in toplevel scope:
|
||||
get window() content,
|
||||
get top() top,
|
||||
get parent() parent,
|
||||
// Use the Greasemonkey naming convention to provide access to the
|
||||
// unwrapped window object so the content script can access document
|
||||
// JavaScript values.
|
||||
// NOTE: this functionality is experimental and may change or go away
|
||||
// at any time!
|
||||
get unsafeWindow() window.wrappedJSObject
|
||||
});
|
||||
|
||||
// Load trusted code that will inject content script API.
|
||||
// We need to expose JS objects defined in same principal in order to
|
||||
// avoid having any kind of wrapper.
|
||||
load(apiSandbox, CONTENT_WORKER_URL);
|
||||
|
||||
// prepare a clean `self.options`
|
||||
let options = 'contentScriptOptions' in worker ?
|
||||
JSON.stringify(worker.contentScriptOptions) :
|
||||
undefined;
|
||||
|
||||
// Then call `inject` method and communicate with this script
|
||||
// by trading two methods that allow to send events to the other side:
|
||||
// - `onEvent` called by content script
|
||||
// - `result.emitToContent` called by addon script
|
||||
// Bug 758203: We have to explicitely define `__exposedProps__` in order
|
||||
// to allow access to these chrome object attributes from this sandbox with
|
||||
// content priviledges
|
||||
// https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
|
||||
let onEvent = onContentEvent.bind(null, this);
|
||||
// `ContentWorker` is defined in CONTENT_WORKER_URL file
|
||||
let chromeAPI = createChromeAPI();
|
||||
let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
|
||||
|
||||
// Merge `emitToContent` and `hasListenerFor` into our private
|
||||
// model of the WorkerSandbox so we can communicate with content
|
||||
// script
|
||||
merge(model, result);
|
||||
|
||||
// Handle messages send by this script:
|
||||
setListeners(this);
|
||||
|
||||
// Inject `addon` global into target document if document is trusted,
|
||||
// `addon` in document is equivalent to `self` in content script.
|
||||
if (requiresAddonGlobal(worker)) {
|
||||
Object.defineProperty(getUnsafeWindow(window), 'addon', {
|
||||
value: content.self
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Inject our `console` into target document if worker doesn't have a tab
|
||||
// (e.g Panel, PageWorker, Widget).
|
||||
// `worker.tab` can't be used because bug 804935.
|
||||
if (!getTabForContentWindow(window)) {
|
||||
let win = getUnsafeWindow(window);
|
||||
|
||||
// export our chrome console to content window, using the same approach
|
||||
// of `ConsoleAPI`:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
|
||||
//
|
||||
// and described here:
|
||||
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
|
||||
let con = Cu.createObjectIn(win);
|
||||
|
||||
let genPropDesc = function genPropDesc(fun) {
|
||||
return { enumerable: true, configurable: true, writable: true,
|
||||
value: console[fun] };
|
||||
}
|
||||
|
||||
const properties = {
|
||||
log: genPropDesc('log'),
|
||||
info: genPropDesc('info'),
|
||||
warn: genPropDesc('warn'),
|
||||
error: genPropDesc('error'),
|
||||
debug: genPropDesc('debug'),
|
||||
trace: genPropDesc('trace'),
|
||||
dir: genPropDesc('dir'),
|
||||
group: genPropDesc('group'),
|
||||
groupCollapsed: genPropDesc('groupCollapsed'),
|
||||
groupEnd: genPropDesc('groupEnd'),
|
||||
time: genPropDesc('time'),
|
||||
timeEnd: genPropDesc('timeEnd'),
|
||||
profile: genPropDesc('profile'),
|
||||
profileEnd: genPropDesc('profileEnd'),
|
||||
__noSuchMethod__: { enumerable: true, configurable: true, writable: true,
|
||||
value: function() {} }
|
||||
};
|
||||
|
||||
Object.defineProperties(con, properties);
|
||||
Cu.makeObjectPropsNormal(con);
|
||||
|
||||
win.console = con;
|
||||
};
|
||||
|
||||
// The order of `contentScriptFile` and `contentScript` evaluation is
|
||||
// intentional, so programs can load libraries like jQuery from script URLs
|
||||
// and use them in scripts.
|
||||
let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
|
||||
: null,
|
||||
contentScript = ('contentScript' in worker) ? worker.contentScript : null;
|
||||
|
||||
if (contentScriptFile)
|
||||
importScripts.apply(null, [this].concat(contentScriptFile));
|
||||
if (contentScript) {
|
||||
evaluateIn(
|
||||
this,
|
||||
Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
|
||||
);
|
||||
}
|
||||
},
|
||||
destroy: function destroy() {
|
||||
this.emitSync('detach');
|
||||
let model = modelFor(this);
|
||||
model.sandbox = null
|
||||
model.worker = null;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
exports.WorkerSandbox = WorkerSandbox;
|
||||
|
||||
/**
|
||||
* Imports scripts to the sandbox by reading files under urls and
|
||||
* evaluating its source. If exception occurs during evaluation
|
||||
* `'error'` event is emitted on the worker.
|
||||
* This is actually an analog to the `importScript` method in web
|
||||
* workers but in our case it's not exposed even though content
|
||||
* scripts may be able to do it synchronously since IO operation
|
||||
* takes place in the UI process.
|
||||
*/
|
||||
function importScripts (workerSandbox, ...urls) {
|
||||
let { worker, sandbox } = modelFor(workerSandbox);
|
||||
for (let i in urls) {
|
||||
let contentScriptFile = urls[i];
|
||||
try {
|
||||
let uri = URL(contentScriptFile);
|
||||
if (uri.scheme === 'resource')
|
||||
load(sandbox, String(uri));
|
||||
else
|
||||
throw Error('Unsupported `contentScriptFile` url: ' + String(uri));
|
||||
}
|
||||
catch(e) {
|
||||
emit(worker, 'error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setListeners (workerSandbox) {
|
||||
let { worker } = modelFor(workerSandbox);
|
||||
// console.xxx calls
|
||||
workerSandbox.on('console', function consoleListener (kind, ...args) {
|
||||
console[kind].apply(console, args);
|
||||
});
|
||||
|
||||
// self.postMessage calls
|
||||
workerSandbox.on('message', function postMessage(data) {
|
||||
// destroyed?
|
||||
if (worker)
|
||||
emit(worker, 'message', data);
|
||||
});
|
||||
|
||||
// self.port.emit calls
|
||||
workerSandbox.on('event', function portEmit (...eventArgs) {
|
||||
// If not destroyed, emit event information to worker
|
||||
// `eventArgs` has the event name as first element,
|
||||
// and remaining elements are additional arguments to pass
|
||||
if (worker)
|
||||
emit.apply(null, [worker.port].concat(eventArgs));
|
||||
});
|
||||
|
||||
// unwrap, recreate and propagate async Errors thrown from content-script
|
||||
workerSandbox.on('error', function onError({instanceOfError, value}) {
|
||||
if (worker) {
|
||||
let error = value;
|
||||
if (instanceOfError) {
|
||||
error = new Error(value.message, value.fileName, value.lineNumber);
|
||||
error.stack = value.stack;
|
||||
error.name = value.name;
|
||||
}
|
||||
emit(worker, 'error', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates code in the sandbox.
|
||||
* @param {String} code
|
||||
* JavaScript source to evaluate.
|
||||
* @param {String} [filename='javascript:' + code]
|
||||
* Name of the file
|
||||
*/
|
||||
function evaluateIn (workerSandbox, code, filename) {
|
||||
let { worker, sandbox } = modelFor(workerSandbox);
|
||||
try {
|
||||
evaluate(sandbox, code, filename || 'javascript:' + code);
|
||||
}
|
||||
catch(e) {
|
||||
emit(worker, 'error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by the worker sandbox when it needs to send a message
|
||||
*/
|
||||
function onContentEvent (workerSandbox, args) {
|
||||
// As `emit`, we ensure having an asynchronous behavior
|
||||
async(function () {
|
||||
// We emit event to chrome/addon listeners
|
||||
emit.apply(null, [workerSandbox].concat(JSON.parse(args)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function modelFor (workerSandbox) {
|
||||
return sandboxes.get(workerSandbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON.stringify is buggy with cross-sandbox values,
|
||||
* it may return '{}' on functions. Use a replacer to match them correctly.
|
||||
*/
|
||||
function replacer (k, v) {
|
||||
return typeof v === 'function' ? undefined : v;
|
||||
}
|
||||
|
||||
function getUnsafeWindow (win) {
|
||||
return win.wrappedJSObject || win;
|
||||
}
|
||||
|
||||
function emitToContent (workerSandbox, args) {
|
||||
return modelFor(workerSandbox).emitToContent(args);
|
||||
}
|
||||
|
||||
function createChromeAPI () {
|
||||
return {
|
||||
timers: {
|
||||
setTimeout: timer.setTimeout,
|
||||
setInterval: timer.setInterval,
|
||||
clearTimeout: timer.clearTimeout,
|
||||
clearInterval: timer.clearInterval,
|
||||
__exposedProps__: {
|
||||
setTimeout: 'r',
|
||||
setInterval: 'r',
|
||||
clearTimeout: 'r',
|
||||
clearInterval: 'r'
|
||||
},
|
||||
},
|
||||
sandbox: {
|
||||
evaluate: evaluate,
|
||||
__exposedProps__: {
|
||||
evaluate: 'r'
|
||||
}
|
||||
},
|
||||
__exposedProps__: {
|
||||
timers: 'r',
|
||||
sandbox: 'r'
|
||||
}
|
||||
};
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
/* 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";
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
let assetsURI = require("../self").data.url();
|
||||
let { merge } = require('../util/object');
|
||||
let assetsURI = require('../self').data.url();
|
||||
let isArray = Array.isArray;
|
||||
let method = require('method/core');
|
||||
|
||||
function isAddonContent({ contentURL }) {
|
||||
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
|
||||
return typeof(contentURL) === 'string' && contentURL.indexOf(assetsURI) === 0;
|
||||
}
|
||||
exports.isAddonContent = isAddonContent;
|
||||
|
||||
@ -24,18 +26,57 @@ function hasContentScript({ contentScript, contentScriptFile }) {
|
||||
exports.hasContentScript = hasContentScript;
|
||||
|
||||
function requiresAddonGlobal(model) {
|
||||
return isAddonContent(model) && !hasContentScript(model);
|
||||
return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model));
|
||||
}
|
||||
exports.requiresAddonGlobal = requiresAddonGlobal;
|
||||
|
||||
function getAttachEventType(model) {
|
||||
if (!model) return null;
|
||||
let when = model.contentScriptWhen;
|
||||
return requiresAddonGlobal(model) ? "document-element-inserted" :
|
||||
when === "start" ? "document-element-inserted" :
|
||||
when === "end" ? "load" :
|
||||
when === "ready" ? "DOMContentLoaded" :
|
||||
return requiresAddonGlobal(model) ? 'document-element-inserted' :
|
||||
when === 'start' ? 'document-element-inserted' :
|
||||
when === 'end' ? 'load' :
|
||||
when === 'ready' ? 'DOMContentLoaded' :
|
||||
null;
|
||||
}
|
||||
exports.getAttachEventType = getAttachEventType;
|
||||
|
||||
let attach = method('worker-attach');
|
||||
exports.attach = attach;
|
||||
|
||||
let detach = method('worker-detach');
|
||||
exports.detach = detach;
|
||||
|
||||
let destroy = method('worker-destroy');
|
||||
exports.destroy = destroy;
|
||||
|
||||
function WorkerHost (workerFor) {
|
||||
// Define worker properties that just proxy to underlying worker
|
||||
return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) {
|
||||
// Use descriptor properties instead so we can call
|
||||
// the worker function in the context of the worker so we
|
||||
// don't have to create new functions with `fn.bind(worker)`
|
||||
let descriptorProp = {
|
||||
value: function (...args) {
|
||||
let worker = workerFor(this);
|
||||
return worker[name].apply(worker, args);
|
||||
}
|
||||
};
|
||||
|
||||
let accessorProp = {
|
||||
get: function () { return workerFor(this)[name]; },
|
||||
set: function (value) { workerFor(this)[name] = value; }
|
||||
};
|
||||
|
||||
Object.defineProperty(proto, name, merge({
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
}, isDescriptor(name) ? descriptorProp : accessorProp));
|
||||
return proto;
|
||||
}, {});
|
||||
|
||||
function isDescriptor (prop) {
|
||||
return ~['postMessage'].indexOf(prop);
|
||||
}
|
||||
}
|
||||
exports.WorkerHost = WorkerHost;
|
||||
|
@ -7,38 +7,25 @@ module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Trait } = require('../deprecated/traits');
|
||||
const { EventEmitter, EventEmitterTrait } = require('../deprecated/events');
|
||||
const { Class } = require('../core/heritage');
|
||||
const { EventTarget } = require('../event/target');
|
||||
const { on, off, emit, setListeners } = require('../event/core');
|
||||
const {
|
||||
attach, detach, destroy
|
||||
} = require('./utils');
|
||||
const { method } = require('../lang/functional');
|
||||
const { Ci, Cu, Cc } = require('chrome');
|
||||
const timer = require('../timers');
|
||||
const { URL } = require('../url');
|
||||
const unload = require('../system/unload');
|
||||
const observers = require('../deprecated/observer-service');
|
||||
const { Cortex } = require('../deprecated/cortex');
|
||||
const { sandbox, evaluate, load } = require("../loader/sandbox");
|
||||
const { merge } = require('../util/object');
|
||||
const xulApp = require("../system/xul-app");
|
||||
const { getInnerId } = require("../window/utils")
|
||||
const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
|
||||
"17.0a2", "*");
|
||||
const events = require('../system/events');
|
||||
const { getInnerId } = require("../window/utils");
|
||||
const { WorkerSandbox } = require('./sandbox');
|
||||
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-worker.js');
|
||||
Then, retrieve URL of these files in the XPI:
|
||||
*/
|
||||
let prefix = module.uri.split('worker.js')[0];
|
||||
const CONTENT_WORKER_URL = prefix + 'content-worker.js';
|
||||
// A weak map of workers to hold private attributes that
|
||||
// should not be exposed
|
||||
const workers = new WeakMap();
|
||||
|
||||
// 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';
|
||||
let modelFor = (worker) => workers.get(worker);
|
||||
|
||||
const ERR_DESTROYED =
|
||||
"Couldn't find the worker to receive this message. " +
|
||||
@ -48,357 +35,43 @@ const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
|
||||
"until it is visible again.";
|
||||
|
||||
|
||||
const WorkerSandbox = EventEmitter.compose({
|
||||
|
||||
/**
|
||||
* Emit a message to the worker content sandbox
|
||||
*/
|
||||
emit: function emit() {
|
||||
// First ensure having a regular array
|
||||
// (otherwise, `arguments` would be mapped to an object by `stringify`)
|
||||
let array = Array.slice(arguments);
|
||||
// JSON.stringify is buggy with cross-sandbox values,
|
||||
// it may return "{}" on functions. Use a replacer to match them correctly.
|
||||
function replacer(k, v) {
|
||||
return typeof v === "function" ? undefined : v;
|
||||
}
|
||||
// Ensure having an asynchronous behavior
|
||||
let self = this;
|
||||
timer.setTimeout(function () {
|
||||
self._emitToContent(JSON.stringify(array, replacer));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronous version of `emit`.
|
||||
* /!\ Should only be used when it is strictly mandatory /!\
|
||||
* Doesn't ensure passing only JSON values.
|
||||
* Mainly used by context-menu in order to avoid breaking it.
|
||||
*/
|
||||
emitSync: function emitSync() {
|
||||
let args = Array.slice(arguments);
|
||||
return this._emitToContent(args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells if content script has at least one listener registered for one event,
|
||||
* through `self.on('xxx', ...)`.
|
||||
* /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
|
||||
*/
|
||||
hasListenerFor: function hasListenerFor(name) {
|
||||
return this._hasListenerFor(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method called by the worker sandbox when it needs to send a message
|
||||
*/
|
||||
_onContentEvent: function onContentEvent(args) {
|
||||
// As `emit`, we ensure having an asynchronous behavior
|
||||
let self = this;
|
||||
timer.setTimeout(function () {
|
||||
// We emit event to chrome/addon listeners
|
||||
self._emit.apply(self, JSON.parse(args));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures sandbox and loads content scripts into it.
|
||||
* @param {Worker} worker
|
||||
* content worker
|
||||
*/
|
||||
constructor: function WorkerSandbox(worker) {
|
||||
this._addonWorker = worker;
|
||||
|
||||
// Ensure that `emit` has always the right `this`
|
||||
this.emit = this.emit.bind(this);
|
||||
this.emitSync = this.emitSync.bind(this);
|
||||
|
||||
// We receive a wrapped window, that may be an xraywrapper if it's content
|
||||
let window = worker._window;
|
||||
let proto = window;
|
||||
|
||||
// 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 wantGlobalProperties = []
|
||||
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;
|
||||
wantGlobalProperties.push("XMLHttpRequest");
|
||||
}
|
||||
|
||||
// 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(principals, { wantXrays: true, sameZoneAs: window });
|
||||
apiSandbox.console = console;
|
||||
|
||||
// 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(principals, {
|
||||
sandboxPrototype: proto,
|
||||
wantXrays: true,
|
||||
wantGlobalProperties: wantGlobalProperties,
|
||||
sameZoneAs: window,
|
||||
metadata: { SDKContentScript: true }
|
||||
});
|
||||
// We have to ensure that window.top and window.parent are the exact same
|
||||
// object than window object, i.e. the sandbox global object. But not
|
||||
// always, in case of iframes, top and parent are another window object.
|
||||
let top = window.top === window ? content : content.top;
|
||||
let parent = window.parent === window ? content : content.parent;
|
||||
merge(content, {
|
||||
// We need "this === window === top" to be true in toplevel scope:
|
||||
get window() content,
|
||||
get top() top,
|
||||
get parent() parent,
|
||||
// Use the Greasemonkey naming convention to provide access to the
|
||||
// unwrapped window object so the content script can access document
|
||||
// JavaScript values.
|
||||
// NOTE: this functionality is experimental and may change or go away
|
||||
// at any time!
|
||||
get unsafeWindow() window.wrappedJSObject
|
||||
});
|
||||
|
||||
// Load trusted code that will inject content script API.
|
||||
// We need to expose JS objects defined in same principal in order to
|
||||
// avoid having any kind of wrapper.
|
||||
load(apiSandbox, CONTENT_WORKER_URL);
|
||||
|
||||
// prepare a clean `self.options`
|
||||
let options = 'contentScriptOptions' in worker ?
|
||||
JSON.stringify( worker.contentScriptOptions ) :
|
||||
undefined;
|
||||
|
||||
// Then call `inject` method and communicate with this script
|
||||
// by trading two methods that allow to send events to the other side:
|
||||
// - `onEvent` called by content script
|
||||
// - `result.emitToContent` called by addon script
|
||||
// Bug 758203: We have to explicitely define `__exposedProps__` in order
|
||||
// to allow access to these chrome object attributes from this sandbox with
|
||||
// content priviledges
|
||||
// https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
|
||||
let chromeAPI = {
|
||||
timers: {
|
||||
setTimeout: timer.setTimeout,
|
||||
setInterval: timer.setInterval,
|
||||
clearTimeout: timer.clearTimeout,
|
||||
clearInterval: timer.clearInterval,
|
||||
__exposedProps__: {
|
||||
setTimeout: 'r',
|
||||
setInterval: 'r',
|
||||
clearTimeout: 'r',
|
||||
clearInterval: 'r'
|
||||
}
|
||||
},
|
||||
sandbox: {
|
||||
evaluate: evaluate,
|
||||
__exposedProps__: {
|
||||
evaluate: 'r',
|
||||
}
|
||||
},
|
||||
__exposedProps__: {
|
||||
timers: 'r',
|
||||
sandbox: 'r',
|
||||
}
|
||||
};
|
||||
let onEvent = this._onContentEvent.bind(this);
|
||||
// `ContentWorker` is defined in CONTENT_WORKER_URL file
|
||||
let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
|
||||
this._emitToContent = result.emitToContent;
|
||||
this._hasListenerFor = result.hasListenerFor;
|
||||
|
||||
// Handle messages send by this script:
|
||||
let self = this;
|
||||
// console.xxx calls
|
||||
this.on("console", function consoleListener(kind) {
|
||||
console[kind].apply(console, Array.slice(arguments, 1));
|
||||
});
|
||||
|
||||
// self.postMessage calls
|
||||
this.on("message", function postMessage(data) {
|
||||
// destroyed?
|
||||
if (self._addonWorker)
|
||||
self._addonWorker._emit('message', data);
|
||||
});
|
||||
|
||||
// self.port.emit calls
|
||||
this.on("event", function portEmit(name, args) {
|
||||
// destroyed?
|
||||
if (self._addonWorker)
|
||||
self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
|
||||
});
|
||||
|
||||
// unwrap, recreate and propagate async Errors thrown from content-script
|
||||
this.on("error", function onError({instanceOfError, value}) {
|
||||
if (self._addonWorker) {
|
||||
let error = value;
|
||||
if (instanceOfError) {
|
||||
error = new Error(value.message, value.fileName, value.lineNumber);
|
||||
error.stack = value.stack;
|
||||
error.name = value.name;
|
||||
}
|
||||
self._addonWorker._emit('error', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Inject `addon` global into target document if document is trusted,
|
||||
// `addon` in document is equivalent to `self` in content script.
|
||||
if (worker._injectInDocument) {
|
||||
let win = window.wrappedJSObject ? window.wrappedJSObject : window;
|
||||
Object.defineProperty(win, "addon", {
|
||||
value: content.self
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Inject our `console` into target document if worker doesn't have a tab
|
||||
// (e.g Panel, PageWorker, Widget).
|
||||
// `worker.tab` can't be used because bug 804935.
|
||||
if (!getTabForContentWindow(window)) {
|
||||
let win = window.wrappedJSObject ? window.wrappedJSObject : window;
|
||||
|
||||
// export our chrome console to content window, using the same approach
|
||||
// of `ConsoleAPI`:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
|
||||
//
|
||||
// and described here:
|
||||
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
|
||||
let con = Cu.createObjectIn(win);
|
||||
|
||||
let genPropDesc = function genPropDesc(fun) {
|
||||
return { enumerable: true, configurable: true, writable: true,
|
||||
value: console[fun] };
|
||||
}
|
||||
|
||||
const properties = {
|
||||
log: genPropDesc('log'),
|
||||
info: genPropDesc('info'),
|
||||
warn: genPropDesc('warn'),
|
||||
error: genPropDesc('error'),
|
||||
debug: genPropDesc('debug'),
|
||||
trace: genPropDesc('trace'),
|
||||
dir: genPropDesc('dir'),
|
||||
group: genPropDesc('group'),
|
||||
groupCollapsed: genPropDesc('groupCollapsed'),
|
||||
groupEnd: genPropDesc('groupEnd'),
|
||||
time: genPropDesc('time'),
|
||||
timeEnd: genPropDesc('timeEnd'),
|
||||
profile: genPropDesc('profile'),
|
||||
profileEnd: genPropDesc('profileEnd'),
|
||||
__noSuchMethod__: { enumerable: true, configurable: true, writable: true,
|
||||
value: function() {} }
|
||||
};
|
||||
|
||||
Object.defineProperties(con, properties);
|
||||
Cu.makeObjectPropsNormal(con);
|
||||
|
||||
win.console = con;
|
||||
};
|
||||
|
||||
// The order of `contentScriptFile` and `contentScript` evaluation is
|
||||
// intentional, so programs can load libraries like jQuery from script URLs
|
||||
// and use them in scripts.
|
||||
let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
|
||||
: null,
|
||||
contentScript = ('contentScript' in worker) ? worker.contentScript : null;
|
||||
|
||||
if (contentScriptFile) {
|
||||
if (Array.isArray(contentScriptFile))
|
||||
this._importScripts.apply(this, contentScriptFile);
|
||||
else
|
||||
this._importScripts(contentScriptFile);
|
||||
}
|
||||
if (contentScript) {
|
||||
this._evaluate(
|
||||
Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
|
||||
);
|
||||
}
|
||||
},
|
||||
destroy: function destroy() {
|
||||
this.emitSync("detach");
|
||||
this._sandbox = null;
|
||||
this._addonWorker = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* JavaScript sandbox where all the content scripts are evaluated.
|
||||
* {Sandbox}
|
||||
*/
|
||||
_sandbox: null,
|
||||
|
||||
/**
|
||||
* Reference to the addon side of the worker.
|
||||
* @type {Worker}
|
||||
*/
|
||||
_addonWorker: null,
|
||||
|
||||
/**
|
||||
* Evaluates code in the sandbox.
|
||||
* @param {String} code
|
||||
* JavaScript source to evaluate.
|
||||
* @param {String} [filename='javascript:' + code]
|
||||
* Name of the file
|
||||
*/
|
||||
_evaluate: function(code, filename) {
|
||||
try {
|
||||
evaluate(this._sandbox, code, filename || 'javascript:' + code);
|
||||
}
|
||||
catch(e) {
|
||||
this._addonWorker._emit('error', e);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Imports scripts to the sandbox by reading files under urls and
|
||||
* evaluating its source. If exception occurs during evaluation
|
||||
* `"error"` event is emitted on the worker.
|
||||
* This is actually an analog to the `importScript` method in web
|
||||
* workers but in our case it's not exposed even though content
|
||||
* scripts may be able to do it synchronously since IO operation
|
||||
* takes place in the UI process.
|
||||
*/
|
||||
_importScripts: function _importScripts(url) {
|
||||
let urls = Array.slice(arguments, 0);
|
||||
for each (let contentScriptFile in urls) {
|
||||
try {
|
||||
let uri = URL(contentScriptFile);
|
||||
if (uri.scheme === 'resource')
|
||||
load(this._sandbox, String(uri));
|
||||
else
|
||||
throw Error("Unsupported `contentScriptFile` url: " + String(uri));
|
||||
}
|
||||
catch(e) {
|
||||
this._addonWorker._emit('error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Message-passing facility for communication between code running
|
||||
* in the content and add-on process.
|
||||
* @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html
|
||||
*/
|
||||
const Worker = EventEmitter.compose({
|
||||
on: Trait.required,
|
||||
_removeAllListeners: Trait.required,
|
||||
const Worker = Class({
|
||||
implements: [EventTarget],
|
||||
initialize: function WorkerConstructor (options) {
|
||||
// Save model in weak map to not expose properties
|
||||
let model = createModel();
|
||||
workers.set(this, model);
|
||||
|
||||
// List of messages fired before worker is initialized
|
||||
get _earlyEvents() {
|
||||
delete this._earlyEvents;
|
||||
this._earlyEvents = [];
|
||||
return this._earlyEvents;
|
||||
options = options || {};
|
||||
|
||||
if ('contentScriptFile' in options)
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
if ('contentScriptOptions' in options)
|
||||
this.contentScriptOptions = options.contentScriptOptions;
|
||||
if ('contentScript' in options)
|
||||
this.contentScript = options.contentScript;
|
||||
if ('injectInDocument' in options)
|
||||
this.injectInDocument = !!options.injectInDocument;
|
||||
|
||||
setListeners(this, options);
|
||||
|
||||
unload.ensure(this, "destroy");
|
||||
|
||||
// Ensure that worker.port is initialized for contentWorker to be able
|
||||
// to send events during worker initialization.
|
||||
this.port = createPort(this);
|
||||
|
||||
model.documentUnload = documentUnload.bind(this);
|
||||
model.pageShow = pageShow.bind(this);
|
||||
model.pageHide = pageHide.bind(this);
|
||||
|
||||
if ('window' in options)
|
||||
attach(this, options.window);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -413,239 +86,197 @@ const Worker = EventEmitter.compose({
|
||||
* implementing `onMessage` function in the global scope of this worker.
|
||||
* @param {Number|String|JSON} data
|
||||
*/
|
||||
postMessage: function (data) {
|
||||
let args = ['message'].concat(Array.slice(arguments));
|
||||
if (!this._inited) {
|
||||
this._earlyEvents.push(args);
|
||||
postMessage: function (...data) {
|
||||
let model = modelFor(this);
|
||||
let args = ['message'].concat(data);
|
||||
if (!model.inited) {
|
||||
model.earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(this, args);
|
||||
processMessage.apply(null, [this].concat(args));
|
||||
},
|
||||
|
||||
/**
|
||||
* EventEmitter, that behaves (calls listeners) asynchronously.
|
||||
* A way to send customized messages to / from the worker.
|
||||
* Events from in the worker can be observed / emitted via
|
||||
* worker.on / worker.emit.
|
||||
*/
|
||||
get port() {
|
||||
// We generate dynamically this attribute as it needs to be accessible
|
||||
// before Worker.constructor gets called. (For ex: Panel)
|
||||
|
||||
// create an event emitter that receive and send events from/to the worker
|
||||
this._port = EventEmitterTrait.create({
|
||||
emit: this._emitEventToContent.bind(this)
|
||||
});
|
||||
|
||||
// expose wrapped port, that exposes only public properties:
|
||||
// We need to destroy this getter in order to be able to set the
|
||||
// final value. We need to update only public port attribute as we never
|
||||
// try to access port attribute from private API.
|
||||
delete this._public.port;
|
||||
this._public.port = Cortex(this._port);
|
||||
// Replicate public port to the private object
|
||||
delete this.port;
|
||||
this.port = this._public.port;
|
||||
|
||||
return this._port;
|
||||
get url () {
|
||||
let model = modelFor(this);
|
||||
// model.window will be null after detach
|
||||
return model.window ? model.window.document.location.href : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Same object than this.port but private API.
|
||||
* Allow access to _emit, in order to send event to port.
|
||||
*/
|
||||
_port: null,
|
||||
|
||||
/**
|
||||
* Emit a custom event to the content script,
|
||||
* i.e. emit this event on `self.port`
|
||||
*/
|
||||
_emitEventToContent: function () {
|
||||
let args = ['event'].concat(Array.slice(arguments));
|
||||
if (!this._inited) {
|
||||
this._earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(this, args);
|
||||
get contentURL () {
|
||||
let model = modelFor(this);
|
||||
return model.window ? model.window.document.URL : null;
|
||||
},
|
||||
|
||||
// Is worker connected to the content worker sandbox ?
|
||||
_inited: false,
|
||||
|
||||
// Is worker being frozen? i.e related document is frozen in bfcache.
|
||||
// Content script should not be reachable if frozen.
|
||||
_frozen: true,
|
||||
|
||||
constructor: function Worker(options) {
|
||||
options = options || {};
|
||||
|
||||
if ('contentScriptFile' in options)
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
if ('contentScriptOptions' in options)
|
||||
this.contentScriptOptions = options.contentScriptOptions;
|
||||
if ('contentScript' in options)
|
||||
this.contentScript = options.contentScript;
|
||||
|
||||
this._setListeners(options);
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
|
||||
// Ensure that worker._port is initialized for contentWorker to be able
|
||||
// to send events during worker initialization.
|
||||
this.port;
|
||||
|
||||
this._documentUnload = this._documentUnload.bind(this);
|
||||
this._pageShow = this._pageShow.bind(this);
|
||||
this._pageHide = this._pageHide.bind(this);
|
||||
|
||||
if ("window" in options) this._attach(options.window);
|
||||
},
|
||||
|
||||
_setListeners: function(options) {
|
||||
if ('onError' in options)
|
||||
this.on('error', options.onError);
|
||||
if ('onMessage' in options)
|
||||
this.on('message', options.onMessage);
|
||||
if ('onDetach' in options)
|
||||
this.on('detach', options.onDetach);
|
||||
},
|
||||
|
||||
_attach: function(window) {
|
||||
this._window = window;
|
||||
// Track document unload to destroy this worker.
|
||||
// We can't watch for unload event on page's window object as it
|
||||
// prevents bfcache from working:
|
||||
// https://developer.mozilla.org/En/Working_with_BFCache
|
||||
this._windowID = getInnerId(this._window);
|
||||
observers.add("inner-window-destroyed", this._documentUnload);
|
||||
|
||||
// Listen to pagehide event in order to freeze the content script
|
||||
// while the document is frozen in bfcache:
|
||||
this._window.addEventListener("pageshow", this._pageShow, true);
|
||||
this._window.addEventListener("pagehide", this._pageHide, true);
|
||||
|
||||
// will set this._contentWorker pointing to the private API:
|
||||
this._contentWorker = WorkerSandbox(this);
|
||||
|
||||
// Mainly enable worker.port.emit to send event to the content worker
|
||||
this._inited = true;
|
||||
this._frozen = false;
|
||||
|
||||
// Process all events and messages that were fired before the
|
||||
// worker was initialized.
|
||||
this._earlyEvents.forEach((function (args) {
|
||||
processMessage.apply(this, args);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_documentUnload: function _documentUnload(subject, topic, data) {
|
||||
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (innerWinID != this._windowID) return false;
|
||||
this._workerCleanup();
|
||||
return true;
|
||||
},
|
||||
|
||||
_pageShow: function _pageShow() {
|
||||
this._contentWorker.emitSync("pageshow");
|
||||
this._emit("pageshow");
|
||||
this._frozen = false;
|
||||
},
|
||||
|
||||
_pageHide: function _pageHide() {
|
||||
this._contentWorker.emitSync("pagehide");
|
||||
this._emit("pagehide");
|
||||
this._frozen = true;
|
||||
},
|
||||
|
||||
get url() {
|
||||
// this._window will be null after detach
|
||||
return this._window ? this._window.document.location.href : null;
|
||||
},
|
||||
|
||||
get tab() {
|
||||
// this._window will be null after detach
|
||||
if (this._window)
|
||||
return getTabForWindow(this._window);
|
||||
get tab () {
|
||||
let model = modelFor(this);
|
||||
// model.window will be null after detach
|
||||
if (model.window)
|
||||
return getTabForWindow(model.window);
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells content worker to unload itself and
|
||||
* removes all the references from itself.
|
||||
*/
|
||||
destroy: function destroy() {
|
||||
this._workerCleanup();
|
||||
this._inited = true;
|
||||
this._removeAllListeners();
|
||||
// Implemented to provide some of the previous features of exposing sandbox
|
||||
// so that Worker can be extended
|
||||
getSandbox: function () {
|
||||
return modelFor(this).contentWorker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all internal references to the attached document
|
||||
* Tells _port to unload itself and removes all the references from itself.
|
||||
*/
|
||||
_workerCleanup: function _workerCleanup() {
|
||||
// maybe unloaded before content side is created
|
||||
// As Symbiont call worker.constructor on document load
|
||||
if (this._contentWorker)
|
||||
this._contentWorker.destroy();
|
||||
this._contentWorker = null;
|
||||
if (this._window) {
|
||||
this._window.removeEventListener("pageshow", this._pageShow, true);
|
||||
this._window.removeEventListener("pagehide", this._pageHide, true);
|
||||
}
|
||||
this._window = null;
|
||||
// This method may be called multiple times,
|
||||
// avoid dispatching `detach` event more than once
|
||||
if (this._windowID) {
|
||||
this._windowID = null;
|
||||
observers.remove("inner-window-destroyed", this._documentUnload);
|
||||
this._earlyEvents.length = 0;
|
||||
this._emit("detach");
|
||||
}
|
||||
this._inited = false;
|
||||
},
|
||||
toString: function () { return '[object Worker]'; },
|
||||
attach: method(attach),
|
||||
detach: method(detach),
|
||||
destroy: method(destroy)
|
||||
});
|
||||
exports.Worker = Worker;
|
||||
|
||||
/**
|
||||
* Receive an event from the content script that need to be sent to
|
||||
* worker.port. Provide a way for composed object to catch all events.
|
||||
*/
|
||||
_onContentScriptEvent: function _onContentScriptEvent() {
|
||||
this._port._emit.apply(this._port, arguments);
|
||||
},
|
||||
attach.define(Worker, function (worker, window) {
|
||||
let model = modelFor(worker);
|
||||
model.window = window;
|
||||
// Track document unload to destroy this worker.
|
||||
// We can't watch for unload event on page's window object as it
|
||||
// prevents bfcache from working:
|
||||
// https://developer.mozilla.org/En/Working_with_BFCache
|
||||
model.windowID = getInnerId(model.window);
|
||||
events.on("inner-window-destroyed", model.documentUnload);
|
||||
|
||||
/**
|
||||
* Reference to the content side of the worker.
|
||||
* @type {WorkerGlobalScope}
|
||||
*/
|
||||
_contentWorker: null,
|
||||
// Listen to pagehide event in order to freeze the content script
|
||||
// while the document is frozen in bfcache:
|
||||
model.window.addEventListener("pageshow", model.pageShow, true);
|
||||
model.window.addEventListener("pagehide", model.pageHide, true);
|
||||
|
||||
/**
|
||||
* Reference to the window that is accessible from
|
||||
* the content scripts.
|
||||
* @type {Object}
|
||||
*/
|
||||
_window: null,
|
||||
// will set model.contentWorker pointing to the private API:
|
||||
model.contentWorker = WorkerSandbox(worker, model.window);
|
||||
|
||||
/**
|
||||
* Flag to enable `addon` object injection in document. (bug 612726)
|
||||
* @type {Boolean}
|
||||
*/
|
||||
_injectInDocument: false
|
||||
// Mainly enable worker.port.emit to send event to the content worker
|
||||
model.inited = true;
|
||||
model.frozen = false;
|
||||
|
||||
// Fire off `attach` event
|
||||
emit(worker, 'attach', window);
|
||||
|
||||
// Process all events and messages that were fired before the
|
||||
// worker was initialized.
|
||||
model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
|
||||
});
|
||||
|
||||
/**
|
||||
* Fired from postMessage and _emitEventToContent, or from the _earlyMessage
|
||||
* Remove all internal references to the attached document
|
||||
* Tells _port to unload itself and removes all the references from itself.
|
||||
*/
|
||||
detach.define(Worker, function (worker) {
|
||||
let model = modelFor(worker);
|
||||
// maybe unloaded before content side is created
|
||||
if (model.contentWorker)
|
||||
model.contentWorker.destroy();
|
||||
model.contentWorker = null;
|
||||
if (model.window) {
|
||||
model.window.removeEventListener("pageshow", model.pageShow, true);
|
||||
model.window.removeEventListener("pagehide", model.pageHide, true);
|
||||
}
|
||||
model.window = null;
|
||||
// This method may be called multiple times,
|
||||
// avoid dispatching `detach` event more than once
|
||||
if (model.windowID) {
|
||||
model.windowID = null;
|
||||
events.off("inner-window-destroyed", model.documentUnload);
|
||||
model.earlyEvents.length = 0;
|
||||
emit(worker, 'detach');
|
||||
}
|
||||
model.inited = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Tells content worker to unload itself and
|
||||
* removes all the references from itself.
|
||||
*/
|
||||
destroy.define(Worker, function (worker) {
|
||||
detach(worker);
|
||||
modelFor(worker).inited = true;
|
||||
// Specifying no type or listener removes all listeners
|
||||
// from target
|
||||
off(worker);
|
||||
});
|
||||
|
||||
/**
|
||||
* Events fired by workers
|
||||
*/
|
||||
function documentUnload ({ subject, data }) {
|
||||
let model = modelFor(this);
|
||||
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (innerWinID != model.windowID) return false;
|
||||
detach(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
function pageShow () {
|
||||
let model = modelFor(this);
|
||||
model.contentWorker.emitSync('pageshow');
|
||||
emit(this, 'pageshow');
|
||||
model.frozen = false;
|
||||
}
|
||||
|
||||
function pageHide () {
|
||||
let model = modelFor(this);
|
||||
model.contentWorker.emitSync('pagehide');
|
||||
emit(this, 'pagehide');
|
||||
model.frozen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired from postMessage and emitEventToContent, or from the earlyMessage
|
||||
* queue when fired before the content is loaded. Sends arguments to
|
||||
* contentWorker if able
|
||||
*/
|
||||
|
||||
function processMessage () {
|
||||
if (!this._contentWorker)
|
||||
function processMessage (worker, ...args) {
|
||||
let model = modelFor(worker) || {};
|
||||
if (!model.contentWorker)
|
||||
throw new Error(ERR_DESTROYED);
|
||||
if (this._frozen)
|
||||
if (model.frozen)
|
||||
throw new Error(ERR_FROZEN);
|
||||
|
||||
this._contentWorker.emit.apply(null, Array.slice(arguments));
|
||||
model.contentWorker.emit.apply(null, args);
|
||||
}
|
||||
|
||||
function createModel () {
|
||||
return {
|
||||
// List of messages fired before worker is initialized
|
||||
earlyEvents: [],
|
||||
// Is worker connected to the content worker sandbox ?
|
||||
inited: false,
|
||||
// Is worker being frozen? i.e related document is frozen in bfcache.
|
||||
// Content script should not be reachable if frozen.
|
||||
frozen: true,
|
||||
/**
|
||||
* Reference to the content side of the worker.
|
||||
* @type {WorkerGlobalScope}
|
||||
*/
|
||||
contentWorker: null,
|
||||
/**
|
||||
* Reference to the window that is accessible from
|
||||
* the content scripts.
|
||||
* @type {Object}
|
||||
*/
|
||||
window: null
|
||||
};
|
||||
}
|
||||
|
||||
function createPort (worker) {
|
||||
let port = EventTarget();
|
||||
port.emit = emitEventToContent.bind(null, worker);
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a custom event to the content script,
|
||||
* i.e. emit this event on `self.port`
|
||||
*/
|
||||
function emitEventToContent (worker, ...eventArgs) {
|
||||
let model = modelFor(worker);
|
||||
let args = ['event'].concat(eventArgs);
|
||||
if (!model.inited) {
|
||||
model.earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(null, [worker].concat(args));
|
||||
}
|
||||
|
||||
exports.Worker = Worker;
|
||||
|
@ -363,10 +363,12 @@ let menuRules = mix(labelledItemRules, {
|
||||
}
|
||||
});
|
||||
|
||||
let ContextWorker = Worker.compose({
|
||||
let ContextWorker = Class({
|
||||
implements: [ Worker ],
|
||||
|
||||
//Returns true if any context listeners are defined in the worker's port.
|
||||
anyContextListeners: function anyContextListeners() {
|
||||
return this._contentWorker.hasListenerFor("context");
|
||||
return this.getSandbox().hasListenerFor("context");
|
||||
},
|
||||
|
||||
// Calls the context workers context listeners and returns the first result
|
||||
@ -374,7 +376,7 @@ let ContextWorker = Worker.compose({
|
||||
// listeners returned false then returns false. If there are no listeners
|
||||
// then returns null.
|
||||
getMatchedContext: function getCurrentContexts(popupNode) {
|
||||
let results = this._contentWorker.emitSync("context", popupNode);
|
||||
let results = this.getSandbox().emitSync("context", popupNode);
|
||||
return results.reduce(function(val, result) val || result, null);
|
||||
},
|
||||
|
||||
@ -382,7 +384,7 @@ let ContextWorker = Worker.compose({
|
||||
// context-clicked, and clickedItemData is the data of the item that was
|
||||
// clicked.
|
||||
fireClick: function fireClick(popupNode, clickedItemData) {
|
||||
this._contentWorker.emitSync("click", popupNode, clickedItemData);
|
||||
this.getSandbox().emitSync("click", popupNode, clickedItemData);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -27,29 +27,6 @@ const VALID_TYPES = [
|
||||
|
||||
const { isArray } = Array;
|
||||
|
||||
/**
|
||||
* Returns a function C that creates instances of privateCtor. C may be called
|
||||
* with or without the new keyword. The prototype of each instance returned
|
||||
* from C is C.prototype, and C.prototype is an object whose prototype is
|
||||
* privateCtor.prototype. Instances returned from C will therefore be instances
|
||||
* of both C and privateCtor. Additionally, the constructor of each instance
|
||||
* returned from C is C.
|
||||
*
|
||||
* @param privateCtor
|
||||
* A constructor.
|
||||
* @return A function that makes new instances of privateCtor.
|
||||
*/
|
||||
exports.publicConstructor = function publicConstructor(privateCtor) {
|
||||
function PublicCtor() {
|
||||
let obj = { constructor: PublicCtor, __proto__: PublicCtor.prototype };
|
||||
memory.track(obj, privateCtor.name);
|
||||
privateCtor.apply(obj, arguments);
|
||||
return obj;
|
||||
}
|
||||
PublicCtor.prototype = { __proto__: privateCtor.prototype };
|
||||
return PublicCtor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a validated options dictionary given some requirements. If any of
|
||||
* the requirements are not met, an exception is thrown.
|
||||
|
@ -1,67 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "deprecated"
|
||||
};
|
||||
|
||||
const {Cc,Ci} = require("chrome");
|
||||
const apiUtils = require("./api-utils");
|
||||
|
||||
/**
|
||||
* A bundle of strings.
|
||||
*
|
||||
* @param url {String}
|
||||
* the URL of the string bundle
|
||||
*/
|
||||
exports.StringBundle = apiUtils.publicConstructor(function StringBundle(url) {
|
||||
|
||||
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle(url);
|
||||
|
||||
this.__defineGetter__("url", function () url);
|
||||
|
||||
/**
|
||||
* Get a string from the bundle.
|
||||
*
|
||||
* @param name {String}
|
||||
* the name of the string to get
|
||||
* @param args {array} [optional]
|
||||
* an array of arguments that replace occurrences of %S in the string
|
||||
*
|
||||
* @returns {String} the value of the string
|
||||
*/
|
||||
this.get = function strings_get(name, args) {
|
||||
try {
|
||||
if (args)
|
||||
return stringBundle.formatStringFromName(name, args, args.length);
|
||||
else
|
||||
return stringBundle.GetStringFromName(name);
|
||||
}
|
||||
catch(ex) {
|
||||
// f.e. "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
|
||||
// [nsIStringBundle.GetStringFromName]"
|
||||
throw new Error("String '" + name + "' could not be retrieved from the " +
|
||||
"bundle due to an unknown error (it doesn't exist?).");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate the strings in the bundle.
|
||||
*
|
||||
*/
|
||||
apiUtils.addIterator(
|
||||
this,
|
||||
function keysValsGen() {
|
||||
let enumerator = stringBundle.getSimpleEnumeration();
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
|
||||
yield [elem.key, elem.value];
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
@ -1,48 +1,52 @@
|
||||
/* 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": "deprecated"
|
||||
};
|
||||
|
||||
const {Cc,Ci,Cu,components} = require("chrome");
|
||||
var trackedObjects = {};
|
||||
const { Cc, Ci, Cu, components } = require("chrome");
|
||||
const { when: unload } = require("../system/unload")
|
||||
|
||||
var Compacter = {
|
||||
INTERVAL: 5000,
|
||||
notify: function(timer) {
|
||||
var trackedObjects = {};
|
||||
const Compacter = {
|
||||
notify: function() {
|
||||
var newTrackedObjects = {};
|
||||
|
||||
for (let name in trackedObjects) {
|
||||
var oldBin = trackedObjects[name];
|
||||
var newBin = [];
|
||||
var strongRefs = [];
|
||||
for (var i = 0; i < oldBin.length; i++) {
|
||||
var strongRef = oldBin[i].weakref.get();
|
||||
let oldBin = trackedObjects[name];
|
||||
let newBin = [];
|
||||
let strongRefs = [];
|
||||
|
||||
for (let i = 0, l = oldBin.length; i < l; i++) {
|
||||
let strongRef = oldBin[i].weakref.get();
|
||||
|
||||
if (strongRef && strongRefs.indexOf(strongRef) == -1) {
|
||||
strongRefs.push(strongRef);
|
||||
newBin.push(oldBin[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (newBin.length)
|
||||
newTrackedObjects[name] = newBin;
|
||||
}
|
||||
|
||||
trackedObjects = newTrackedObjects;
|
||||
}
|
||||
};
|
||||
|
||||
var timer = Cc["@mozilla.org/timer;1"]
|
||||
.createInstance(Ci.nsITimer);
|
||||
|
||||
timer.initWithCallback(Compacter,
|
||||
Compacter.INTERVAL,
|
||||
5000,
|
||||
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
||||
|
||||
var track = exports.track = function track(object, bin, stackFrameNumber) {
|
||||
function track(object, bin, stackFrameNumber) {
|
||||
var frame = components.stack.caller;
|
||||
var weakref = Cu.getWeakReference(object);
|
||||
|
||||
if (!bin && 'constructor' in object)
|
||||
bin = object.constructor.name;
|
||||
if (bin == "Object")
|
||||
@ -61,7 +65,8 @@ var track = exports.track = function track(object, bin, stackFrameNumber) {
|
||||
filename: frame.filename,
|
||||
lineNo: frame.lineNumber,
|
||||
bin: bin});
|
||||
};
|
||||
}
|
||||
exports.track = track;
|
||||
|
||||
var getBins = exports.getBins = function getBins() {
|
||||
var names = [];
|
||||
@ -70,49 +75,55 @@ var getBins = exports.getBins = function getBins() {
|
||||
return names;
|
||||
};
|
||||
|
||||
var getObjects = exports.getObjects = function getObjects(bin) {
|
||||
function getLiveObjectsInBin(bin, array) {
|
||||
for (var i = 0; i < bin.length; i++) {
|
||||
var object = bin[i].weakref.get();
|
||||
if (object)
|
||||
array.push(bin[i]);
|
||||
function getObjects(bin) {
|
||||
var results = [];
|
||||
|
||||
function getLiveObjectsInBin(bin) {
|
||||
for (let i = 0, l = bin.length; i < l; i++) {
|
||||
let object = bin[i].weakref.get();
|
||||
|
||||
if (object) {
|
||||
results.push(bin[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var results = [];
|
||||
if (bin) {
|
||||
if (bin in trackedObjects)
|
||||
getLiveObjectsInBin(trackedObjects[bin], results);
|
||||
} else
|
||||
getLiveObjectsInBin(trackedObjects[bin]);
|
||||
}
|
||||
else {
|
||||
for (let name in trackedObjects)
|
||||
getLiveObjectsInBin(trackedObjects[name], results);
|
||||
return results;
|
||||
};
|
||||
getLiveObjectsInBin(trackedObjects[name]);
|
||||
}
|
||||
|
||||
var gc = exports.gc = function gc() {
|
||||
return results;
|
||||
}
|
||||
exports.getObjects = getObjects;
|
||||
|
||||
function gc() {
|
||||
// Components.utils.forceGC() doesn't currently perform
|
||||
// cycle collection, which means that e.g. DOM elements
|
||||
// won't be collected by it. Fortunately, there are
|
||||
// other ways...
|
||||
|
||||
var window = Cc["@mozilla.org/appshell/appShellService;1"]
|
||||
var test_utils = Cc["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Ci.nsIAppShellService)
|
||||
.hiddenDOMWindow;
|
||||
var test_utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
.hiddenDOMWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
test_utils.garbageCollect();
|
||||
// Clean metadata for dead objects
|
||||
Compacter.notify();
|
||||
|
||||
// Not sure why, but sometimes it appears that we don't get
|
||||
// them all with just one CC, so let's do it again.
|
||||
test_utils.garbageCollect();
|
||||
};
|
||||
exports.gc = gc;
|
||||
|
||||
require("../system/unload").when(
|
||||
function() {
|
||||
trackedObjects = {};
|
||||
if (timer) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
unload(_ => {
|
||||
trackedObjects = {};
|
||||
if (timer) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
|
@ -1,134 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "deprecated"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { when: unload } = require("../system/unload");
|
||||
const { ns } = require("../core/namespace");
|
||||
const { on, off, emit, once } = require("../system/events");
|
||||
const { id } = require("../self");
|
||||
|
||||
const subscribers = ns();
|
||||
const cache = [];
|
||||
|
||||
/**
|
||||
* Topics specifically available to Jetpack-generated extensions.
|
||||
*
|
||||
* Using these predefined consts instead of the platform strings is good:
|
||||
* - allows us to scope topics specifically for Jetpacks
|
||||
* - addons aren't dependent on strings nor behavior of core platform topics
|
||||
* - the core platform topics are not clearly named
|
||||
*
|
||||
*/
|
||||
exports.topics = {
|
||||
/**
|
||||
* A topic indicating that the application is in a state usable
|
||||
* by add-ons.
|
||||
*/
|
||||
APPLICATION_READY: id + "_APPLICATION_READY"
|
||||
};
|
||||
|
||||
function Listener(callback, target) {
|
||||
return function listener({ subject, data }) {
|
||||
callback.call(target || callback, subject, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given callback as an observer of the given topic.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic to observe
|
||||
*
|
||||
* @param callback {Object}
|
||||
* the callback; an Object that implements nsIObserver or a Function
|
||||
* that gets called when the notification occurs
|
||||
*
|
||||
* @param target {Object} [optional]
|
||||
* the object to use as |this| when calling a Function callback
|
||||
*
|
||||
* @returns the observer
|
||||
*/
|
||||
function add(topic, callback, target) {
|
||||
let listeners = subscribers(callback);
|
||||
if (!(topic in listeners)) {
|
||||
let listener = Listener(callback, target);
|
||||
listeners[topic] = listener;
|
||||
|
||||
// Cache callback unless it's already cached.
|
||||
if (!~cache.indexOf(callback))
|
||||
cache.push(callback);
|
||||
|
||||
on(topic, listener);
|
||||
}
|
||||
};
|
||||
exports.add = add;
|
||||
|
||||
/**
|
||||
* Unregister the given callback as an observer of the given topic.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic being observed
|
||||
*
|
||||
* @param callback {Object}
|
||||
* the callback doing the observing
|
||||
*
|
||||
* @param target {Object} [optional]
|
||||
* the object being used as |this| when calling a Function callback
|
||||
*/
|
||||
function remove(topic, callback, target) {
|
||||
let listeners = subscribers(callback);
|
||||
if (topic in listeners) {
|
||||
let listener = listeners[topic];
|
||||
delete listeners[topic];
|
||||
|
||||
// If no more observers are registered and callback is still in cache
|
||||
// then remove it.
|
||||
let index = cache.indexOf(callback);
|
||||
if (~index && !Object.keys(listeners).length)
|
||||
cache.splice(index, 1)
|
||||
|
||||
off(topic, listener);
|
||||
}
|
||||
};
|
||||
exports.remove = remove;
|
||||
|
||||
/**
|
||||
* Notify observers about something.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic to notify observers about
|
||||
*
|
||||
* @param subject {Object} [optional]
|
||||
* some information about the topic; can be any JS object or primitive
|
||||
*
|
||||
* @param data {String} [optional] [deprecated]
|
||||
* some more information about the topic; deprecated as the subject
|
||||
* is sufficient to pass all needed information to the JS observers
|
||||
* that this module targets; if you have multiple values to pass to
|
||||
* the observer, wrap them in an object and pass them via the subject
|
||||
* parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
|
||||
*/
|
||||
function notify(topic, subject, data) {
|
||||
emit(topic, {
|
||||
subject: subject === undefined ? null : subject,
|
||||
data: data === undefined ? null : data
|
||||
});
|
||||
}
|
||||
exports.notify = notify;
|
||||
|
||||
unload(function() {
|
||||
// Make a copy of cache first, since cache will be changing as we
|
||||
// iterate through it.
|
||||
cache.slice().forEach(function(callback) {
|
||||
Object.keys(subscribers(callback)).forEach(function(topic) {
|
||||
remove(topic, callback);
|
||||
});
|
||||
});
|
||||
})
|
@ -4,13 +4,13 @@
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
"stability": "deprecated"
|
||||
};
|
||||
|
||||
const { Worker } = require('./worker');
|
||||
const { Loader } = require('./loader');
|
||||
const { Worker } = require('./traits-worker');
|
||||
const { Loader } = require('../content/loader');
|
||||
const hiddenFrames = require('../frame/hidden-frame');
|
||||
const observers = require('../deprecated/observer-service');
|
||||
const { on, off } = require('../system/events');
|
||||
const unload = require('../system/unload');
|
||||
const { getDocShell } = require("../frame/utils");
|
||||
const { ignoreWindow } = require('../private-browsing/utils');
|
||||
@ -28,7 +28,7 @@ const Symbiont = Worker.resolve({
|
||||
constructor: '_initWorker',
|
||||
destroy: '_workerDestroy'
|
||||
}).compose(Loader, {
|
||||
|
||||
|
||||
/**
|
||||
* The constructor requires all the options that are required by
|
||||
* `require('content').Worker` with the difference that the `frame` option
|
||||
@ -80,7 +80,7 @@ const Symbiont = Worker.resolve({
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
},
|
||||
|
||||
|
||||
destroy: function destroy() {
|
||||
this._workerDestroy();
|
||||
this._unregisterListener();
|
||||
@ -90,14 +90,14 @@ const Symbiont = Worker.resolve({
|
||||
this._hiddenFrame = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* XUL iframe or browser elements with attribute `type` being `content`.
|
||||
* Used to create `ContentSymbiont` from.
|
||||
* @type {nsIFrame|nsIBrowser}
|
||||
*/
|
||||
_frame: null,
|
||||
|
||||
|
||||
/**
|
||||
* Listener to the `'frameReady"` event (emitted when `iframe` is ready).
|
||||
* Removes listener, sets right permissions to the frame and loads content.
|
||||
@ -105,7 +105,7 @@ const Symbiont = Worker.resolve({
|
||||
_initFrame: function _initFrame(frame) {
|
||||
if (this._loadListener)
|
||||
this._unregisterListener();
|
||||
|
||||
|
||||
this._frame = frame;
|
||||
|
||||
if (getDocShell(frame)) {
|
||||
@ -113,16 +113,16 @@ const Symbiont = Worker.resolve({
|
||||
}
|
||||
else {
|
||||
if (this._waitForFrame) {
|
||||
observers.remove('content-document-global-created', this._waitForFrame);
|
||||
off('content-document-global-created', this._waitForFrame);
|
||||
}
|
||||
this._waitForFrame = this.__waitForFrame.bind(this, frame);
|
||||
observers.add('content-document-global-created', this._waitForFrame);
|
||||
on('content-document-global-created', this._waitForFrame);
|
||||
}
|
||||
},
|
||||
|
||||
__waitForFrame: function _waitForFrame(frame, win, topic) {
|
||||
__waitForFrame: function _waitForFrame(frame, { subject: win }) {
|
||||
if (frame.contentWindow == win) {
|
||||
observers.remove('content-document-global-created', this._waitForFrame);
|
||||
off('content-document-global-created', this._waitForFrame);
|
||||
delete this._waitForFrame;
|
||||
this._reallyInitFrame(frame);
|
||||
}
|
||||
@ -157,13 +157,13 @@ const Symbiont = Worker.resolve({
|
||||
this._onInit();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let self = this;
|
||||
|
||||
|
||||
if ('start' == this.contentScriptWhen) {
|
||||
this._loadEvent = 'start';
|
||||
observers.add('document-element-inserted',
|
||||
this._loadListener = function onStart(doc) {
|
||||
on('document-element-inserted',
|
||||
this._loadListener = function onStart({ subject: doc }) {
|
||||
let window = doc.defaultView;
|
||||
|
||||
if (ignoreWindow(window)) {
|
||||
@ -174,41 +174,41 @@ const Symbiont = Worker.resolve({
|
||||
self._unregisterListener();
|
||||
self._onInit();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
|
||||
let self = this;
|
||||
this._loadEvent = eventName;
|
||||
frame.addEventListener(eventName,
|
||||
frame.addEventListener(eventName,
|
||||
this._loadListener = function _onReady(event) {
|
||||
|
||||
|
||||
if (event.target != frame.contentDocument)
|
||||
return;
|
||||
self._unregisterListener();
|
||||
|
||||
|
||||
self._onInit();
|
||||
|
||||
|
||||
}, true);
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Unregister listener that watchs for document being ready to be injected.
|
||||
* This listener is registered in `Symbiont._initFrame`.
|
||||
*/
|
||||
_unregisterListener: function _unregisterListener() {
|
||||
if (this._waitForFrame) {
|
||||
observers.remove('content-document-global-created', this._waitForFrame);
|
||||
off('content-document-global-created', this._waitForFrame);
|
||||
delete this._waitForFrame;
|
||||
}
|
||||
|
||||
if (!this._loadListener)
|
||||
return;
|
||||
if (this._loadEvent == "start") {
|
||||
observers.remove('document-element-inserted', this._loadListener);
|
||||
off('document-element-inserted', this._loadListener);
|
||||
}
|
||||
else {
|
||||
this._frame.removeEventListener(this._loadEvent, this._loadListener,
|
||||
@ -216,14 +216,14 @@ const Symbiont = Worker.resolve({
|
||||
}
|
||||
this._loadListener = null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Called by Symbiont itself when the frame is ready to load
|
||||
* content scripts according to contentScriptWhen. Overloaded by Panel.
|
||||
* Called by Symbiont itself when the frame is ready to load
|
||||
* content scripts according to contentScriptWhen. Overloaded by Panel.
|
||||
*/
|
||||
_onInit: function () {
|
||||
this._initWorker({ window: this._frame.contentWindow });
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
exports.Symbiont = Symbiont;
|
660
addon-sdk/source/lib/sdk/deprecated/traits-worker.js
Normal file
660
addon-sdk/source/lib/sdk/deprecated/traits-worker.js
Normal file
@ -0,0 +1,660 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
*
|
||||
* `deprecated/traits-worker` was previously `content/worker` and kept
|
||||
* only due to `deprecated/symbiont` using it, which is necessary for
|
||||
* `widget`, until that reaches deprecation EOL.
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "deprecated"
|
||||
};
|
||||
|
||||
const { Trait } = require('./traits');
|
||||
const { EventEmitter, EventEmitterTrait } = require('./events');
|
||||
const { Ci, Cu, Cc } = require('chrome');
|
||||
const timer = require('../timers');
|
||||
const { URL } = require('../url');
|
||||
const unload = require('../system/unload');
|
||||
const observers = require('../system/events');
|
||||
const { Cortex } = require('./cortex');
|
||||
const { sandbox, evaluate, load } = require("../loader/sandbox");
|
||||
const { merge } = require('../util/object');
|
||||
const xulApp = require("../system/xul-app");
|
||||
const { getInnerId } = require("../window/utils")
|
||||
const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
|
||||
"17.0a2", "*");
|
||||
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/content-worker.js');
|
||||
Then, retrieve URL of these files in the XPI:
|
||||
*/
|
||||
let prefix = module.uri.split('deprecated/traits-worker.js')[0];
|
||||
const CONTENT_WORKER_URL = prefix + 'content/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 =
|
||||
"Couldn't find the worker to receive this message. " +
|
||||
"The script may not be initialized yet, or may already have been unloaded.";
|
||||
|
||||
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
|
||||
"until it is visible again.";
|
||||
|
||||
|
||||
const WorkerSandbox = EventEmitter.compose({
|
||||
|
||||
/**
|
||||
* Emit a message to the worker content sandbox
|
||||
*/
|
||||
emit: function emit() {
|
||||
// First ensure having a regular array
|
||||
// (otherwise, `arguments` would be mapped to an object by `stringify`)
|
||||
let array = Array.slice(arguments);
|
||||
// JSON.stringify is buggy with cross-sandbox values,
|
||||
// it may return "{}" on functions. Use a replacer to match them correctly.
|
||||
function replacer(k, v) {
|
||||
return typeof v === "function" ? undefined : v;
|
||||
}
|
||||
// Ensure having an asynchronous behavior
|
||||
let self = this;
|
||||
timer.setTimeout(function () {
|
||||
self._emitToContent(JSON.stringify(array, replacer));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronous version of `emit`.
|
||||
* /!\ Should only be used when it is strictly mandatory /!\
|
||||
* Doesn't ensure passing only JSON values.
|
||||
* Mainly used by context-menu in order to avoid breaking it.
|
||||
*/
|
||||
emitSync: function emitSync() {
|
||||
let args = Array.slice(arguments);
|
||||
return this._emitToContent(args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells if content script has at least one listener registered for one event,
|
||||
* through `self.on('xxx', ...)`.
|
||||
* /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
|
||||
*/
|
||||
hasListenerFor: function hasListenerFor(name) {
|
||||
return this._hasListenerFor(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method called by the worker sandbox when it needs to send a message
|
||||
*/
|
||||
_onContentEvent: function onContentEvent(args) {
|
||||
// As `emit`, we ensure having an asynchronous behavior
|
||||
let self = this;
|
||||
timer.setTimeout(function () {
|
||||
// We emit event to chrome/addon listeners
|
||||
self._emit.apply(self, JSON.parse(args));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures sandbox and loads content scripts into it.
|
||||
* @param {Worker} worker
|
||||
* content worker
|
||||
*/
|
||||
constructor: function WorkerSandbox(worker) {
|
||||
this._addonWorker = worker;
|
||||
|
||||
// Ensure that `emit` has always the right `this`
|
||||
this.emit = this.emit.bind(this);
|
||||
this.emitSync = this.emitSync.bind(this);
|
||||
|
||||
// We receive a wrapped window, that may be an xraywrapper if it's content
|
||||
let window = worker._window;
|
||||
let proto = window;
|
||||
|
||||
// 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 wantGlobalProperties = []
|
||||
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;
|
||||
wantGlobalProperties.push("XMLHttpRequest");
|
||||
}
|
||||
|
||||
// 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(principals, { wantXrays: true, sameZoneAs: window });
|
||||
apiSandbox.console = console;
|
||||
|
||||
// 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(principals, {
|
||||
sandboxPrototype: proto,
|
||||
wantXrays: true,
|
||||
wantGlobalProperties: wantGlobalProperties,
|
||||
sameZoneAs: window,
|
||||
metadata: { SDKContentScript: true }
|
||||
});
|
||||
// We have to ensure that window.top and window.parent are the exact same
|
||||
// object than window object, i.e. the sandbox global object. But not
|
||||
// always, in case of iframes, top and parent are another window object.
|
||||
let top = window.top === window ? content : content.top;
|
||||
let parent = window.parent === window ? content : content.parent;
|
||||
merge(content, {
|
||||
// We need "this === window === top" to be true in toplevel scope:
|
||||
get window() content,
|
||||
get top() top,
|
||||
get parent() parent,
|
||||
// Use the Greasemonkey naming convention to provide access to the
|
||||
// unwrapped window object so the content script can access document
|
||||
// JavaScript values.
|
||||
// NOTE: this functionality is experimental and may change or go away
|
||||
// at any time!
|
||||
get unsafeWindow() window.wrappedJSObject
|
||||
});
|
||||
|
||||
// Load trusted code that will inject content script API.
|
||||
// We need to expose JS objects defined in same principal in order to
|
||||
// avoid having any kind of wrapper.
|
||||
load(apiSandbox, CONTENT_WORKER_URL);
|
||||
|
||||
// prepare a clean `self.options`
|
||||
let options = 'contentScriptOptions' in worker ?
|
||||
JSON.stringify( worker.contentScriptOptions ) :
|
||||
undefined;
|
||||
|
||||
// Then call `inject` method and communicate with this script
|
||||
// by trading two methods that allow to send events to the other side:
|
||||
// - `onEvent` called by content script
|
||||
// - `result.emitToContent` called by addon script
|
||||
// Bug 758203: We have to explicitely define `__exposedProps__` in order
|
||||
// to allow access to these chrome object attributes from this sandbox with
|
||||
// content priviledges
|
||||
// https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
|
||||
let chromeAPI = {
|
||||
timers: {
|
||||
setTimeout: timer.setTimeout,
|
||||
setInterval: timer.setInterval,
|
||||
clearTimeout: timer.clearTimeout,
|
||||
clearInterval: timer.clearInterval,
|
||||
__exposedProps__: {
|
||||
setTimeout: 'r',
|
||||
setInterval: 'r',
|
||||
clearTimeout: 'r',
|
||||
clearInterval: 'r'
|
||||
}
|
||||
},
|
||||
sandbox: {
|
||||
evaluate: evaluate,
|
||||
__exposedProps__: {
|
||||
evaluate: 'r',
|
||||
}
|
||||
},
|
||||
__exposedProps__: {
|
||||
timers: 'r',
|
||||
sandbox: 'r',
|
||||
}
|
||||
};
|
||||
let onEvent = this._onContentEvent.bind(this);
|
||||
// `ContentWorker` is defined in CONTENT_WORKER_URL file
|
||||
let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
|
||||
this._emitToContent = result.emitToContent;
|
||||
this._hasListenerFor = result.hasListenerFor;
|
||||
|
||||
// Handle messages send by this script:
|
||||
let self = this;
|
||||
// console.xxx calls
|
||||
this.on("console", function consoleListener(kind) {
|
||||
console[kind].apply(console, Array.slice(arguments, 1));
|
||||
});
|
||||
|
||||
// self.postMessage calls
|
||||
this.on("message", function postMessage(data) {
|
||||
// destroyed?
|
||||
if (self._addonWorker)
|
||||
self._addonWorker._emit('message', data);
|
||||
});
|
||||
|
||||
// self.port.emit calls
|
||||
this.on("event", function portEmit(name, args) {
|
||||
// destroyed?
|
||||
if (self._addonWorker)
|
||||
self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
|
||||
});
|
||||
|
||||
// unwrap, recreate and propagate async Errors thrown from content-script
|
||||
this.on("error", function onError({instanceOfError, value}) {
|
||||
if (self._addonWorker) {
|
||||
let error = value;
|
||||
if (instanceOfError) {
|
||||
error = new Error(value.message, value.fileName, value.lineNumber);
|
||||
error.stack = value.stack;
|
||||
error.name = value.name;
|
||||
}
|
||||
self._addonWorker._emit('error', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Inject `addon` global into target document if document is trusted,
|
||||
// `addon` in document is equivalent to `self` in content script.
|
||||
if (worker._injectInDocument) {
|
||||
let win = window.wrappedJSObject ? window.wrappedJSObject : window;
|
||||
Object.defineProperty(win, "addon", {
|
||||
value: content.self
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Inject our `console` into target document if worker doesn't have a tab
|
||||
// (e.g Panel, PageWorker, Widget).
|
||||
// `worker.tab` can't be used because bug 804935.
|
||||
if (!getTabForContentWindow(window)) {
|
||||
let win = window.wrappedJSObject ? window.wrappedJSObject : window;
|
||||
|
||||
// export our chrome console to content window, using the same approach
|
||||
// of `ConsoleAPI`:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
|
||||
//
|
||||
// and described here:
|
||||
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
|
||||
let con = Cu.createObjectIn(win);
|
||||
|
||||
let genPropDesc = function genPropDesc(fun) {
|
||||
return { enumerable: true, configurable: true, writable: true,
|
||||
value: console[fun] };
|
||||
}
|
||||
|
||||
const properties = {
|
||||
log: genPropDesc('log'),
|
||||
info: genPropDesc('info'),
|
||||
warn: genPropDesc('warn'),
|
||||
error: genPropDesc('error'),
|
||||
debug: genPropDesc('debug'),
|
||||
trace: genPropDesc('trace'),
|
||||
dir: genPropDesc('dir'),
|
||||
group: genPropDesc('group'),
|
||||
groupCollapsed: genPropDesc('groupCollapsed'),
|
||||
groupEnd: genPropDesc('groupEnd'),
|
||||
time: genPropDesc('time'),
|
||||
timeEnd: genPropDesc('timeEnd'),
|
||||
profile: genPropDesc('profile'),
|
||||
profileEnd: genPropDesc('profileEnd'),
|
||||
__noSuchMethod__: { enumerable: true, configurable: true, writable: true,
|
||||
value: function() {} }
|
||||
};
|
||||
|
||||
Object.defineProperties(con, properties);
|
||||
Cu.makeObjectPropsNormal(con);
|
||||
|
||||
win.console = con;
|
||||
};
|
||||
|
||||
// The order of `contentScriptFile` and `contentScript` evaluation is
|
||||
// intentional, so programs can load libraries like jQuery from script URLs
|
||||
// and use them in scripts.
|
||||
let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
|
||||
: null,
|
||||
contentScript = ('contentScript' in worker) ? worker.contentScript : null;
|
||||
|
||||
if (contentScriptFile) {
|
||||
if (Array.isArray(contentScriptFile))
|
||||
this._importScripts.apply(this, contentScriptFile);
|
||||
else
|
||||
this._importScripts(contentScriptFile);
|
||||
}
|
||||
if (contentScript) {
|
||||
this._evaluate(
|
||||
Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
|
||||
);
|
||||
}
|
||||
},
|
||||
destroy: function destroy() {
|
||||
this.emitSync("detach");
|
||||
this._sandbox = null;
|
||||
this._addonWorker = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* JavaScript sandbox where all the content scripts are evaluated.
|
||||
* {Sandbox}
|
||||
*/
|
||||
_sandbox: null,
|
||||
|
||||
/**
|
||||
* Reference to the addon side of the worker.
|
||||
* @type {Worker}
|
||||
*/
|
||||
_addonWorker: null,
|
||||
|
||||
/**
|
||||
* Evaluates code in the sandbox.
|
||||
* @param {String} code
|
||||
* JavaScript source to evaluate.
|
||||
* @param {String} [filename='javascript:' + code]
|
||||
* Name of the file
|
||||
*/
|
||||
_evaluate: function(code, filename) {
|
||||
try {
|
||||
evaluate(this._sandbox, code, filename || 'javascript:' + code);
|
||||
}
|
||||
catch(e) {
|
||||
this._addonWorker._emit('error', e);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Imports scripts to the sandbox by reading files under urls and
|
||||
* evaluating its source. If exception occurs during evaluation
|
||||
* `"error"` event is emitted on the worker.
|
||||
* This is actually an analog to the `importScript` method in web
|
||||
* workers but in our case it's not exposed even though content
|
||||
* scripts may be able to do it synchronously since IO operation
|
||||
* takes place in the UI process.
|
||||
*/
|
||||
_importScripts: function _importScripts(url) {
|
||||
let urls = Array.slice(arguments, 0);
|
||||
for each (let contentScriptFile in urls) {
|
||||
try {
|
||||
let uri = URL(contentScriptFile);
|
||||
if (uri.scheme === 'resource')
|
||||
load(this._sandbox, String(uri));
|
||||
else
|
||||
throw Error("Unsupported `contentScriptFile` url: " + String(uri));
|
||||
}
|
||||
catch(e) {
|
||||
this._addonWorker._emit('error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Message-passing facility for communication between code running
|
||||
* in the content and add-on process.
|
||||
* @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html
|
||||
*/
|
||||
const Worker = EventEmitter.compose({
|
||||
on: Trait.required,
|
||||
_removeAllListeners: Trait.required,
|
||||
|
||||
// List of messages fired before worker is initialized
|
||||
get _earlyEvents() {
|
||||
delete this._earlyEvents;
|
||||
this._earlyEvents = [];
|
||||
return this._earlyEvents;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to the worker's global scope. Method takes single
|
||||
* argument, which represents data to be sent to the worker. The data may
|
||||
* be any primitive type value or `JSON`. Call of this method asynchronously
|
||||
* emits `message` event with data value in the global scope of this
|
||||
* symbiont.
|
||||
*
|
||||
* `message` event listeners can be set either by calling
|
||||
* `self.on` with a first argument string `"message"` or by
|
||||
* implementing `onMessage` function in the global scope of this worker.
|
||||
* @param {Number|String|JSON} data
|
||||
*/
|
||||
postMessage: function (data) {
|
||||
let args = ['message'].concat(Array.slice(arguments));
|
||||
if (!this._inited) {
|
||||
this._earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(this, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* EventEmitter, that behaves (calls listeners) asynchronously.
|
||||
* A way to send customized messages to / from the worker.
|
||||
* Events from in the worker can be observed / emitted via
|
||||
* worker.on / worker.emit.
|
||||
*/
|
||||
get port() {
|
||||
// We generate dynamically this attribute as it needs to be accessible
|
||||
// before Worker.constructor gets called. (For ex: Panel)
|
||||
|
||||
// create an event emitter that receive and send events from/to the worker
|
||||
this._port = EventEmitterTrait.create({
|
||||
emit: this._emitEventToContent.bind(this)
|
||||
});
|
||||
|
||||
// expose wrapped port, that exposes only public properties:
|
||||
// We need to destroy this getter in order to be able to set the
|
||||
// final value. We need to update only public port attribute as we never
|
||||
// try to access port attribute from private API.
|
||||
delete this._public.port;
|
||||
this._public.port = Cortex(this._port);
|
||||
// Replicate public port to the private object
|
||||
delete this.port;
|
||||
this.port = this._public.port;
|
||||
|
||||
return this._port;
|
||||
},
|
||||
|
||||
/**
|
||||
* Same object than this.port but private API.
|
||||
* Allow access to _emit, in order to send event to port.
|
||||
*/
|
||||
_port: null,
|
||||
|
||||
/**
|
||||
* Emit a custom event to the content script,
|
||||
* i.e. emit this event on `self.port`
|
||||
*/
|
||||
_emitEventToContent: function () {
|
||||
let args = ['event'].concat(Array.slice(arguments));
|
||||
if (!this._inited) {
|
||||
this._earlyEvents.push(args);
|
||||
return;
|
||||
}
|
||||
processMessage.apply(this, args);
|
||||
},
|
||||
|
||||
// Is worker connected to the content worker sandbox ?
|
||||
_inited: false,
|
||||
|
||||
// Is worker being frozen? i.e related document is frozen in bfcache.
|
||||
// Content script should not be reachable if frozen.
|
||||
_frozen: true,
|
||||
|
||||
constructor: function Worker(options) {
|
||||
options = options || {};
|
||||
|
||||
if ('contentScriptFile' in options)
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
if ('contentScriptOptions' in options)
|
||||
this.contentScriptOptions = options.contentScriptOptions;
|
||||
if ('contentScript' in options)
|
||||
this.contentScript = options.contentScript;
|
||||
|
||||
this._setListeners(options);
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
|
||||
// Ensure that worker._port is initialized for contentWorker to be able
|
||||
// to send events during worker initialization.
|
||||
this.port;
|
||||
|
||||
this._documentUnload = this._documentUnload.bind(this);
|
||||
this._pageShow = this._pageShow.bind(this);
|
||||
this._pageHide = this._pageHide.bind(this);
|
||||
|
||||
if ("window" in options) this._attach(options.window);
|
||||
},
|
||||
|
||||
_setListeners: function(options) {
|
||||
if ('onError' in options)
|
||||
this.on('error', options.onError);
|
||||
if ('onMessage' in options)
|
||||
this.on('message', options.onMessage);
|
||||
if ('onDetach' in options)
|
||||
this.on('detach', options.onDetach);
|
||||
},
|
||||
|
||||
_attach: function(window) {
|
||||
this._window = window;
|
||||
// Track document unload to destroy this worker.
|
||||
// We can't watch for unload event on page's window object as it
|
||||
// prevents bfcache from working:
|
||||
// https://developer.mozilla.org/En/Working_with_BFCache
|
||||
this._windowID = getInnerId(this._window);
|
||||
observers.on("inner-window-destroyed", this._documentUnload);
|
||||
|
||||
// Listen to pagehide event in order to freeze the content script
|
||||
// while the document is frozen in bfcache:
|
||||
this._window.addEventListener("pageshow", this._pageShow, true);
|
||||
this._window.addEventListener("pagehide", this._pageHide, true);
|
||||
|
||||
// will set this._contentWorker pointing to the private API:
|
||||
this._contentWorker = WorkerSandbox(this);
|
||||
|
||||
// Mainly enable worker.port.emit to send event to the content worker
|
||||
this._inited = true;
|
||||
this._frozen = false;
|
||||
|
||||
// Process all events and messages that were fired before the
|
||||
// worker was initialized.
|
||||
this._earlyEvents.forEach((function (args) {
|
||||
processMessage.apply(this, args);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_documentUnload: function _documentUnload({ subject, data }) {
|
||||
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (innerWinID != this._windowID) return false;
|
||||
this._workerCleanup();
|
||||
return true;
|
||||
},
|
||||
|
||||
_pageShow: function _pageShow() {
|
||||
this._contentWorker.emitSync("pageshow");
|
||||
this._emit("pageshow");
|
||||
this._frozen = false;
|
||||
},
|
||||
|
||||
_pageHide: function _pageHide() {
|
||||
this._contentWorker.emitSync("pagehide");
|
||||
this._emit("pagehide");
|
||||
this._frozen = true;
|
||||
},
|
||||
|
||||
get url() {
|
||||
// this._window will be null after detach
|
||||
return this._window ? this._window.document.location.href : null;
|
||||
},
|
||||
|
||||
get tab() {
|
||||
// this._window will be null after detach
|
||||
if (this._window)
|
||||
return getTabForWindow(this._window);
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells content worker to unload itself and
|
||||
* removes all the references from itself.
|
||||
*/
|
||||
destroy: function destroy() {
|
||||
this._workerCleanup();
|
||||
this._inited = true;
|
||||
this._removeAllListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all internal references to the attached document
|
||||
* Tells _port to unload itself and removes all the references from itself.
|
||||
*/
|
||||
_workerCleanup: function _workerCleanup() {
|
||||
// maybe unloaded before content side is created
|
||||
// As Symbiont call worker.constructor on document load
|
||||
if (this._contentWorker)
|
||||
this._contentWorker.destroy();
|
||||
this._contentWorker = null;
|
||||
if (this._window) {
|
||||
this._window.removeEventListener("pageshow", this._pageShow, true);
|
||||
this._window.removeEventListener("pagehide", this._pageHide, true);
|
||||
}
|
||||
this._window = null;
|
||||
// This method may be called multiple times,
|
||||
// avoid dispatching `detach` event more than once
|
||||
if (this._windowID) {
|
||||
this._windowID = null;
|
||||
observers.off("inner-window-destroyed", this._documentUnload);
|
||||
this._earlyEvents.length = 0;
|
||||
this._emit("detach");
|
||||
}
|
||||
this._inited = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Receive an event from the content script that need to be sent to
|
||||
* worker.port. Provide a way for composed object to catch all events.
|
||||
*/
|
||||
_onContentScriptEvent: function _onContentScriptEvent() {
|
||||
this._port._emit.apply(this._port, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reference to the content side of the worker.
|
||||
* @type {WorkerGlobalScope}
|
||||
*/
|
||||
_contentWorker: null,
|
||||
|
||||
/**
|
||||
* Reference to the window that is accessible from
|
||||
* the content scripts.
|
||||
* @type {Object}
|
||||
*/
|
||||
_window: null,
|
||||
|
||||
/**
|
||||
* Flag to enable `addon` object injection in document. (bug 612726)
|
||||
* @type {Boolean}
|
||||
*/
|
||||
_injectInDocument: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Fired from postMessage and _emitEventToContent, or from the _earlyMessage
|
||||
* queue when fired before the content is loaded. Sends arguments to
|
||||
* contentWorker if able
|
||||
*/
|
||||
|
||||
function processMessage () {
|
||||
if (!this._contentWorker)
|
||||
throw new Error(ERR_DESTROYED);
|
||||
if (this._frozen)
|
||||
throw new Error(ERR_FROZEN);
|
||||
|
||||
this._contentWorker.emit.apply(null, Array.slice(arguments));
|
||||
}
|
||||
|
||||
exports.Worker = Worker;
|
@ -16,6 +16,7 @@ const { ns } = require('../core/namespace');
|
||||
const event = ns();
|
||||
|
||||
const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
|
||||
exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;
|
||||
|
||||
// Utility function to access given event `target` object's event listeners for
|
||||
// the specific event `type`. If listeners for this type does not exists they
|
||||
@ -161,9 +162,10 @@ function setListeners(target, listeners) {
|
||||
Object.keys(listeners || {}).forEach(key => {
|
||||
let match = EVENT_TYPE_PATTERN.exec(key);
|
||||
let type = match && match[1].toLowerCase();
|
||||
let listener = listeners[key];
|
||||
if (!type) return;
|
||||
|
||||
if (type && typeof(listener) === 'function')
|
||||
let listener = listeners[key];
|
||||
if (typeof(listener) === 'function')
|
||||
on(target, type, listener);
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
let { emit, on, once, off } = require("./core");
|
||||
let { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core");
|
||||
|
||||
// This module provides set of high order function for working with event
|
||||
// streams (streams in a NodeJS style that dispatch data, end and error
|
||||
@ -255,3 +255,21 @@ Reactor.prototype.run = function(input) {
|
||||
this.onStart(input.value);
|
||||
};
|
||||
exports.Reactor = Reactor;
|
||||
|
||||
/**
|
||||
* Takes an object used as options with potential keys like 'onMessage',
|
||||
* used to be called `require('sdk/event/core').setListeners` on.
|
||||
* This strips all keys that would trigger a listener to be set.
|
||||
*
|
||||
* @params {Object} object
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function stripListeners (object) {
|
||||
return Object.keys(object).reduce((agg, key) => {
|
||||
if (!EVENT_TYPE_PATTERN.test(key))
|
||||
agg[key] = object[key];
|
||||
return agg;
|
||||
}, {});
|
||||
}
|
||||
exports.stripListeners = stripListeners;
|
||||
|
@ -1,16 +1,15 @@
|
||||
/* 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 observers = require("../deprecated/observer-service");
|
||||
const { on } = require("../system/events");
|
||||
const core = require("./core");
|
||||
const { id: jetpackId} = require('../self');
|
||||
|
||||
const OPTIONS_DISPLAYED = "addon-options-displayed";
|
||||
|
||||
function onOptionsDisplayed(document, addonId) {
|
||||
function onOptionsDisplayed({ subjec: document, data: addonId }) {
|
||||
if (addonId !== jetpackId)
|
||||
return;
|
||||
let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
|
||||
@ -40,5 +39,4 @@ function onOptionsDisplayed(document, addonId) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observers.add(OPTIONS_DISPLAYED, onOptionsDisplayed);
|
||||
on(OPTIONS_DISPLAYED, onOptionsDisplayed);
|
||||
|
@ -7,7 +7,7 @@ module.metadata = {
|
||||
"stability": "stable"
|
||||
};
|
||||
|
||||
const observers = require('./deprecated/observer-service');
|
||||
const observers = require('./system/events');
|
||||
const { Loader, validationAttributes } = require('./content/loader');
|
||||
const { Worker } = require('./content/worker');
|
||||
const { Registry } = require('./util/registry');
|
||||
@ -100,7 +100,7 @@ const PageMod = Loader.compose(EventEmitter, {
|
||||
|
||||
let include = options.include;
|
||||
let rules = this.include = Rules();
|
||||
|
||||
|
||||
if (!include)
|
||||
throw new Error('The `include` option must always contain atleast one rule');
|
||||
|
||||
@ -217,13 +217,13 @@ const PageModManager = Registry.resolve({
|
||||
}).compose({
|
||||
constructor: function PageModRegistry(constructor) {
|
||||
this._init(PageMod);
|
||||
observers.add(
|
||||
observers.on(
|
||||
'document-element-inserted',
|
||||
this._onContentWindow = this._onContentWindow.bind(this)
|
||||
);
|
||||
},
|
||||
_destructor: function _destructor() {
|
||||
observers.remove('document-element-inserted', this._onContentWindow);
|
||||
observers.off('document-element-inserted', this._onContentWindow);
|
||||
this._removeAllListeners();
|
||||
|
||||
// We need to do some cleaning er PageMods, like unregistering any
|
||||
@ -234,7 +234,7 @@ const PageModManager = Registry.resolve({
|
||||
|
||||
this._registryDestructor();
|
||||
},
|
||||
_onContentWindow: function _onContentWindow(document) {
|
||||
_onContentWindow: function _onContentWindow({ subject: document }) {
|
||||
let window = document.defaultView;
|
||||
// XML documents don't have windows, and we don't yet support them.
|
||||
if (!window)
|
||||
|
@ -9,8 +9,9 @@ module.metadata = {
|
||||
|
||||
const { Class } = require('./core/heritage');
|
||||
const { on, emit, off, setListeners } = require('./event/core');
|
||||
const { filter, pipe, map, merge: streamMerge } = require('./event/utils');
|
||||
const { WorkerHost, Worker, detach, attach, destroy } = require('./worker/utils');
|
||||
const { filter, pipe, map, merge: streamMerge, stripListeners } = require('./event/utils');
|
||||
const { detach, attach, destroy, WorkerHost } = require('./content/utils');
|
||||
const { Worker } = require('./content/worker');
|
||||
const { Disposable } = require('./core/disposable');
|
||||
const { EventTarget } = require('./event/target');
|
||||
const { unload } = require('./system/unload');
|
||||
@ -66,8 +67,8 @@ function disableScript (page) {
|
||||
|
||||
function Allow (page) {
|
||||
return {
|
||||
get script() getDocShell(viewFor(page)).allowJavascript,
|
||||
set script(value) value ? enableScript(page) : disableScript(page)
|
||||
get script() { return getDocShell(viewFor(page)).allowJavascript; },
|
||||
set script(value) { return value ? enableScript(page) : disableScript(page); }
|
||||
};
|
||||
}
|
||||
|
||||
@ -89,7 +90,6 @@ const Page = Class({
|
||||
setup: function Page(options) {
|
||||
let page = this;
|
||||
options = pageContract(options);
|
||||
setListeners(this, options);
|
||||
let view = makeFrame(window.document, {
|
||||
nodeName: 'iframe',
|
||||
type: 'content',
|
||||
@ -100,12 +100,15 @@ const Page = Class({
|
||||
});
|
||||
|
||||
['contentScriptFile', 'contentScript', 'contentScriptWhen']
|
||||
.forEach(function (prop) page[prop] = options[prop]);
|
||||
.forEach(prop => page[prop] = options[prop]);
|
||||
|
||||
views.set(this, view);
|
||||
pages.set(view, this);
|
||||
|
||||
let worker = new Worker(options);
|
||||
// Set listeners on the {Page} object itself, not the underlying worker,
|
||||
// like `onMessage`, as it gets piped
|
||||
setListeners(this, options);
|
||||
let worker = new Worker(stripListeners(options));
|
||||
workers.set(this, worker);
|
||||
pipe(worker, this);
|
||||
|
||||
@ -114,7 +117,7 @@ const Page = Class({
|
||||
this.rules.add.apply(this.rules, [].concat(this.include || options.include));
|
||||
}
|
||||
},
|
||||
get allow() Allow(this),
|
||||
get allow() { return Allow(this); },
|
||||
set allow(value) {
|
||||
let allowJavascript = pageContract({ allow: value }).allow.script;
|
||||
return allowJavascript ? enableScript(this) : disableScript(this);
|
||||
@ -133,7 +136,7 @@ const Page = Class({
|
||||
views.delete(this);
|
||||
destroy(workers.get(this));
|
||||
},
|
||||
toString: function () '[object Page]'
|
||||
toString: function () { return '[object Page]' }
|
||||
});
|
||||
|
||||
exports.Page = Page;
|
||||
|
@ -19,8 +19,8 @@ const { isPrivateBrowsingSupported } = require('./self');
|
||||
const { isWindowPBSupported } = require('./private-browsing/utils');
|
||||
const { Class } = require("./core/heritage");
|
||||
const { merge } = require("./util/object");
|
||||
const { WorkerHost, Worker, detach, attach, destroy,
|
||||
requiresAddonGlobal } = require("./worker/utils");
|
||||
const { WorkerHost, detach, attach, destroy } = require("./content/utils");
|
||||
const { Worker } = require("./content/worker");
|
||||
const { Disposable } = require("./core/disposable");
|
||||
const { contract: loaderContract } = require("./content/loader");
|
||||
const { contract } = require("./util/contract");
|
||||
@ -29,7 +29,7 @@ const { EventTarget } = require("./event/target");
|
||||
const domPanel = require("./panel/utils");
|
||||
const { events } = require("./panel/events");
|
||||
const systemEvents = require("./system/events");
|
||||
const { filter, pipe } = require("./event/utils");
|
||||
const { filter, pipe, stripListeners } = require("./event/utils");
|
||||
const { getNodeView, getActiveView } = require("./view/core");
|
||||
const { isNil, isObject } = require("./lang/type");
|
||||
const { getAttachEventType } = require("./content/utils");
|
||||
@ -117,8 +117,6 @@ const Panel = Class({
|
||||
}, panelContract(options));
|
||||
models.set(this, model);
|
||||
|
||||
// Setup listeners.
|
||||
setListeners(this, options);
|
||||
|
||||
// Setup view
|
||||
let view = domPanel.make();
|
||||
@ -130,7 +128,9 @@ const Panel = Class({
|
||||
|
||||
setupAutoHide(this);
|
||||
|
||||
let worker = new Worker(options);
|
||||
// Setup listeners.
|
||||
setListeners(this, options);
|
||||
let worker = new Worker(stripListeners(options));
|
||||
workers.set(this, worker);
|
||||
|
||||
// pipe events from worker to a panel.
|
||||
|
@ -8,10 +8,9 @@ module.metadata = {
|
||||
};
|
||||
|
||||
const { emit, off } = require("./event/core");
|
||||
const { when: unload } = require("./system/unload");
|
||||
const { PrefsTarget } = require("./preferences/event-target");
|
||||
const { id } = require("./self");
|
||||
const observers = require("./deprecated/observer-service");
|
||||
const { on } = require("./system/events");
|
||||
|
||||
const ADDON_BRANCH = "extensions." + id + ".";
|
||||
const BUTTON_PRESSED = id + "-cmdPressed";
|
||||
@ -19,14 +18,9 @@ const BUTTON_PRESSED = id + "-cmdPressed";
|
||||
const target = PrefsTarget({ branchName: ADDON_BRANCH });
|
||||
|
||||
// Listen to clicks on buttons
|
||||
function buttonClick(subject, data) {
|
||||
function buttonClick({ data }) {
|
||||
emit(target, data);
|
||||
}
|
||||
observers.add(BUTTON_PRESSED, buttonClick);
|
||||
|
||||
// Make sure we cleanup listeners on unload.
|
||||
unload(function() {
|
||||
observers.remove(BUTTON_PRESSED, buttonClick);
|
||||
});
|
||||
on(BUTTON_PRESSED, buttonClick);
|
||||
|
||||
module.exports = target;
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* 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 = {
|
||||
@ -12,17 +11,25 @@ const { Cc, Ci, Cu } = require("chrome");
|
||||
const { Loader } = require('./loader');
|
||||
const { serializeStack, parseStack } = require("toolkit/loader");
|
||||
const { setTimeout } = require('../timers');
|
||||
const memory = require('../deprecated/memory');
|
||||
const { PlainTextConsole } = require("../console/plain-text");
|
||||
const { when: unload } = require("../system/unload");
|
||||
const { format, fromException } = require("../console/traceback");
|
||||
const system = require("../system");
|
||||
const memory = require('../deprecated/memory');
|
||||
const { gc: gcPromise } = require('./memory');
|
||||
const { defer } = require('../core/promise');
|
||||
|
||||
// Trick manifest builder to make it think we need these modules ?
|
||||
const unit = require("../deprecated/unit-test");
|
||||
const test = require("../../test");
|
||||
const url = require("../url");
|
||||
|
||||
function emptyPromise() {
|
||||
let { promise, resolve } = defer();
|
||||
resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
var cService = Cc['@mozilla.org/consoleservice;1'].getService()
|
||||
.QueryInterface(Ci.nsIConsoleService);
|
||||
|
||||
@ -143,36 +150,30 @@ function dictDiff(last, curr) {
|
||||
}
|
||||
|
||||
function reportMemoryUsage() {
|
||||
memory.gc();
|
||||
|
||||
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
|
||||
// Bug 916501: this code is *so* bogus -- nsIMemoryReporter changed its |memoryUsed|
|
||||
// field to |amount| *years* ago, and even bigger changes have happened
|
||||
// since -- that it must just never be run.
|
||||
var reporters = mgr.enumerateReporters();
|
||||
if (reporters.hasMoreElements())
|
||||
print("\n");
|
||||
|
||||
while (reporters.hasMoreElements()) {
|
||||
var reporter = reporters.getNext();
|
||||
reporter.QueryInterface(Ci.nsIMemoryReporter);
|
||||
print(reporter.description + ": " + reporter.memoryUsed + "\n");
|
||||
if (!profileMemory) {
|
||||
return emptyPromise();
|
||||
}
|
||||
|
||||
var weakrefs = [info.weakref.get()
|
||||
for each (info in memory.getObjects())];
|
||||
weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
|
||||
print("Tracked memory objects in testing sandbox: " +
|
||||
weakrefs.length + "\n");
|
||||
return gcPromise().then((function () {
|
||||
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager);
|
||||
let count = 0;
|
||||
function logReporter(process, path, kind, units, amount, description) {
|
||||
print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n");
|
||||
}
|
||||
mgr.getReportsForThisProcess(logReporter, null);
|
||||
|
||||
var weakrefs = [info.weakref.get()
|
||||
for each (info in memory.getObjects())];
|
||||
weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
|
||||
print("Tracked memory objects in testing sandbox: " + weakrefs.length + "\n");
|
||||
}));
|
||||
}
|
||||
|
||||
var gWeakrefInfo;
|
||||
|
||||
function checkMemory() {
|
||||
memory.gc();
|
||||
Cu.schedulePreciseGC(function () {
|
||||
return gcPromise().then(_ => {
|
||||
let leaks = getPotentialLeaks();
|
||||
|
||||
let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
|
||||
@ -188,12 +189,12 @@ function checkMemory() {
|
||||
|
||||
for (let url of windowURLs)
|
||||
console.warn("LEAKED", leaks.windows[url]);
|
||||
|
||||
showResults();
|
||||
});
|
||||
}).then(showResults);
|
||||
}
|
||||
|
||||
function showResults() {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
if (gWeakrefInfo) {
|
||||
gWeakrefInfo.forEach(
|
||||
function(info) {
|
||||
@ -211,6 +212,9 @@ function showResults() {
|
||||
}
|
||||
|
||||
onDone(results);
|
||||
|
||||
resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
@ -250,7 +254,8 @@ function cleanup() {
|
||||
loader = null;
|
||||
|
||||
memory.gc();
|
||||
} catch (e) {
|
||||
}
|
||||
catch (e) {
|
||||
results.failed++;
|
||||
console.error("unload.send() threw an exception.");
|
||||
console.exception(e);
|
||||
@ -333,7 +338,7 @@ function getPotentialLeaks() {
|
||||
console.error("Unable to parse compartment detail " + matches[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let item = {
|
||||
path: matches[1],
|
||||
principal: details[1],
|
||||
@ -349,8 +354,7 @@ function getPotentialLeaks() {
|
||||
return;
|
||||
}
|
||||
|
||||
matches = windowRegexp.exec(path);
|
||||
if (matches) {
|
||||
if (matches = windowRegexp.exec(path)) {
|
||||
if (matches[1] in windows)
|
||||
return;
|
||||
|
||||
@ -374,10 +378,9 @@ function getPotentialLeaks() {
|
||||
}
|
||||
}
|
||||
|
||||
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
|
||||
getService(Ci.nsIMemoryReporterManager);
|
||||
|
||||
mgr.getReportsForThisProcess(logReporter, null);
|
||||
Cc["@mozilla.org/memory-reporter-manager;1"]
|
||||
.getService(Ci.nsIMemoryReporterManager)
|
||||
.getReportsForThisProcess(logReporter, null);
|
||||
|
||||
return { compartments: compartments, windows: windows };
|
||||
}
|
||||
@ -387,22 +390,28 @@ function nextIteration(tests) {
|
||||
results.passed += tests.passed;
|
||||
results.failed += tests.failed;
|
||||
|
||||
if (profileMemory)
|
||||
reportMemoryUsage();
|
||||
|
||||
let testRun = [];
|
||||
for each (let test in tests.testRunSummary) {
|
||||
let testCopy = {};
|
||||
for (let info in test) {
|
||||
testCopy[info] = test[info];
|
||||
reportMemoryUsage().then(_ => {
|
||||
let testRun = [];
|
||||
for each (let test in tests.testRunSummary) {
|
||||
let testCopy = {};
|
||||
for (let info in test) {
|
||||
testCopy[info] = test[info];
|
||||
}
|
||||
testRun.push(testCopy);
|
||||
}
|
||||
testRun.push(testCopy);
|
||||
}
|
||||
|
||||
results.testRuns.push(testRun);
|
||||
iterationsLeft--;
|
||||
results.testRuns.push(testRun);
|
||||
iterationsLeft--;
|
||||
|
||||
checkForEnd();
|
||||
})
|
||||
}
|
||||
else {
|
||||
checkForEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForEnd() {
|
||||
if (iterationsLeft && (!stopOnError || results.failed == 0)) {
|
||||
// Pass the loader which has a hooked console that doesn't dispatch
|
||||
// errors to the JS console and avoid firing false alarm in our
|
||||
|
17
addon-sdk/source/lib/sdk/test/memory.js
Normal file
17
addon-sdk/source/lib/sdk/test/memory.js
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const memory = require('../deprecated/memory');
|
||||
const { defer } = require('../core/promise');
|
||||
|
||||
function gc() {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
Cu.forceGC();
|
||||
memory.gc();
|
||||
|
||||
Cu.schedulePreciseGC(_ => resolve());
|
||||
|
||||
return promise;
|
||||
}
|
||||
exports.gc = gc;
|
@ -10,6 +10,17 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
// Because Firefox Holly, we still need to check if `CustomizableUI` is
|
||||
// available. Once Australis will officially land, we can safely remove it.
|
||||
// See Bug 959142
|
||||
try {
|
||||
require('chrome').Cu.import('resource:///modules/CustomizableUI.jsm', {});
|
||||
}
|
||||
catch (e) {
|
||||
throw Error('Unsupported Application: The module ' + module.id +
|
||||
' does not support this application.');
|
||||
}
|
||||
|
||||
const { Class } = require('../../core/heritage');
|
||||
const { merge } = require('../../util/object');
|
||||
const { Disposable } = require('../../core/disposable');
|
||||
|
@ -10,6 +10,17 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
// Because Firefox Holly, we still need to check if `CustomizableUI` is
|
||||
// available. Once Australis will officially land, we can safely remove it.
|
||||
// See Bug 959142
|
||||
try {
|
||||
require('chrome').Cu.import('resource:///modules/CustomizableUI.jsm', {});
|
||||
}
|
||||
catch (e) {
|
||||
throw Error('Unsupported Application: The module ' + module.id +
|
||||
' does not support this application.');
|
||||
}
|
||||
|
||||
const { Class } = require('../../core/heritage');
|
||||
const { merge } = require('../../util/object');
|
||||
const { Disposable } = require('../../core/disposable');
|
||||
|
@ -14,7 +14,8 @@ const { Cu } = require('chrome');
|
||||
const { on, off, emit } = require('../../event/core');
|
||||
|
||||
const { id: addonID, data } = require('sdk/self');
|
||||
const buttonPrefix = 'button--' + addonID.replace(/@/g, '-at-');
|
||||
const buttonPrefix =
|
||||
'button--' + addonID.toLowerCase().replace(/[^a-z0-9-_]/g, '');
|
||||
|
||||
const { isObject } = require('../../lang/type');
|
||||
|
||||
|
@ -10,6 +10,17 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
// Because Firefox Holly, we still need to check if `CustomizableUI` is
|
||||
// available. Once Australis will officially land, we can safely remove it.
|
||||
// See Bug 959142
|
||||
try {
|
||||
require("chrome").Cu.import("resource:///modules/CustomizableUI.jsm", {});
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unsupported Application: The module" + module.id +
|
||||
" does not support this application.");
|
||||
}
|
||||
|
||||
require("./frame/view");
|
||||
const { Frame } = require("./frame/model");
|
||||
|
||||
|
@ -24,7 +24,7 @@ const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = requ
|
||||
const { ns } = require('../core/namespace');
|
||||
const { remove: removeFromArray } = require('../util/array');
|
||||
const { show, hide, toggle } = require('./sidebar/actions');
|
||||
const { Worker: WorkerTrait } = require('../content/worker');
|
||||
const { Worker } = require('../content/worker');
|
||||
const { contract: sidebarContract } = require('./sidebar/contract');
|
||||
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
|
||||
const { defer } = require('../core/promise');
|
||||
@ -34,12 +34,6 @@ const { ensure } = require('../system/unload');
|
||||
const { identify } = require('./id');
|
||||
const { uuid } = require('../util/uuid');
|
||||
|
||||
const Worker = WorkerTrait.resolve({
|
||||
_injectInDocument: '__injectInDocument'
|
||||
}).compose({
|
||||
get _injectInDocument() true
|
||||
});
|
||||
|
||||
const sidebarNS = ns();
|
||||
|
||||
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
|
||||
@ -118,7 +112,8 @@ const Sidebar = Class({
|
||||
}
|
||||
|
||||
let worker = windowNS(window).worker = Worker({
|
||||
window: panelBrowser.contentWindow
|
||||
window: panelBrowser.contentWindow,
|
||||
injectInDocument: true
|
||||
});
|
||||
|
||||
function onWebPanelSidebarUnload() {
|
||||
|
@ -10,6 +10,17 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
// Because Firefox Holly, we still need to check if `CustomizableUI` is
|
||||
// available. Once Australis will officially land, we can safely remove it.
|
||||
// See Bug 959142
|
||||
try {
|
||||
require("chrome").Cu.import("resource:///modules/CustomizableUI.jsm", {});
|
||||
}
|
||||
catch (e) {
|
||||
throw Error("Unsupported Application: The module" + module.id +
|
||||
" does not support this application.");
|
||||
}
|
||||
|
||||
const { Toolbar } = require("./toolbar/model");
|
||||
require("./toolbar/view");
|
||||
|
||||
|
@ -9,7 +9,6 @@ module.metadata = {
|
||||
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const array = require('../util/array');
|
||||
const observers = require('../deprecated/observer-service');
|
||||
const { defer } = require('sdk/core/promise');
|
||||
|
||||
const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
|
||||
@ -151,25 +150,6 @@ exports.getWindowLoadingContext = getWindowLoadingContext;
|
||||
const isTopLevel = window => window && getToplevelWindow(window) === window;
|
||||
exports.isTopLevel = isTopLevel;
|
||||
|
||||
/**
|
||||
* Removes given window from the application's window registry. Unless
|
||||
* `options.close` is `false` window is automatically closed on application
|
||||
* quit.
|
||||
* @params {nsIDOMWindow} window
|
||||
* @params {Boolean} options.close
|
||||
*/
|
||||
function backgroundify(window, options) {
|
||||
let base = getBaseWindow(window);
|
||||
base.visibility = false;
|
||||
base.enabled = false;
|
||||
appShellService.unregisterTopLevelWindow(getXULWindow(window));
|
||||
if (!options || options.close !== false)
|
||||
observers.add('quit-application-granted', window.close.bind(window));
|
||||
|
||||
return window;
|
||||
}
|
||||
exports.backgroundify = backgroundify;
|
||||
|
||||
/**
|
||||
* Takes hash of options and serializes it to a features string that
|
||||
* can be used passed to `window.open`. For more details on features string see:
|
||||
|
@ -2,102 +2,18 @@
|
||||
* 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";
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
'stability': 'deprecated'
|
||||
};
|
||||
|
||||
// This module attempts to hide trait based nature of the worker so that
|
||||
// code depending on workers could be de-trait-ified without changing worker
|
||||
// implementation.
|
||||
const {
|
||||
requiresAddonGlobal, attach, detach, destroy, WorkerHost
|
||||
} = require('../content/utils');
|
||||
|
||||
const { Worker: WorkerTrait } = require("../content/worker");
|
||||
const { Loader } = require("../content/loader");
|
||||
const { merge } = require("../util/object");
|
||||
const { emit } = require("../event/core");
|
||||
|
||||
let assetsURI = require("../self").data.url();
|
||||
let isArray = Array.isArray;
|
||||
|
||||
function isAddonContent({ contentURL }) {
|
||||
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
|
||||
}
|
||||
|
||||
function hasContentScript({ contentScript, contentScriptFile }) {
|
||||
return (isArray(contentScript) ? contentScript.length > 0 :
|
||||
!!contentScript) ||
|
||||
(isArray(contentScriptFile) ? contentScriptFile.length > 0 :
|
||||
!!contentScriptFile);
|
||||
}
|
||||
|
||||
function requiresAddonGlobal(model) {
|
||||
return isAddonContent(model) && !hasContentScript(model);
|
||||
}
|
||||
exports.requiresAddonGlobal = requiresAddonGlobal;
|
||||
|
||||
|
||||
const LegacyWorker = WorkerTrait.compose(Loader).resolve({
|
||||
_setListeners: "__setListeners",
|
||||
_injectInDocument: "__injectInDocument",
|
||||
contentURL: "__contentURL"
|
||||
}).compose({
|
||||
_setListeners: function() {},
|
||||
get contentURL() this._window.document.URL,
|
||||
get _injectInDocument() requiresAddonGlobal(this),
|
||||
attach: function(window) this._attach(window),
|
||||
detach: function() this._workerCleanup()
|
||||
});
|
||||
|
||||
// Weak map that stores mapping between regular worker instances and
|
||||
// legacy trait based worker instances.
|
||||
let traits = new WeakMap();
|
||||
|
||||
function traitFor(worker) traits.get(worker, null);
|
||||
|
||||
function WorkerHost(workerFor) {
|
||||
// Define worker properties that just proxy to a wrapped trait.
|
||||
return ["postMessage", "port", "url", "tab"].reduce(function(proto, name) {
|
||||
Object.defineProperty(proto, name, {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get: function() traitFor(workerFor(this))[name],
|
||||
set: function(value) traitFor(workerFor(this))[name] = value
|
||||
});
|
||||
return proto;
|
||||
}, {});
|
||||
}
|
||||
exports.WorkerHost = WorkerHost;
|
||||
|
||||
// Type representing worker instance.
|
||||
function Worker(options) {
|
||||
let worker = Object.create(Worker.prototype);
|
||||
let trait = new LegacyWorker(options);
|
||||
["pageshow", "pagehide", "detach", "message", "error"].forEach(function(key) {
|
||||
trait.on(key, function() {
|
||||
emit.apply(emit, [worker, key].concat(Array.slice(arguments)));
|
||||
});
|
||||
});
|
||||
traits.set(worker, trait);
|
||||
return worker;
|
||||
}
|
||||
exports.Worker = Worker;
|
||||
|
||||
function detach(worker) {
|
||||
let trait = traitFor(worker);
|
||||
if (trait) trait.detach();
|
||||
}
|
||||
exports.detach = detach;
|
||||
|
||||
function attach(worker, window) {
|
||||
let trait = traitFor(worker);
|
||||
// Cleanup the worker before injecting the content script into a new document.
|
||||
trait.attach(window);
|
||||
}
|
||||
exports.attach = attach;
|
||||
|
||||
function destroy(worker) {
|
||||
let trait = traitFor(worker);
|
||||
if (trait) trait.destroy();
|
||||
}
|
||||
exports.destroy = destroy;
|
||||
exports.requiresAddonGlobal = requiresAddonGlobal;
|
||||
|
@ -613,8 +613,20 @@ const Require = iced(function Require(loader, requirer) {
|
||||
// We also freeze module to prevent it from further changes
|
||||
// at runtime.
|
||||
if (!(uri in modules)) {
|
||||
// Many of the loader's functionalities are dependent
|
||||
// on modules[uri] being set before loading, so we set it and
|
||||
// remove it if we have any errors.
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
freeze(load(loader, module));
|
||||
try {
|
||||
freeze(load(loader, module));
|
||||
}
|
||||
catch (e) {
|
||||
// Clear out modules cache so we can throw on a second invalid require
|
||||
delete modules[uri];
|
||||
// Also clear out the Sandbox that was created
|
||||
delete loader.sandboxes[uri];
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
|
@ -15,7 +15,6 @@
|
||||
"loader": "sdk/loader/loader",
|
||||
"memory": "sdk/deprecated/memory",
|
||||
"namespace": "sdk/core/namespace",
|
||||
"observer-service": "sdk/deprecated/observer-service",
|
||||
"preferences-service": "sdk/preferences/service",
|
||||
"promise": "sdk/core/promise",
|
||||
"system": "sdk/system",
|
||||
|
@ -96,9 +96,6 @@ exports["test compatibility"] = function(assert) {
|
||||
assert.equal(require("xhr"),
|
||||
require("sdk/net/xhr"), "sdk/io/xhr -> xhr");
|
||||
|
||||
assert.equal(require("observer-service"),
|
||||
require("sdk/deprecated/observer-service"), "sdk/deprecated/observer-service -> observer-service");
|
||||
|
||||
assert.equal(require("private-browsing"),
|
||||
require("sdk/private-browsing"), "sdk/private-browsing -> private-browsing");
|
||||
|
||||
@ -147,9 +144,6 @@ exports["test compatibility"] = function(assert) {
|
||||
assert.equal(require("tabs/utils"),
|
||||
require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
|
||||
|
||||
assert.equal(require("app-strings"),
|
||||
require("sdk/deprecated/app-strings"), "sdk/deprecated/app-strings -> app-strings");
|
||||
|
||||
assert.equal(require("dom/events"),
|
||||
require("sdk/dom/events"), "sdk/dom/events -> dom/events");
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const { data } = require("sdk/self");
|
||||
const { Symbiont } = require("sdk/content/symbiont");
|
||||
const { Symbiont } = require("sdk/deprecated/symbiont");
|
||||
|
||||
exports["test:direct communication with trusted document"] = function(assert, done) {
|
||||
let worker = Symbiont({
|
||||
|
1
addon-sdk/source/test/fixtures/loader/missing-twice/file.json
vendored
Normal file
1
addon-sdk/source/test/fixtures/loader/missing-twice/file.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
an invalid json file
|
32
addon-sdk/source/test/fixtures/loader/missing-twice/main.js
vendored
Normal file
32
addon-sdk/source/test/fixtures/loader/missing-twice/main.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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';
|
||||
|
||||
try {
|
||||
require('./not-found');
|
||||
}
|
||||
catch (e1) {
|
||||
exports.firstError = e1;
|
||||
// It should throw again and not be cached
|
||||
try {
|
||||
require('./not-found');
|
||||
}
|
||||
catch (e2) {
|
||||
exports.secondError = e2;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
require('./file.json');
|
||||
}
|
||||
catch (e) {
|
||||
exports.invalidJSON1 = e;
|
||||
try {
|
||||
require('./file.json');
|
||||
}
|
||||
catch (e) {
|
||||
exports.invalidJSON2 = e;
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ const timer = require('sdk/timers');
|
||||
const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
|
||||
const { windows, onFocus, getMostRecentBrowserWindow } = require('sdk/window/utils');
|
||||
const { open, focus, close } = require('sdk/window/helpers');
|
||||
const { StringBundle } = require('sdk/deprecated/app-strings');
|
||||
const tabs = require('sdk/tabs');
|
||||
const { browserWindows } = require('sdk/windows');
|
||||
const { set: setPref } = require("sdk/preferences/service");
|
||||
@ -19,13 +18,17 @@ const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAA
|
||||
|
||||
// Bug 682681 - tab.title should never be empty
|
||||
exports.testBug682681_aboutURI = function(assert, done) {
|
||||
let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties');
|
||||
let url = 'chrome://browser/locale/tabbrowser.properties';
|
||||
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle(url);
|
||||
let emptyTabTitle = stringBundle.GetStringFromName('tabs.emptyTabTitle');
|
||||
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
|
||||
assert.equal(tab.title,
|
||||
tabStrings.get('tabs.emptyTabTitle'),
|
||||
emptyTabTitle,
|
||||
"title of about: tab is not blank");
|
||||
|
||||
tab.close(done);
|
||||
|
@ -1,12 +1,11 @@
|
||||
/* 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 { Cc, Ci, Cu } = require("chrome");
|
||||
const AddonInstaller = require("sdk/addon/installer");
|
||||
const observers = require("sdk/deprecated/observer-service");
|
||||
const { on, off } = require("sdk/system/events");
|
||||
const { setTimeout } = require("sdk/timers");
|
||||
const tmp = require("sdk/test/tmp-file");
|
||||
const system = require("sdk/system");
|
||||
@ -20,10 +19,10 @@ exports["test Install"] = function (assert, done) {
|
||||
|
||||
// Save all events distpatched by bootstrap.js of the installed addon
|
||||
let events = [];
|
||||
function eventsObserver(subject, data) {
|
||||
function eventsObserver({ data }) {
|
||||
events.push(data);
|
||||
}
|
||||
observers.add("addon-install-unit-test", eventsObserver, false);
|
||||
on("addon-install-unit-test", eventsObserver);
|
||||
|
||||
// Install the test addon
|
||||
AddonInstaller.install(ADDON_PATH).then(
|
||||
@ -39,13 +38,13 @@ exports["test Install"] = function (assert, done) {
|
||||
JSON.stringify(expectedEvents),
|
||||
"addon's bootstrap.js functions have been called");
|
||||
|
||||
observers.remove("addon-install-unit-test", eventsObserver);
|
||||
off("addon-install-unit-test", eventsObserver);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function onFailure(code) {
|
||||
assert.fail("Install failed: "+code);
|
||||
observers.remove("addon-install-unit-test", eventsObserver);
|
||||
off("addon-install-unit-test", eventsObserver);
|
||||
done();
|
||||
}
|
||||
);
|
||||
@ -84,10 +83,8 @@ exports["test Update"] = function (assert, done) {
|
||||
// Save all events distpatched by bootstrap.js of the installed addon
|
||||
let events = [];
|
||||
let iteration = 1;
|
||||
function eventsObserver(subject, data) {
|
||||
events.push(data);
|
||||
}
|
||||
observers.add("addon-install-unit-test", eventsObserver);
|
||||
let eventsObserver = ({data}) => events.push(data);
|
||||
on("addon-install-unit-test", eventsObserver);
|
||||
|
||||
function onInstalled(id) {
|
||||
let prefix = "[" + iteration + "] ";
|
||||
@ -115,14 +112,14 @@ exports["test Update"] = function (assert, done) {
|
||||
JSON.stringify(expectedEvents),
|
||||
prefix + "addon's bootstrap.js functions have been called");
|
||||
|
||||
observers.remove("addon-install-unit-test", eventsObserver);
|
||||
off("addon-install-unit-test", eventsObserver);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
function onFailure(code) {
|
||||
assert.fail("Install failed: "+code);
|
||||
observers.remove("addon-install-unit-test", eventsObserver);
|
||||
off("addon-install-unit-test", eventsObserver);
|
||||
done();
|
||||
}
|
||||
|
||||
|
@ -4,31 +4,6 @@
|
||||
|
||||
const apiUtils = require("sdk/deprecated/api-utils");
|
||||
|
||||
exports.testPublicConstructor = function (assert) {
|
||||
function PrivateCtor() {}
|
||||
PrivateCtor.prototype = {};
|
||||
|
||||
let PublicCtor = apiUtils.publicConstructor(PrivateCtor);
|
||||
assert.ok(
|
||||
PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype),
|
||||
"PrivateCtor.prototype should be prototype of PublicCtor.prototype"
|
||||
);
|
||||
|
||||
function testObj(useNew) {
|
||||
let obj = useNew ? new PublicCtor() : PublicCtor();
|
||||
assert.ok(obj instanceof PublicCtor,
|
||||
"Object should be instance of PublicCtor");
|
||||
assert.ok(obj instanceof PrivateCtor,
|
||||
"Object should be instance of PrivateCtor");
|
||||
assert.ok(PublicCtor.prototype.isPrototypeOf(obj),
|
||||
"PublicCtor's prototype should be prototype of object");
|
||||
assert.equal(obj.constructor, PublicCtor,
|
||||
"Object constructor should be PublicCtor");
|
||||
}
|
||||
testObj(true);
|
||||
testObj(false);
|
||||
};
|
||||
|
||||
exports.testValidateOptionsEmpty = function (assert) {
|
||||
let val = apiUtils.validateOptions(null, {});
|
||||
|
||||
|
@ -1,68 +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";
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { StringBundle } = require("sdk/deprecated/app-strings");
|
||||
|
||||
exports.testStringBundle = function(assert) {
|
||||
let url = "chrome://global/locale/security/caps.properties";
|
||||
|
||||
let strings = StringBundle(url);
|
||||
|
||||
assert.equal(strings.url, url,
|
||||
"'url' property contains correct URL of string bundle");
|
||||
|
||||
let appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
|
||||
getService(Ci.nsILocaleService).
|
||||
getApplicationLocale();
|
||||
|
||||
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle(url, appLocale);
|
||||
|
||||
let (name = "CheckMessage") {
|
||||
assert.equal(strings.get(name), stringBundle.GetStringFromName(name),
|
||||
"getting a string returns the string");
|
||||
}
|
||||
|
||||
let (name = "CreateWrapperDenied", args = ["foo"]) {
|
||||
assert.equal(strings.get(name, args),
|
||||
stringBundle.formatStringFromName(name, args, args.length),
|
||||
"getting a formatted string returns the formatted string");
|
||||
}
|
||||
|
||||
assert.throws(function () strings.get("nonexistentString"),
|
||||
RegExp("String 'nonexistentString' could not be retrieved from " +
|
||||
"the bundle due to an unknown error \\(it doesn't exist\\?\\)\\."),
|
||||
"retrieving a nonexistent string throws an exception");
|
||||
|
||||
let a = [], b = [];
|
||||
let enumerator = stringBundle.getSimpleEnumeration();
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
|
||||
a.push([elem.key, elem.value]);
|
||||
}
|
||||
|
||||
for (let key in strings) {
|
||||
b.push([ key, strings.get(key) ]);
|
||||
}
|
||||
|
||||
// Sort the arrays, because we don't assume enumeration has a set order.
|
||||
// Sort compares [key, val] as string "key,val", which sorts the way we want
|
||||
// it to, so there is no need to provide a custom compare function.
|
||||
a.sort();
|
||||
b.sort();
|
||||
|
||||
assert.equal(a.length, b.length,
|
||||
"the iterator returns the correct number of items");
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
assert.equal(a[i][0], b[i][0], "the iterated string's name is correct");
|
||||
assert.equal(a[i][1], b[i][1],
|
||||
"the iterated string's value is correct");
|
||||
}
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
@ -4,7 +4,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const { Symbiont } = require('sdk/content/symbiont');
|
||||
const { Symbiont } = require('sdk/deprecated/symbiont');
|
||||
const self = require('sdk/self');
|
||||
const fixtures = require("./fixtures");
|
||||
const { close } = require('sdk/window/helpers');
|
||||
|
@ -12,6 +12,7 @@ module.metadata = {
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { on } = require("sdk/event/core");
|
||||
const { setTimeout } = require("sdk/timers");
|
||||
const { LoaderWithHookedConsole } = require("sdk/test/loader");
|
||||
const { Worker } = require("sdk/content/worker");
|
||||
@ -116,6 +117,8 @@ exports["test:sample"] = WorkerTest(
|
||||
|
||||
assert.equal(worker.url, window.location.href,
|
||||
"worker.url works");
|
||||
assert.equal(worker.contentURL, window.location.href,
|
||||
"worker.contentURL works");
|
||||
worker.postMessage("hi!");
|
||||
}
|
||||
);
|
||||
@ -226,7 +229,7 @@ exports["test:post-json-values-only"] = WorkerTest(
|
||||
self.postMessage([ message.fun === undefined,
|
||||
typeof message.w,
|
||||
message.w && "port" in message.w,
|
||||
message.w.url,
|
||||
message.w._url,
|
||||
Array.isArray(message.array),
|
||||
JSON.stringify(message.array)]);
|
||||
});
|
||||
@ -247,6 +250,10 @@ exports["test:post-json-values-only"] = WorkerTest(
|
||||
"Array is correctly serialized");
|
||||
done();
|
||||
});
|
||||
// Add a new url property sa the Class function used by
|
||||
// Worker doesn't set enumerables to true for non-functions
|
||||
worker._url = DEFAULT_CONTENT_URL;
|
||||
|
||||
worker.postMessage({ fun: function () {}, w: worker, array: array });
|
||||
}
|
||||
);
|
||||
@ -264,7 +271,7 @@ exports["test:emit-json-values-only"] = WorkerTest(
|
||||
fun === null,
|
||||
typeof w,
|
||||
"port" in w,
|
||||
w.url,
|
||||
w._url,
|
||||
"fun" in obj,
|
||||
Object.keys(obj.dom).length,
|
||||
Array.isArray(array),
|
||||
@ -295,6 +302,9 @@ exports["test:emit-json-values-only"] = WorkerTest(
|
||||
fun: function () {},
|
||||
dom: browser.contentWindow.document.createElement("div")
|
||||
};
|
||||
// Add a new url property sa the Class function used by
|
||||
// Worker doesn't set enumerables to true for non-functions
|
||||
worker._url = DEFAULT_CONTENT_URL;
|
||||
worker.port.emit("addon-to-content", function () {}, worker, obj, array);
|
||||
}
|
||||
);
|
||||
@ -829,4 +839,37 @@ exports['test:conentScriptFile as URL instance'] = WorkerTest(
|
||||
}
|
||||
);
|
||||
|
||||
exports.testWorkerEvents = WorkerTest(DEFAULT_CONTENT_URL, function (assert, browser, done) {
|
||||
let window = browser.contentWindow;
|
||||
let events = [];
|
||||
let worker = Worker({
|
||||
window: window,
|
||||
contentScript: 'new ' + function WorkerScope() {
|
||||
self.postMessage('start');
|
||||
},
|
||||
onAttach: win => {
|
||||
events.push('attach');
|
||||
assert.pass('attach event called when attached');
|
||||
assert.equal(window, win, 'attach event passes in attached window');
|
||||
},
|
||||
onError: err => {
|
||||
assert.equal(err.message, 'Custom',
|
||||
'Error passed into error event');
|
||||
worker.detach();
|
||||
},
|
||||
onMessage: msg => {
|
||||
assert.pass('`onMessage` handles postMessage')
|
||||
throw new Error('Custom');
|
||||
},
|
||||
onDetach: _ => {
|
||||
assert.pass('`onDetach` called when worker detached');
|
||||
done();
|
||||
}
|
||||
});
|
||||
// `attach` event is called synchronously during instantiation,
|
||||
// so we can't listen to that, TODO FIX?
|
||||
// worker.on('attach', obj => console.log('attach', obj));
|
||||
});
|
||||
|
||||
|
||||
require("test").run(exports);
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const { on, once, off, emit, count, amass } = require('sdk/event/core');
|
||||
const { on, once, off, emit, count } = require('sdk/event/core');
|
||||
const { LoaderWithHookedConsole } = require("sdk/test/loader");
|
||||
|
||||
exports['test add a listener'] = function(assert) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
const { on, emit } = require("sdk/event/core");
|
||||
const { filter, map, merge, expand, pipe } = require("sdk/event/utils");
|
||||
const { filter, map, merge, expand, pipe, stripListeners } = require("sdk/event/utils");
|
||||
const $ = require("./event/helpers");
|
||||
|
||||
function isEven(x) !(x % 2)
|
||||
@ -168,7 +168,7 @@ exports["test expand"] = function(assert) {
|
||||
exports["test pipe"] = function (assert, done) {
|
||||
let src = {};
|
||||
let dest = {};
|
||||
|
||||
|
||||
let aneventCount = 0, multiargsCount = 0;
|
||||
let wildcardCount = {};
|
||||
|
||||
@ -184,7 +184,7 @@ exports["test pipe"] = function (assert, done) {
|
||||
++multiargsCount;
|
||||
check();
|
||||
});
|
||||
|
||||
|
||||
on(dest, '*', (name, ...data) => {
|
||||
wildcardCount[name] = (wildcardCount[name] || 0) + 1;
|
||||
if (name === 'multiargs') {
|
||||
@ -201,12 +201,12 @@ exports["test pipe"] = function (assert, done) {
|
||||
|
||||
for (let i = 0; i < 3; i++)
|
||||
emit(src, 'an-event', 'my-arg');
|
||||
|
||||
|
||||
emit(src, 'multiargs', 'a', 'b', 'c');
|
||||
|
||||
function check () {
|
||||
if (aneventCount === 3 && multiargsCount === 1 &&
|
||||
wildcardCount['an-event'] === 3 &&
|
||||
wildcardCount['an-event'] === 3 &&
|
||||
wildcardCount['multiargs'] === 1)
|
||||
done();
|
||||
}
|
||||
@ -237,7 +237,7 @@ exports["test pipe multiple targets"] = function (assert) {
|
||||
assert.equal(middleFired, 1, 'event passes through the middle of pipe chain');
|
||||
assert.equal(destFired, 1, 'event propagates to end of pipe chain');
|
||||
assert.equal(src2Fired, 0, 'event does not fire on alternative chain routes');
|
||||
|
||||
|
||||
emit(src2, 'ev');
|
||||
assert.equal(src2Fired, 1, 'event triggers in source in pipe chain');
|
||||
assert.equal(middleFired, 2,
|
||||
@ -245,7 +245,7 @@ exports["test pipe multiple targets"] = function (assert) {
|
||||
assert.equal(destFired, 2,
|
||||
'event propagates to end of pipe chain from different src');
|
||||
assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes');
|
||||
|
||||
|
||||
emit(middle, 'ev');
|
||||
assert.equal(middleFired, 3,
|
||||
'event triggers in source of pipe chain');
|
||||
@ -255,4 +255,28 @@ exports["test pipe multiple targets"] = function (assert) {
|
||||
assert.equal(src2Fired, 1, 'event does not fire on alternative chain routes');
|
||||
};
|
||||
|
||||
exports['test stripListeners'] = function (assert) {
|
||||
var options = {
|
||||
onAnEvent: noop1,
|
||||
onMessage: noop2,
|
||||
verb: noop1,
|
||||
value: 100
|
||||
};
|
||||
|
||||
var stripped = stripListeners(options);
|
||||
assert.ok(stripped !== options, 'stripListeners should return a new object');
|
||||
assert.equal(options.onAnEvent, noop1, 'stripListeners does not affect original');
|
||||
assert.equal(options.onMessage, noop2, 'stripListeners does not affect original');
|
||||
assert.equal(options.verb, noop1, 'stripListeners does not affect original');
|
||||
assert.equal(options.value, 100, 'stripListeners does not affect original');
|
||||
|
||||
assert.ok(!stripped.onAnEvent, 'stripListeners removes `on*` values');
|
||||
assert.ok(!stripped.onMessage, 'stripListeners removes `on*` values');
|
||||
assert.equal(stripped.verb, noop1, 'stripListeners leaves not `on*` values');
|
||||
assert.equal(stripped.value, 100, 'stripListeners leaves not `on*` values');
|
||||
|
||||
function noop1 () {}
|
||||
function noop2 () {}
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
||||
|
@ -101,6 +101,13 @@ exports['test syntax errors'] = function(assert) {
|
||||
}
|
||||
}
|
||||
|
||||
exports['test sandboxes are not added if error'] = function (assert) {
|
||||
let uri = root + '/fixtures/loader/missing-twice/';
|
||||
let loader = Loader({ paths: { '': uri } });
|
||||
let program = main(loader, 'main');
|
||||
assert.ok(!(uri + 'not-found.js' in loader.sandboxes), 'not-found.js not in loader.sandboxes');
|
||||
}
|
||||
|
||||
exports['test missing module'] = function(assert) {
|
||||
let uri = root + '/fixtures/loader/missing/'
|
||||
let loader = Loader({ paths: { '': uri } });
|
||||
@ -128,6 +135,26 @@ exports['test missing module'] = function(assert) {
|
||||
}
|
||||
}
|
||||
|
||||
exports["test invalid module not cached and throws everytime"] = function(assert) {
|
||||
let uri = root + "/fixtures/loader/missing-twice/";
|
||||
let loader = Loader({ paths: { "": uri } });
|
||||
|
||||
let { firstError, secondError, invalidJSON1, invalidJSON2 } = main(loader, "main");
|
||||
assert.equal(firstError.message, "Module `not-found` is not found at " +
|
||||
uri + "not-found.js", "throws on first invalid require");
|
||||
assert.equal(firstError.lineNumber, 8, "first error is on line 7");
|
||||
assert.equal(secondError.message, "Module `not-found` is not found at " +
|
||||
uri + "not-found.js", "throws on second invalid require");
|
||||
assert.equal(secondError.lineNumber, 14, "second error is on line 14");
|
||||
|
||||
assert.equal(invalidJSON1.message,
|
||||
"JSON.parse: unexpected character at line 1 column 1 of the JSON data",
|
||||
"throws on invalid JSON");
|
||||
assert.equal(invalidJSON2.message,
|
||||
"JSON.parse: unexpected character at line 1 column 1 of the JSON data",
|
||||
"throws on invalid JSON second time");
|
||||
};
|
||||
|
||||
exports['test exceptions in modules'] = function(assert) {
|
||||
let uri = root + '/fixtures/loader/exceptions/'
|
||||
|
||||
|
@ -1,21 +1,22 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var memory = require("sdk/deprecated/memory");
|
||||
const memory = require("sdk/deprecated/memory");
|
||||
const { gc } = require("sdk/test/memory");
|
||||
|
||||
exports.testMemory = function(assert) {
|
||||
assert.pass("Skipping this test until Gecko memory debugging issues " +
|
||||
"are resolved (see bug 592774).");
|
||||
return;
|
||||
|
||||
var obj = {};
|
||||
memory.track(obj, "testMemory.testObj");
|
||||
|
||||
var objs = memory.getObjects("testMemory.testObj");
|
||||
assert.equal(objs[0].weakref.get(), obj);
|
||||
obj = null;
|
||||
memory.gc();
|
||||
assert.equal(objs[0].weakref.get(), null);
|
||||
|
||||
gc().then(function() {
|
||||
assert.equal(objs[0].weakref.get(), null);
|
||||
});
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
@ -1,79 +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/. */
|
||||
|
||||
const observers = require("sdk/deprecated/observer-service");
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { LoaderWithHookedConsole2 } = require("sdk/test/loader");
|
||||
|
||||
exports.testUnloadAndErrorLogging = function(assert) {
|
||||
let { loader, messages } = LoaderWithHookedConsole2(module);
|
||||
var sbobsvc = loader.require("sdk/deprecated/observer-service");
|
||||
|
||||
var timesCalled = 0;
|
||||
var cb = function(subject, data) {
|
||||
timesCalled++;
|
||||
};
|
||||
var badCb = function(subject, data) {
|
||||
throw new Error("foo");
|
||||
};
|
||||
sbobsvc.add("blarg", cb);
|
||||
observers.notify("blarg", "yo yo");
|
||||
assert.equal(timesCalled, 1);
|
||||
sbobsvc.add("narg", badCb);
|
||||
observers.notify("narg", "yo yo");
|
||||
|
||||
assert.equal(messages[0], "console.error: " + require("sdk/self").name + ": \n");
|
||||
var lines = messages[1].split("\n");
|
||||
assert.equal(lines[0], " Message: Error: foo");
|
||||
assert.equal(lines[1], " Stack:");
|
||||
// Keep in mind to update "18" to the line of "throw new Error("foo")"
|
||||
assert.ok(lines[2].indexOf(module.uri + ":18") != -1);
|
||||
|
||||
loader.unload();
|
||||
observers.notify("blarg", "yo yo");
|
||||
assert.equal(timesCalled, 1);
|
||||
};
|
||||
|
||||
exports.testObserverService = function(assert) {
|
||||
var ios = Cc['@mozilla.org/network/io-service;1']
|
||||
.getService(Ci.nsIIOService);
|
||||
var service = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
var uri = ios.newURI("http://www.foo.com", null, null);
|
||||
var timesCalled = 0;
|
||||
var lastSubject = null;
|
||||
var lastData = null;
|
||||
|
||||
var cb = function(subject, data) {
|
||||
timesCalled++;
|
||||
lastSubject = subject;
|
||||
lastData = data;
|
||||
};
|
||||
|
||||
observers.add("blarg", cb);
|
||||
service.notifyObservers(uri, "blarg", "some data");
|
||||
assert.equal(timesCalled, 1,
|
||||
"observer-service.add() should call callback");
|
||||
assert.equal(lastSubject, uri,
|
||||
"observer-service.add() should pass subject");
|
||||
assert.equal(lastData, "some data",
|
||||
"observer-service.add() should pass data");
|
||||
|
||||
function customSubject() {}
|
||||
function customData() {}
|
||||
observers.notify("blarg", customSubject, customData);
|
||||
assert.equal(timesCalled, 2,
|
||||
"observer-service.notify() should work");
|
||||
assert.equal(lastSubject, customSubject,
|
||||
"observer-service.notify() should pass+wrap subject");
|
||||
assert.equal(lastData, customData,
|
||||
"observer-service.notify() should pass data");
|
||||
|
||||
observers.remove("blarg", cb);
|
||||
service.notifyObservers(null, "blarg", "some data");
|
||||
assert.equal(timesCalled, 2,
|
||||
"observer-service.remove() should work");
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
@ -5,7 +5,7 @@
|
||||
|
||||
const { Loader } = require("sdk/test/loader");
|
||||
const { setTimeout } = require("sdk/timers");
|
||||
const { notify } = require("sdk/deprecated/observer-service");
|
||||
const { emit } = require("sdk/system/events");
|
||||
const { id } = require("sdk/self");
|
||||
const simplePrefs = require("sdk/simple-prefs");
|
||||
const { prefs: sp } = simplePrefs;
|
||||
@ -132,7 +132,7 @@ exports.testBtnListener = function(assert, done) {
|
||||
assert.pass("Button press event was heard");
|
||||
done();
|
||||
});
|
||||
notify((id + "-cmdPressed"), "", name);
|
||||
emit((id + "-cmdPressed"), { subject: "", data: name });
|
||||
};
|
||||
|
||||
exports.testPrefRemoveListener = function(assert, done) {
|
||||
|
23
addon-sdk/source/test/test-test-memory.js
Normal file
23
addon-sdk/source/test/test-test-memory.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* 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 { Cc, Ci, Cu, components } = require('chrome');
|
||||
const { gc } = require('sdk/test/memory');
|
||||
|
||||
exports.testGC = function(assert, done) {
|
||||
let weakref;
|
||||
let (tempObj = {}) {
|
||||
weakref = Cu.getWeakReference(tempObj);
|
||||
assert.equal(weakref.get(), tempObj, 'the weakref returned the tempObj');
|
||||
}
|
||||
|
||||
gc().then(function(arg) {
|
||||
assert.equal(arg, undefined, 'there is no argument');
|
||||
assert.pass('gc() returns a promise which eventually resolves');
|
||||
assert.equal(weakref.get(), undefined, 'the weakref returned undefined');
|
||||
}).then(done).then(null, assert.fail);
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
@ -1,9 +1,11 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var traceback = require("sdk/console/traceback");
|
||||
var {Cc,Ci,Cr,Cu} = require("chrome");
|
||||
const { on, off } = require("sdk/system/events");
|
||||
|
||||
function throwNsIException() {
|
||||
var ios = Cc['@mozilla.org/network/io-service;1']
|
||||
@ -16,7 +18,6 @@ function throwError() {
|
||||
}
|
||||
|
||||
exports.testFormatDoesNotFetchRemoteFiles = function(assert) {
|
||||
var observers = require("sdk/deprecated/observer-service");
|
||||
["http", "https"].forEach(
|
||||
function(scheme) {
|
||||
var httpRequests = 0;
|
||||
@ -24,7 +25,7 @@ exports.testFormatDoesNotFetchRemoteFiles = function(assert) {
|
||||
httpRequests++;
|
||||
}
|
||||
|
||||
observers.add("http-on-modify-request", onHttp);
|
||||
on("http-on-modify-request", onHttp);
|
||||
|
||||
try {
|
||||
var tb = [{filename: scheme + "://www.mozilla.org/",
|
||||
@ -35,7 +36,7 @@ exports.testFormatDoesNotFetchRemoteFiles = function(assert) {
|
||||
assert.fail(e);
|
||||
}
|
||||
|
||||
observers.remove("http-on-modify-request", onHttp);
|
||||
off("http-on-modify-request", onHttp);
|
||||
|
||||
assert.equal(httpRequests, 0,
|
||||
"traceback.format() does not make " +
|
||||
|
@ -11,7 +11,7 @@ module.metadata = {
|
||||
};
|
||||
|
||||
const { Ci } = require('chrome');
|
||||
const { open, backgroundify, windows, isBrowser,
|
||||
const { open, windows, isBrowser,
|
||||
getXULWindow, getBaseWindow, getToplevelWindow, getMostRecentWindow,
|
||||
getMostRecentBrowserWindow } = require('sdk/window/utils');
|
||||
const { close } = require('sdk/window/helpers');
|
||||
@ -78,7 +78,7 @@ exports['test new top window with various URIs'] = function(assert, done) {
|
||||
}, msg);
|
||||
assert.throws(function () {
|
||||
open('https://foo');
|
||||
}, msg);
|
||||
}, msg);
|
||||
assert.throws(function () {
|
||||
open('ftp://foo');
|
||||
}, msg);
|
||||
@ -88,7 +88,7 @@ exports['test new top window with various URIs'] = function(assert, done) {
|
||||
|
||||
let chromeWindow = open('chrome://foo/content/');
|
||||
assert.ok(~windows().indexOf(chromeWindow), 'chrome URI works');
|
||||
|
||||
|
||||
let resourceWindow = open('resource://foo');
|
||||
assert.ok(~windows().indexOf(resourceWindow), 'resource URI works');
|
||||
|
||||
@ -96,22 +96,6 @@ exports['test new top window with various URIs'] = function(assert, done) {
|
||||
close(chromeWindow).then(close.bind(null, resourceWindow)).then(done);
|
||||
};
|
||||
|
||||
exports.testBackgroundify = function(assert, done) {
|
||||
let window = open('data:text/html;charset=utf-8,backgroundy');
|
||||
assert.ok(~windows().indexOf(window),
|
||||
'window is in the list of windows');
|
||||
let backgroundy = backgroundify(window);
|
||||
assert.equal(backgroundy, window, 'backgroundify returs give window back');
|
||||
assert.ok(!~windows().indexOf(window),
|
||||
'backgroundifyied window is in the list of windows');
|
||||
|
||||
// Wait for the window unload before ending test
|
||||
// backgroundified windows doesn't dispatch domwindowclosed event
|
||||
// so that we have to manually wait for unload event
|
||||
window.onunload = done;
|
||||
window.close();
|
||||
};
|
||||
|
||||
exports.testIsBrowser = function(assert) {
|
||||
// dummy window, bad type
|
||||
assert.equal(isBrowser({ document: { documentElement: { getAttribute: function() {
|
||||
|
@ -5,11 +5,7 @@
|
||||
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const { browserWindows } = require('sdk/windows');
|
||||
const { viewFor } = require('sdk/view/core');
|
||||
const { Ci } = require("chrome");
|
||||
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
|
||||
const { defer } = require("sdk/lang/functional");
|
||||
|
||||
|
||||
// TEST: browserWindows Iterator
|
||||
exports.testBrowserWindowsIterator = function(assert) {
|
||||
@ -61,26 +57,4 @@ exports.testWindowActivateMethod_simple = function(assert) {
|
||||
'Active tab is active after window.activate() call');
|
||||
};
|
||||
|
||||
|
||||
exports["test getView(window)"] = function(assert, done) {
|
||||
browserWindows.once("open", window => {
|
||||
const view = viewFor(window);
|
||||
|
||||
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
|
||||
assert.ok(isBrowser(view), "view is a browser window");
|
||||
assert.equal(getWindowTitle(view), window.title,
|
||||
"window has a right title");
|
||||
|
||||
window.close();
|
||||
// Defer handler cause window is destroyed after event is dispatched.
|
||||
browserWindows.once("close", defer(_ => {
|
||||
assert.equal(viewFor(window), null, "window view is gone");
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
browserWindows.open({ url: "data:text/html,<title>yo</title>" });
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
@ -6,7 +6,7 @@
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const { onFocus, getMostRecentWindow, windows } = require('sdk/window/utils');
|
||||
const { onFocus, getMostRecentWindow, windows, isBrowser, getWindowTitle } = require('sdk/window/utils');
|
||||
const { open, close, focus } = require('sdk/window/helpers');
|
||||
const { browserWindows } = require("sdk/windows");
|
||||
const tabs = require("sdk/tabs");
|
||||
@ -14,6 +14,8 @@ const winUtils = require("sdk/deprecated/window-utils");
|
||||
const { WindowTracker } = winUtils;
|
||||
const { isPrivate } = require('sdk/private-browsing');
|
||||
const { isWindowPBSupported } = require('sdk/private-browsing/utils');
|
||||
const { viewFor } = require("sdk/view/core");
|
||||
const { defer } = require("sdk/lang/functional");
|
||||
|
||||
// TEST: open & close window
|
||||
exports.testOpenAndCloseWindow = function(assert, done) {
|
||||
@ -418,6 +420,26 @@ exports.testWindowIteratorPrivateDefault = function(assert, done) {
|
||||
|
||||
close(window).then(done);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports["test getView(window)"] = function(assert, done) {
|
||||
browserWindows.once("open", window => {
|
||||
const view = viewFor(window);
|
||||
|
||||
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
|
||||
assert.ok(isBrowser(view), "view is a browser window");
|
||||
assert.equal(getWindowTitle(view), window.title,
|
||||
"window has a right title");
|
||||
|
||||
window.close();
|
||||
// Defer handler cause window is destroyed after event is dispatched.
|
||||
browserWindows.once("close", defer(_ => {
|
||||
assert.equal(viewFor(window), null, "window view is gone");
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
browserWindows.open({ url: "data:text/html,<title>yo</title>" });
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
Loading…
Reference in New Issue
Block a user