mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-19 23:07:33 +09:00
Move Add-on SDK source to toolkit/jetpack
This commit is contained in:
parent
0a03067ee3
commit
23b67db438
993 changed files with 484 additions and 100663 deletions
|
|
@ -1,426 +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': 'unstable'
|
||||
};
|
||||
|
||||
const { Class } = require('../core/heritage');
|
||||
const { EventTarget } = require('../event/target');
|
||||
const { on, off, emit } = require('../event/core');
|
||||
const { events } = require('./sandbox/events');
|
||||
const { requiresAddonGlobal } = 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 { getTabForContentWindowNoShim } = require('../tabs/utils');
|
||||
const { getInnerId } = require('../window/utils');
|
||||
const { PlainTextConsole } = require('../console/plain-text');
|
||||
const { data } = require('../self');const { isChildLoader } = require('../remote/core');
|
||||
// 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:
|
||||
*/
|
||||
var prefix = module.uri.split('sandbox.js')[0];
|
||||
const CONTENT_WORKER_URL = prefix + 'content-worker.js';
|
||||
const metadata = require('@loader/options').metadata;
|
||||
|
||||
// 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 = (metadata && metadata['permissions']) || {};
|
||||
const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];
|
||||
|
||||
const waiveSecurityMembrane = !!permissions['unsafe-content-script'];
|
||||
|
||||
const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager;
|
||||
const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
|
||||
getService(Ci.nsIScriptSecurityManager);
|
||||
|
||||
const JS_VERSION = '1.8';
|
||||
|
||||
// Tests whether this window is loaded in a tab
|
||||
function isWindowInTab(window) {
|
||||
if (isChildLoader) {
|
||||
let { frames } = require('../remote/child');
|
||||
let frame = frames.getFrameForWindow(window.top);
|
||||
return frame && frame.isTab;
|
||||
}
|
||||
else {
|
||||
// The deprecated sync worker API still does everything in the main process
|
||||
return getTabForContentWindowNoShim(window);
|
||||
}
|
||||
}
|
||||
|
||||
const WorkerSandbox = Class({
|
||||
implements: [ EventTarget ],
|
||||
|
||||
/**
|
||||
* Emit a message to the worker content sandbox
|
||||
*/
|
||||
emit: function emit(type, ...args) {
|
||||
// JSON.stringify is buggy with cross-sandbox values,
|
||||
// it may return "{}" on functions. Use a replacer to match them correctly.
|
||||
let replacer = (k, v) =>
|
||||
typeof(v) === "function"
|
||||
? (type === "console" ? Function.toString.call(v) : void(0))
|
||||
: v;
|
||||
|
||||
// Ensure having an asynchronous behavior
|
||||
async(() =>
|
||||
emitToContent(this, JSON.stringify([type, ...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) {
|
||||
// because the arguments could be also non JSONable values,
|
||||
// we need to ensure the array instance is created from
|
||||
// the content's sandbox
|
||||
return emitToContent(this, new modelFor(this).sandbox.Array(...args));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Use expanded principal for content-script if the content is a
|
||||
// regular web content for better isolation.
|
||||
// (This behavior can be turned off for now with the unsafe-content-script
|
||||
// flag to give addon developers time for making the necessary changes)
|
||||
// But prevent it when the Worker isn't used for a content script but for
|
||||
// injecting `addon` object into a Panel scope, for example.
|
||||
// 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 = [];
|
||||
let isSystemPrincipal = secMan.isSystemPrincipal(
|
||||
window.document.nodePrincipal);
|
||||
if (!isSystemPrincipal && !requiresAddonGlobal(worker)) {
|
||||
if (EXPANDED_PRINCIPALS.length > 0) {
|
||||
// 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');
|
||||
}
|
||||
if (!waiveSecurityMembrane)
|
||||
principals = EXPANDED_PRINCIPALS.concat(window);
|
||||
}
|
||||
|
||||
// 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: !requiresAddonGlobal(worker),
|
||||
wantGlobalProperties: wantGlobalProperties,
|
||||
wantExportHelpers: true,
|
||||
sameZoneAs: window,
|
||||
metadata: {
|
||||
SDKContentScript: true,
|
||||
'inner-window-id': getInnerId(window)
|
||||
}
|
||||
});
|
||||
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() {
|
||||
return content;
|
||||
},
|
||||
get top() {
|
||||
return top;
|
||||
},
|
||||
get parent() {
|
||||
return 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!
|
||||
//
|
||||
// Note that because waivers aren't propagated between origins, we
|
||||
// need the unsafeWindow getter to live in the sandbox.
|
||||
var unsafeWindowGetter =
|
||||
new content.Function('return window.wrappedJSObject || window;');
|
||||
Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter});
|
||||
|
||||
// Load trusted code that will inject content script API.
|
||||
let ContentWorker = load(content, 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
|
||||
let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker);
|
||||
let chromeAPI = createChromeAPI(ContentWorker);
|
||||
let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);
|
||||
|
||||
// Merge `emitToContent` into our private model of the
|
||||
// WorkerSandbox so we can communicate with content script
|
||||
model.emitToContent = result;
|
||||
|
||||
let console = new PlainTextConsole(null, getInnerId(window));
|
||||
|
||||
// Handle messages send by this script:
|
||||
setListeners(this, console);
|
||||
|
||||
// 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,
|
||||
configurable: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Inject our `console` into target document if worker doesn't have a tab
|
||||
// (e.g Panel, PageWorker).
|
||||
// `worker.tab` can't be used because bug 804935.
|
||||
if (!isWindowInTab(window)) {
|
||||
let win = getUnsafeWindow(window);
|
||||
|
||||
// export our chrome console to content window, as 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'),
|
||||
exception: genPropDesc('exception'),
|
||||
assert: genPropDesc('assert'),
|
||||
count: genPropDesc('count'),
|
||||
table: genPropDesc('table'),
|
||||
clear: genPropDesc('clear'),
|
||||
dirxml: genPropDesc('dirxml'),
|
||||
markTimeline: genPropDesc('markTimeline'),
|
||||
timeline: genPropDesc('timeline'),
|
||||
timelineEnd: genPropDesc('timelineEnd'),
|
||||
timeStamp: genPropDesc('timeStamp'),
|
||||
};
|
||||
|
||||
Object.defineProperties(con, properties);
|
||||
Cu.makeObjectPropsNormal(con);
|
||||
|
||||
win.console = con;
|
||||
};
|
||||
|
||||
emit(events, "content-script-before-inserted", {
|
||||
window: window,
|
||||
worker: worker
|
||||
});
|
||||
|
||||
// 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(reason) {
|
||||
if (typeof reason != 'string')
|
||||
reason = '';
|
||||
this.emitSync('event', 'detach', reason);
|
||||
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 = data.url(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, console) {
|
||||
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);
|
||||
}
|
||||
|
||||
function getUnsafeWindow (win) {
|
||||
return win.wrappedJSObject || win;
|
||||
}
|
||||
|
||||
function emitToContent (workerSandbox, args) {
|
||||
return modelFor(workerSandbox).emitToContent(args);
|
||||
}
|
||||
|
||||
function createChromeAPI (scope) {
|
||||
return Cu.cloneInto({
|
||||
timers: {
|
||||
setTimeout: timer.setTimeout.bind(timer),
|
||||
setInterval: timer.setInterval.bind(timer),
|
||||
clearTimeout: timer.clearTimeout.bind(timer),
|
||||
clearInterval: timer.clearInterval.bind(timer),
|
||||
},
|
||||
sandbox: {
|
||||
evaluate: evaluate,
|
||||
},
|
||||
}, scope, {cloneFunctions: true});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue