mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-18 13:17:29 +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
182
toolkit/jetpack/sdk/addon/bootstrap.js
vendored
Normal file
182
toolkit/jetpack/sdk/addon/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/* 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 { Cu } = require("chrome");
|
||||
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
|
||||
const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
|
||||
const { readURI } = require("sdk/net/url");
|
||||
const { mount, unmount } = require("sdk/uri/resource");
|
||||
const { setTimeout } = require("sdk/timers");
|
||||
const { Loader, Require, Module, main, unload } = require("toolkit/loader");
|
||||
const prefs = require("sdk/preferences/service");
|
||||
|
||||
// load below now, so that it can be used by sdk/addon/runner
|
||||
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
|
||||
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
|
||||
|
||||
const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
|
||||
"install", "uninstall", "upgrade", "downgrade" ];
|
||||
|
||||
const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
|
||||
// Takes add-on ID and normalizes it to a domain name so that add-on
|
||||
// can be mapped to resource://domain/
|
||||
const readDomain = id =>
|
||||
// If only `@` character is the first one, than just substract it,
|
||||
// otherwise fallback to legacy normalization code path. Note: `.`
|
||||
// is valid character for resource substitutaiton & we intend to
|
||||
// make add-on URIs intuitive, so it's best to just stick to an
|
||||
// add-on author typed input.
|
||||
id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
|
||||
id.toLowerCase().
|
||||
replace(/@/g, "-at-").
|
||||
replace(/\./g, "-dot-").
|
||||
replace(UUID_PATTERN, "$1");
|
||||
|
||||
const readPaths = id => {
|
||||
const base = `extensions.modules.${id}.path.`;
|
||||
const domain = readDomain(id);
|
||||
return prefs.keys(base).reduce((paths, key) => {
|
||||
const value = prefs.get(key);
|
||||
const name = key.replace(base, "");
|
||||
const path = name.split(".").join("/");
|
||||
const prefix = path.length ? `${path}/` : path;
|
||||
const uri = value.endsWith("/") ? value : `${value}/`;
|
||||
const root = `extensions.modules.${domain}.commonjs.path.${name}`;
|
||||
|
||||
mount(root, uri);
|
||||
|
||||
paths[prefix] = `resource://${root}/`;
|
||||
return paths;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const Bootstrap = function(mountURI) {
|
||||
this.mountURI = mountURI;
|
||||
this.install = this.install.bind(this);
|
||||
this.uninstall = this.uninstall.bind(this);
|
||||
this.startup = this.startup.bind(this);
|
||||
this.shutdown = this.shutdown.bind(this);
|
||||
};
|
||||
Bootstrap.prototype = {
|
||||
constructor: Bootstrap,
|
||||
mount(domain, rootURI) {
|
||||
mount(domain, rootURI);
|
||||
this.domain = domain;
|
||||
},
|
||||
unmount() {
|
||||
if (this.domain) {
|
||||
unmount(this.domain);
|
||||
this.domain = null;
|
||||
}
|
||||
},
|
||||
install(addon, reason) {
|
||||
return new Promise(resolve => resolve());
|
||||
},
|
||||
uninstall(addon, reason) {
|
||||
return new Promise(resolve => {
|
||||
const {id} = addon;
|
||||
|
||||
prefs.reset(`extensions.${id}.sdk.domain`);
|
||||
prefs.reset(`extensions.${id}.sdk.version`);
|
||||
prefs.reset(`extensions.${id}.sdk.rootURI`);
|
||||
prefs.reset(`extensions.${id}.sdk.baseURI`);
|
||||
prefs.reset(`extensions.${id}.sdk.load.reason`);
|
||||
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
startup(addon, reasonCode) {
|
||||
const { id, version, resourceURI: { spec: addonURI } } = addon;
|
||||
const rootURI = this.mountURI || addonURI;
|
||||
const reason = REASON[reasonCode];
|
||||
const self = this;
|
||||
|
||||
return spawn(function*() {
|
||||
const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
|
||||
const domain = readDomain(id);
|
||||
const baseURI = `resource://${domain}/`;
|
||||
|
||||
this.mount(domain, rootURI);
|
||||
|
||||
prefs.set(`extensions.${id}.sdk.domain`, domain);
|
||||
prefs.set(`extensions.${id}.sdk.version`, version);
|
||||
prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
|
||||
prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
|
||||
prefs.set(`extensions.${id}.sdk.load.reason`, reason);
|
||||
|
||||
const command = prefs.get(`extensions.${id}.sdk.load.command`);
|
||||
|
||||
const loader = Loader({
|
||||
id,
|
||||
isNative: true,
|
||||
checkCompatibility: true,
|
||||
prefixURI: baseURI,
|
||||
rootURI: baseURI,
|
||||
name: metadata.name,
|
||||
paths: Object.assign({
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
"devtools/": "resource://devtools/",
|
||||
"./": baseURI
|
||||
}, readPaths(id)),
|
||||
manifest: metadata,
|
||||
metadata: metadata,
|
||||
modules: {
|
||||
"@test/options": {},
|
||||
},
|
||||
noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
|
||||
});
|
||||
self.loader = loader;
|
||||
|
||||
const module = Module("package.json", `${baseURI}package.json`);
|
||||
const require = Require(loader, module);
|
||||
const main = command === "test" ? "sdk/test/runner" : null;
|
||||
const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
|
||||
|
||||
// Init the 'sdk/webextension' module from the bootstrap addon parameter.
|
||||
require("sdk/webextension").initFromBootstrapAddonParam(addon);
|
||||
|
||||
const { startup } = require("sdk/addon/runner");
|
||||
startup(reason, {loader, main, prefsURI});
|
||||
}.bind(this)).catch(error => {
|
||||
console.error(`Failed to start ${id} addon`, error);
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
shutdown(addon, code) {
|
||||
this.unmount();
|
||||
return this.unload(REASON[code]);
|
||||
},
|
||||
unload(reason) {
|
||||
return new Promise(resolve => {
|
||||
const { loader } = this;
|
||||
if (loader) {
|
||||
this.loader = null;
|
||||
unload(loader, reason);
|
||||
|
||||
setTimeout(() => {
|
||||
for (let uri of Object.keys(loader.sandboxes)) {
|
||||
let sandbox = loader.sandboxes[uri];
|
||||
if (Cu.getClassName(sandbox, true) == "Sandbox")
|
||||
Cu.nukeSandbox(sandbox);
|
||||
delete loader.sandboxes[uri];
|
||||
delete loader.modules[uri];
|
||||
}
|
||||
|
||||
try {
|
||||
Cu.nukeSandbox(loader.sharedGlobalSandbox);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
resolve();
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.Bootstrap = Bootstrap;
|
||||
56
toolkit/jetpack/sdk/addon/events.js
Normal file
56
toolkit/jetpack/sdk/addon/events.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'experimental'
|
||||
};
|
||||
|
||||
var { request: hostReq, response: hostRes } = require('./host');
|
||||
var { defer: async } = require('../lang/functional');
|
||||
var { defer } = require('../core/promise');
|
||||
var { emit: emitSync, on, off } = require('../event/core');
|
||||
var { uuid } = require('../util/uuid');
|
||||
var emit = async(emitSync);
|
||||
|
||||
// Map of IDs to deferreds
|
||||
var requests = new Map();
|
||||
|
||||
// May not be necessary to wrap this in `async`
|
||||
// once promises are async via bug 881047
|
||||
var receive = async(function ({data, id, error}) {
|
||||
let request = requests.get(id);
|
||||
if (request) {
|
||||
if (error) request.reject(error);
|
||||
else request.resolve(clone(data));
|
||||
requests.delete(id);
|
||||
}
|
||||
});
|
||||
on(hostRes, 'data', receive);
|
||||
|
||||
/*
|
||||
* Send is a helper to be used in client APIs to send
|
||||
* a request to host
|
||||
*/
|
||||
function send (eventName, data) {
|
||||
let id = uuid();
|
||||
let deferred = defer();
|
||||
requests.set(id, deferred);
|
||||
emit(hostReq, 'data', {
|
||||
id: id,
|
||||
data: clone(data),
|
||||
event: eventName
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.send = send;
|
||||
|
||||
/*
|
||||
* Implement internal structured cloning algorithm in the future?
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#internal-structured-cloning-algorithm
|
||||
*/
|
||||
function clone (obj) {
|
||||
return JSON.parse(JSON.stringify(obj || {}));
|
||||
}
|
||||
12
toolkit/jetpack/sdk/addon/host.js
Normal file
12
toolkit/jetpack/sdk/addon/host.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
exports.request = {};
|
||||
exports.response = {};
|
||||
121
toolkit/jetpack/sdk/addon/installer.js
Normal file
121
toolkit/jetpack/sdk/addon/installer.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* 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/. */
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
const { defer } = require("../core/promise");
|
||||
const { setTimeout } = require("../timers");
|
||||
|
||||
/**
|
||||
* `install` method error codes:
|
||||
*
|
||||
* https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors
|
||||
*/
|
||||
exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE;
|
||||
exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH;
|
||||
exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE;
|
||||
exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS;
|
||||
|
||||
/**
|
||||
* Immediatly install an addon.
|
||||
*
|
||||
* @param {String} xpiPath
|
||||
* file path to an xpi file to install
|
||||
* @return {Promise}
|
||||
* A promise resolved when the addon is finally installed.
|
||||
* Resolved with addon id as value or rejected with an error code.
|
||||
*/
|
||||
exports.install = function install(xpiPath) {
|
||||
let { promise, resolve, reject } = defer();
|
||||
|
||||
// Create nsIFile for the xpi file
|
||||
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
|
||||
try {
|
||||
file.initWithPath(xpiPath);
|
||||
}
|
||||
catch(e) {
|
||||
reject(exports.ERROR_FILE_ACCESS);
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Listen for installation end
|
||||
let listener = {
|
||||
onInstallEnded: function(aInstall, aAddon) {
|
||||
aInstall.removeListener(listener);
|
||||
// Bug 749745: on FF14+, onInstallEnded is called just before `startup()`
|
||||
// is called, but we expect to resolve the promise only after it.
|
||||
// As startup is called synchronously just after onInstallEnded,
|
||||
// a simple setTimeout(0) is enough
|
||||
setTimeout(resolve, 0, aAddon.id);
|
||||
},
|
||||
onInstallFailed: function (aInstall) {
|
||||
aInstall.removeListener(listener);
|
||||
reject(aInstall.error);
|
||||
},
|
||||
onDownloadFailed: function(aInstall) {
|
||||
this.onInstallFailed(aInstall);
|
||||
}
|
||||
};
|
||||
|
||||
// Order AddonManager to install the addon
|
||||
AddonManager.getInstallForFile(file, function(install) {
|
||||
if (install.error == 0) {
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
} else {
|
||||
reject(install.error);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
exports.uninstall = function uninstall(addonId) {
|
||||
let { promise, resolve, reject } = defer();
|
||||
|
||||
// Listen for uninstallation end
|
||||
let listener = {
|
||||
onUninstalled: function onUninstalled(aAddon) {
|
||||
if (aAddon.id != addonId)
|
||||
return;
|
||||
AddonManager.removeAddonListener(listener);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
AddonManager.addAddonListener(listener);
|
||||
|
||||
// Order Addonmanager to uninstall the addon
|
||||
getAddon(addonId).then(addon => addon.uninstall(), reject);
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
exports.disable = function disable(addonId) {
|
||||
return getAddon(addonId).then(addon => {
|
||||
addon.userDisabled = true;
|
||||
return addonId;
|
||||
});
|
||||
};
|
||||
|
||||
exports.enable = function enabled(addonId) {
|
||||
return getAddon(addonId).then(addon => {
|
||||
addon.userDisabled = false;
|
||||
return addonId;
|
||||
});
|
||||
};
|
||||
|
||||
exports.isActive = function isActive(addonId) {
|
||||
return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled);
|
||||
};
|
||||
|
||||
const getAddon = function getAddon (id) {
|
||||
let { promise, resolve, reject } = defer();
|
||||
AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject());
|
||||
return promise;
|
||||
}
|
||||
exports.getAddon = getAddon;
|
||||
18
toolkit/jetpack/sdk/addon/manager.js
Normal file
18
toolkit/jetpack/sdk/addon/manager.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
|
||||
const { defer } = require("../core/promise");
|
||||
|
||||
function getAddonByID(id) {
|
||||
let { promise, resolve } = defer();
|
||||
AddonManager.getAddonByID(id, resolve);
|
||||
return promise;
|
||||
}
|
||||
exports.getAddonByID = getAddonByID;
|
||||
180
toolkit/jetpack/sdk/addon/runner.js
Normal file
180
toolkit/jetpack/sdk/addon/runner.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/* 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/. */
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Cc, Ci, Cu } = require('chrome');
|
||||
const { rootURI, metadata, isNative } = require('@loader/options');
|
||||
const { id, loadReason } = require('../self');
|
||||
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
|
||||
const { once } = require('../system/events');
|
||||
const { exit, env, staticArgs } = require('../system');
|
||||
const { when: unload } = require('../system/unload');
|
||||
const globals = require('../system/globals');
|
||||
const xulApp = require('../system/xul-app');
|
||||
const { get } = require('../preferences/service');
|
||||
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
|
||||
getService(Ci.nsIAppShellService);
|
||||
const { preferences } = metadata;
|
||||
|
||||
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
|
||||
return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}).
|
||||
BrowserToolboxProcess;
|
||||
});
|
||||
|
||||
// Initializes default preferences
|
||||
function setDefaultPrefs(prefsURI) {
|
||||
const prefs = Cc['@mozilla.org/preferences-service;1'].
|
||||
getService(Ci.nsIPrefService).
|
||||
QueryInterface(Ci.nsIPrefBranch2);
|
||||
const branch = prefs.getDefaultBranch('');
|
||||
const sandbox = Sandbox({
|
||||
name: prefsURI,
|
||||
prototype: {
|
||||
pref: function(key, val) {
|
||||
switch (typeof val) {
|
||||
case 'boolean':
|
||||
branch.setBoolPref(key, val);
|
||||
break;
|
||||
case 'number':
|
||||
if (val % 1 == 0) // number must be a integer, otherwise ignore it
|
||||
branch.setIntPref(key, val);
|
||||
break;
|
||||
case 'string':
|
||||
branch.setCharPref(key, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// load preferences.
|
||||
evaluate(sandbox, prefsURI);
|
||||
}
|
||||
|
||||
function definePseudo(loader, id, exports) {
|
||||
let uri = resolveURI(id, loader.mapping);
|
||||
loader.modules[uri] = { exports: exports };
|
||||
}
|
||||
|
||||
function startup(reason, options) {
|
||||
return Startup.onceInitialized.then(() => {
|
||||
// Inject globals ASAP in order to have console API working ASAP
|
||||
Object.defineProperties(options.loader.globals, descriptor(globals));
|
||||
|
||||
// NOTE: Module is intentionally required only now because it relies
|
||||
// on existence of hidden window, which does not exists until startup.
|
||||
let { ready } = require('../addon/window');
|
||||
// Load localization manifest and .properties files.
|
||||
// Run the addon even in case of error (best effort approach)
|
||||
require('../l10n/loader').
|
||||
load(rootURI).
|
||||
then(null, function failure(error) {
|
||||
if (!isNative)
|
||||
console.info("Error while loading localization: " + error.message);
|
||||
}).
|
||||
then(function onLocalizationReady(data) {
|
||||
// Exports data to a pseudo module so that api-utils/l10n/core
|
||||
// can get access to it
|
||||
definePseudo(options.loader, '@l10n/data', data ? data : null);
|
||||
return ready;
|
||||
}).then(function() {
|
||||
run(options);
|
||||
}).then(null, console.exception);
|
||||
return void 0; // otherwise we raise a warning, see bug 910304
|
||||
});
|
||||
}
|
||||
|
||||
function run(options) {
|
||||
try {
|
||||
// Try initializing HTML localization before running main module. Just print
|
||||
// an exception in case of error, instead of preventing addon to be run.
|
||||
try {
|
||||
// Do not enable HTML localization while running test as it is hard to
|
||||
// disable. Because unit tests are evaluated in a another Loader who
|
||||
// doesn't have access to this current loader.
|
||||
if (options.main !== 'sdk/test/runner') {
|
||||
require('../l10n/html').enable();
|
||||
}
|
||||
}
|
||||
catch(error) {
|
||||
console.exception(error);
|
||||
}
|
||||
|
||||
// native-options does stuff directly with preferences key from package.json
|
||||
if (preferences && preferences.length > 0) {
|
||||
try {
|
||||
require('../preferences/native-options').
|
||||
enable({ preferences: preferences, id: id }).
|
||||
catch(console.exception);
|
||||
}
|
||||
catch (error) {
|
||||
console.exception(error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// keeping support for addons packaged with older SDK versions,
|
||||
// when cfx didn't include the 'preferences' key in @loader/options
|
||||
|
||||
// Initialize inline options localization, without preventing addon to be
|
||||
// run in case of error
|
||||
try {
|
||||
require('../l10n/prefs').enable();
|
||||
}
|
||||
catch(error) {
|
||||
console.exception(error);
|
||||
}
|
||||
|
||||
// TODO: When bug 564675 is implemented this will no longer be needed
|
||||
// Always set the default prefs, because they disappear on restart
|
||||
if (options.prefsURI) {
|
||||
// Only set if `prefsURI` specified
|
||||
try {
|
||||
setDefaultPrefs(options.prefsURI);
|
||||
}
|
||||
catch (err) {
|
||||
// cfx bootstrap always passes prefsURI, even in addons without prefs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is where the addon's main.js finally run.
|
||||
let program = main(options.loader, options.main);
|
||||
|
||||
if (typeof(program.onUnload) === 'function')
|
||||
unload(program.onUnload);
|
||||
|
||||
if (typeof(program.main) === 'function') {
|
||||
program.main({
|
||||
loadReason: loadReason,
|
||||
staticArgs: staticArgs
|
||||
}, {
|
||||
print: function print(_) { dump(_ + '\n') },
|
||||
quit: exit
|
||||
});
|
||||
}
|
||||
|
||||
if (get("extensions." + id + ".sdk.debug.show", false)) {
|
||||
BrowserToolboxProcess.init({ addonID: id });
|
||||
}
|
||||
} catch (error) {
|
||||
console.exception(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
exports.startup = startup;
|
||||
|
||||
// If add-on is lunched via `cfx run` we need to use `system.exit` to let
|
||||
// cfx know we're done (`cfx test` will take care of exit so we don't do
|
||||
// anything here).
|
||||
if (env.CFX_COMMAND === 'run') {
|
||||
unload(function(reason) {
|
||||
if (reason === 'shutdown')
|
||||
exit(0);
|
||||
});
|
||||
}
|
||||
66
toolkit/jetpack/sdk/addon/window.js
Normal file
66
toolkit/jetpack/sdk/addon/window.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Ci, Cc } = require("chrome");
|
||||
const { make: makeWindow, getHiddenWindow } = require("../window/utils");
|
||||
const { create: makeFrame, getDocShell } = require("../frame/utils");
|
||||
const { defer } = require("../core/promise");
|
||||
const { when: unload } = require("../system/unload");
|
||||
const cfxArgs = require("../test/options");
|
||||
|
||||
var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"].
|
||||
createInstance(Ci.nsIPrincipal);
|
||||
|
||||
var hiddenWindow = getHiddenWindow();
|
||||
|
||||
if (cfxArgs.parseable) {
|
||||
console.info("hiddenWindow document.documentURI:" +
|
||||
hiddenWindow.document.documentURI);
|
||||
console.info("hiddenWindow document.readyState:" +
|
||||
hiddenWindow.document.readyState);
|
||||
}
|
||||
|
||||
// Once Bug 565388 is fixed and shipped we'll be able to make invisible,
|
||||
// permanent docShells. Meanwhile we create hidden top level window and
|
||||
// use it's docShell.
|
||||
var frame = makeFrame(hiddenWindow.document, {
|
||||
nodeName: "iframe",
|
||||
namespaceURI: "http://www.w3.org/1999/xhtml",
|
||||
allowJavascript: true,
|
||||
allowPlugins: true
|
||||
})
|
||||
var docShell = getDocShell(frame);
|
||||
var eventTarget = docShell.chromeEventHandler;
|
||||
|
||||
// We need to grant docShell system principals in order to load XUL document
|
||||
// from data URI into it.
|
||||
docShell.createAboutBlankContentViewer(addonPrincipal);
|
||||
|
||||
// Get a reference to the DOM window of the given docShell and load
|
||||
// such document into that would allow us to create XUL iframes, that
|
||||
// are necessary for hidden frames etc..
|
||||
var window = docShell.contentViewer.DOMDocument.defaultView;
|
||||
window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>";
|
||||
|
||||
// Create a promise that is delivered once add-on window is interactive,
|
||||
// used by add-on runner to defer add-on loading until window is ready.
|
||||
var { promise, resolve } = defer();
|
||||
eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
|
||||
eventTarget.removeEventListener("DOMContentLoaded", handler, false);
|
||||
resolve();
|
||||
}, false);
|
||||
|
||||
exports.ready = promise;
|
||||
exports.window = window;
|
||||
|
||||
// Still close window on unload to claim memory back early.
|
||||
unload(function() {
|
||||
window.close()
|
||||
frame.parentNode.removeChild(frame);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue