Bug 1350522: Part 2 - Convert toolkit APIs to lazy loading. r=aswan

MozReview-Commit-ID: 8TbTIM4WX2d

--HG--
extra : source : a3ed5ad1bc338e7fd8055c2efcf73695c25e09e5
This commit is contained in:
Kris Maglione 2017-03-31 19:36:00 -07:00
Родитель 4ef4a153de
Коммит bb351ebfca
37 изменённых файлов: 2131 добавлений и 1941 удалений

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

@ -1065,7 +1065,11 @@ class ContentGlobal {
}
promiseEvent(this.global, "DOMContentLoaded", true).then(() => {
this.global.sendAsyncMessage("Extension:ExtensionViewLoaded");
let windowId = getInnerWindowID(this.global.content);
let context = ExtensionChild.extensionContexts.get(windowId);
this.global.sendAsyncMessage("Extension:ExtensionViewLoaded",
{childId: context && context.childManager.id});
});
/* FALLTHROUGH */

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

@ -673,10 +673,12 @@ ParentAPIManager = {
// Store pending listener additions so we can be sure they're all
// fully initialize before we consider extension startup complete.
const {listenerPromises} = context;
listenerPromises.add(promise);
let remove = () => { listenerPromises.delete(promise); };
promise.then(remove, remove);
if (context.viewType === "background" && context.listenerPromises) {
const {listenerPromises} = context;
listenerPromises.add(promise);
let remove = () => { listenerPromises.delete(promise); };
promise.then(remove, remove);
}
let handler = await promise;
handler.addListener(listener, ...args);
@ -858,9 +860,9 @@ class HiddenExtensionPage {
function promiseExtensionViewLoaded(browser) {
return new Promise(resolve => {
browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad() {
browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad({data}) {
browser.messageManager.removeMessageListener("Extension:ExtensionViewLoaded", onLoad);
resolve();
resolve(data.childId && ParentAPIManager.getContextById(data.childId));
});
});
}

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

@ -1,17 +1,14 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
// WeakMap[Extension -> Map[name -> Alarm]]
var alarmsMap = new WeakMap();
let alarmsMap = new WeakMap();
// WeakMap[Extension -> Set[callback]]
var alarmCallbacksMap = new WeakMap();
let alarmCallbacksMap = new WeakMap();
// Manages an alarm created by the extension (alarms API).
function Alarm(extension, name, alarmInfo) {
@ -76,80 +73,81 @@ Alarm.prototype = {
},
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("startup", (type, extension) => {
alarmsMap.set(extension, new Map());
alarmCallbacksMap.set(extension, new Set());
});
this.alarms = class extends ExtensionAPI {
onShutdown() {
let {extension} = this;
extensions.on("shutdown", (type, extension) => {
if (alarmsMap.has(extension)) {
for (let alarm of alarmsMap.get(extension).values()) {
alarm.clear();
if (alarmsMap.has(extension)) {
for (let alarm of alarmsMap.get(extension).values()) {
alarm.clear();
}
alarmsMap.delete(extension);
alarmCallbacksMap.delete(extension);
}
alarmsMap.delete(extension);
alarmCallbacksMap.delete(extension);
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("alarms", "addon_parent", context => {
let {extension} = context;
return {
alarms: {
create: function(name, alarmInfo) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
alarms.get(name).clear();
}
let alarm = new Alarm(extension, name, alarmInfo);
alarms.set(alarm.name, alarm);
getAPI(context) {
let {extension} = context;
alarmsMap.set(extension, new Map());
alarmCallbacksMap.set(extension, new Set());
return {
alarms: {
create: function(name, alarmInfo) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
alarms.get(name).clear();
}
let alarm = new Alarm(extension, name, alarmInfo);
alarms.set(alarm.name, alarm);
},
get: function(name) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
return Promise.resolve(alarms.get(name).data);
}
return Promise.resolve();
},
getAll: function() {
let result = Array.from(alarmsMap.get(extension).values(), alarm => alarm.data);
return Promise.resolve(result);
},
clear: function(name) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
alarms.get(name).clear();
return Promise.resolve(true);
}
return Promise.resolve(false);
},
clearAll: function() {
let cleared = false;
for (let alarm of alarmsMap.get(extension).values()) {
alarm.clear();
cleared = true;
}
return Promise.resolve(cleared);
},
onAlarm: new SingletonEventManager(context, "alarms.onAlarm", fire => {
let callback = alarm => {
fire.sync(alarm.data);
};
alarmCallbacksMap.get(extension).add(callback);
return () => {
alarmCallbacksMap.get(extension).delete(callback);
};
}).api(),
},
get: function(name) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
return Promise.resolve(alarms.get(name).data);
}
return Promise.resolve();
},
getAll: function() {
let result = Array.from(alarmsMap.get(extension).values(), alarm => alarm.data);
return Promise.resolve(result);
},
clear: function(name) {
name = name || "";
let alarms = alarmsMap.get(extension);
if (alarms.has(name)) {
alarms.get(name).clear();
return Promise.resolve(true);
}
return Promise.resolve(false);
},
clearAll: function() {
let cleared = false;
for (let alarm of alarmsMap.get(extension).values()) {
alarm.clear();
cleared = true;
}
return Promise.resolve(cleared);
},
onAlarm: new SingletonEventManager(context, "alarms.onAlarm", fire => {
let callback = alarm => {
fire.sync(alarm.data);
};
alarmCallbacksMap.get(extension).add(callback);
return () => {
alarmCallbacksMap.get(extension).delete(callback);
};
}).api(),
},
};
});
};
}
};

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

@ -1,7 +1,5 @@
"use strict";
var {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@ -9,13 +7,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/ExtensionParent.jsm");
const {
var {
HiddenExtensionPage,
promiseExtensionViewLoaded,
} = ExtensionParent;
// WeakMap[Extension -> BackgroundPage]
var backgroundPagesMap = new WeakMap();
let backgroundPagesMap = new WeakMap();
// Responsible for the background_page section of the manifest.
class BackgroundPage extends HiddenExtensionPage {
@ -38,30 +36,35 @@ class BackgroundPage extends HiddenExtensionPage {
}
}
build() {
return Task.spawn(function* () {
yield this.createBrowserElement();
async build() {
await this.createBrowserElement();
extensions.emit("extension-browser-inserted", this.browser);
extensions.emit("extension-browser-inserted", this.browser);
this.browser.loadURI(this.url);
this.browser.loadURI(this.url);
yield promiseExtensionViewLoaded(this.browser);
let context = await promiseExtensionViewLoaded(this.browser);
if (this.browser.docShell) {
this.webNav = this.browser.docShell.QueryInterface(Ci.nsIWebNavigation);
let window = this.webNav.document.defaultView;
if (this.browser.docShell) {
this.webNav = this.browser.docShell.QueryInterface(Ci.nsIWebNavigation);
let window = this.webNav.document.defaultView;
// Set the add-on's main debugger global, for use in the debugger
// console.
if (this.extension.addonData.instanceID) {
AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
.then(addon => addon.setDebugGlobal(window));
}
// Set the add-on's main debugger global, for use in the debugger
// console.
if (this.extension.addonData.instanceID) {
AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
.then(addon => addon.setDebugGlobal(window));
}
}
this.extension.emit("startup");
}.bind(this));
if (context) {
// Wait until all event listeners registered by the script so far
// to be handled.
await Promise.all(context.listenerPromises);
}
context.listenerPromises = null;
this.extension.emit("startup");
}
shutdown() {
@ -74,18 +77,23 @@ class BackgroundPage extends HiddenExtensionPage {
}
}
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_background", (type, directive, extension, manifest) => {
let bgPage = new BackgroundPage(extension, manifest.background);
this.backgroundPage = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
backgroundPagesMap.set(extension, bgPage);
return bgPage.build();
});
let bgPage = new BackgroundPage(extension, manifest.background);
extensions.on("shutdown", (type, extension) => {
if (backgroundPagesMap.has(extension)) {
backgroundPagesMap.get(extension).shutdown();
backgroundPagesMap.delete(extension);
backgroundPagesMap.set(extension, bgPage);
return bgPage.build();
}
});
/* eslint-enable mozilla/balanced-listeners */
onShutdown() {
let {extension} = this;
if (backgroundPagesMap.has(extension)) {
backgroundPagesMap.get(extension).shutdown();
backgroundPagesMap.delete(extension);
}
}
};

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

@ -1,12 +1,10 @@
/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
@ -22,6 +20,7 @@ XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
return require("devtools/shared/css/color").colorUtils;
});
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
getWinUtils,
stylesheetMap,

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

@ -1,45 +1,25 @@
"use strict";
global.initializeBackgroundPage = (contentWindow) => {
// Override the `alert()` method inside background windows;
// we alias it to console.log().
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
let alertDisplayedWarning = false;
let alertOverwrite = text => {
if (!alertDisplayedWarning) {
require("devtools/client/framework/devtools-browser");
let hudservice = require("devtools/client/webconsole/hudservice");
hudservice.openBrowserConsoleOrFocus();
contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead.");
alertDisplayedWarning = true;
}
contentWindow.console.log(text);
};
Cu.exportFunction(alertOverwrite, contentWindow, {defineAs: "alert"});
};
extensions.registerSchemaAPI("extension", "addon_child", context => {
function getBackgroundPage() {
for (let view of context.extension.views) {
if (view.viewType == "background" && context.principal.subsumes(view.principal)) {
return view.contentWindow;
this.backgroundPage = class extends ExtensionAPI {
getAPI(context) {
function getBackgroundPage() {
for (let view of context.extension.views) {
if (view.viewType == "background" && context.principal.subsumes(view.principal)) {
return view.contentWindow;
}
}
return null;
}
return null;
}
return {
extension: {
getBackgroundPage,
},
runtime: {
getBackgroundPage() {
return context.cloneScope.Promise.resolve(getBackgroundPage());
return {
extension: {
getBackgroundPage,
},
},
};
});
runtime: {
getBackgroundPage() {
return context.cloneScope.Promise.resolve(getBackgroundPage());
},
},
};
}
};

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

@ -3,9 +3,9 @@
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
function extensionApiFactory(context) {
return {
extension: {
this.extension = class extends ExtensionAPI {
getAPI(context) {
let api = {
getURL(url) {
return context.extension.baseURI.resolve(url);
},
@ -17,17 +17,10 @@ function extensionApiFactory(context) {
get inIncognitoContext() {
return context.incognito;
},
},
};
}
};
extensions.registerSchemaAPI("extension", "addon_child", extensionApiFactory);
extensions.registerSchemaAPI("extension", "content_child", extensionApiFactory);
extensions.registerSchemaAPI("extension", "devtools_child", extensionApiFactory);
extensions.registerSchemaAPI("extension", "addon_child", context => {
return {
extension: {
getViews: function(fetchProperties) {
if (context.envType === "addon_child") {
api.getViews = function(fetchProperties) {
let result = Cu.cloneInto([], context.cloneScope);
for (let view of context.extension.views) {
@ -52,7 +45,9 @@ extensions.registerSchemaAPI("extension", "addon_child", context => {
}
return result;
},
},
};
});
};
}
return {extension: api};
}
};

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

@ -3,7 +3,7 @@
/* global redirectDomain */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC} = Components;
var {Constructor: CC} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -18,8 +18,7 @@ let CryptoHash = CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithSt
Cu.importGlobalProperties(["URL", "XMLHttpRequest", "TextEncoder"]);
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
var {
promiseDocumentLoaded,
} = ExtensionUtils;
@ -107,53 +106,55 @@ function openOAuthWindow(details, redirectURI) {
});
}
extensions.registerSchemaAPI("identity", "addon_child", context => {
let {extension} = context;
return {
identity: {
launchWebAuthFlow: function(details) {
// In OAuth2 the url should have a redirect_uri param, parse the url and grab it
let url, redirectURI;
try {
url = new URL(details.url);
} catch (e) {
return Promise.reject({message: "details.url is invalid"});
}
try {
redirectURI = new URL(url.searchParams.get("redirect_uri"));
if (!redirectURI) {
return Promise.reject({message: "redirect_uri is missing"});
this.identity = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
identity: {
launchWebAuthFlow: function(details) {
// In OAuth2 the url should have a redirect_uri param, parse the url and grab it
let url, redirectURI;
try {
url = new URL(details.url);
} catch (e) {
return Promise.reject({message: "details.url is invalid"});
}
} catch (e) {
return Promise.reject({message: "redirect_uri is invalid"});
}
if (!redirectURI.href.startsWith(this.getRedirectURL())) {
// Any url will work, but we suggest addons use getRedirectURL.
Services.console.logStringMessage("WebExtensions: redirect_uri should use browser.identity.getRedirectURL");
}
// If the request is automatically redirected the user has already
// authorized and we do not want to show the window.
return checkRedirected(details.url, redirectURI).catch((requestError) => {
// requestError is zero or xhr.status
if (requestError !== 0) {
Cu.reportError(`browser.identity auth check failed with ${requestError}`);
return Promise.reject({message: "Invalid request"});
try {
redirectURI = new URL(url.searchParams.get("redirect_uri"));
if (!redirectURI) {
return Promise.reject({message: "redirect_uri is missing"});
}
} catch (e) {
return Promise.reject({message: "redirect_uri is invalid"});
}
if (!details.interactive) {
return Promise.reject({message: `Requires user interaction`});
if (!redirectURI.href.startsWith(this.getRedirectURL())) {
// Any url will work, but we suggest addons use getRedirectURL.
Services.console.logStringMessage("WebExtensions: redirect_uri should use browser.identity.getRedirectURL");
}
return openOAuthWindow(details, redirectURI);
});
// If the request is automatically redirected the user has already
// authorized and we do not want to show the window.
return checkRedirected(details.url, redirectURI).catch((requestError) => {
// requestError is zero or xhr.status
if (requestError !== 0) {
Cu.reportError(`browser.identity auth check failed with ${requestError}`);
return Promise.reject({message: "Invalid request"});
}
if (!details.interactive) {
return Promise.reject({message: `Requires user interaction`});
}
return openOAuthWindow(details, redirectURI);
});
},
getRedirectURL: function(path = "") {
let hash = computeHash(extension.id);
let url = new URL(`https://${hash}.${redirectDomain}/`);
url.pathname = path;
return url.href;
},
},
getRedirectURL: function(path = "") {
let hash = computeHash(extension.id);
let url = new URL(`https://${hash}.${redirectDomain}/`);
url.pathname = path;
return url.href;
},
},
};
});
};
}
};

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

@ -1,19 +1,22 @@
"use strict";
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {ExtensionError} = ExtensionUtils;
var {
ExtensionError,
} = ExtensionUtils;
extensions.registerSchemaAPI("permissions", "addon_child", context => {
return {
permissions: {
async request(perms) {
let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
if (!winUtils.isHandlingUserInput) {
throw new ExtensionError("May only request permissions from a user input handler");
}
this.permissions = class extends ExtensionAPI {
getAPI(context) {
return {
permissions: {
async request(perms) {
let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
if (!winUtils.isHandlingUserInput) {
throw new ExtensionError("May only request permissions from a user input handler");
}
return context.childManager.callParentAsyncFunction("permissions.request_parent", [perms]);
return context.childManager.callParentAsyncFunction("permissions.request_parent", [perms]);
},
},
},
};
});
};
}
};

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

@ -1,106 +1,103 @@
"use strict";
function runtimeApiFactory(context) {
let {extension} = context;
this.runtime = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
runtime: {
onConnect: context.messenger.onConnect("runtime.onConnect"),
return {
runtime: {
onConnect: context.messenger.onConnect("runtime.onConnect"),
onMessage: context.messenger.onMessage("runtime.onMessage"),
onMessage: context.messenger.onMessage("runtime.onMessage"),
onConnectExternal: context.messenger.onConnectExternal("runtime.onConnectExternal"),
onConnectExternal: context.messenger.onConnectExternal("runtime.onConnectExternal"),
onMessageExternal: context.messenger.onMessageExternal("runtime.onMessageExternal"),
onMessageExternal: context.messenger.onMessageExternal("runtime.onMessageExternal"),
connect: function(extensionId, connectInfo) {
let name = (connectInfo !== null && connectInfo.name) || "";
extensionId = extensionId || extension.id;
let recipient = {extensionId};
connect: function(extensionId, connectInfo) {
let name = (connectInfo !== null && connectInfo.name) || "";
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.connect(context.messageManager, name, recipient);
},
return context.messenger.connect(context.messageManager, name, recipient);
},
sendMessage: function(...args) {
let options; // eslint-disable-line no-unused-vars
let extensionId, message, responseCallback;
if (typeof args[args.length - 1] === "function") {
responseCallback = args.pop();
}
if (!args.length) {
return Promise.reject({message: "runtime.sendMessage's message argument is missing"});
} else if (args.length === 1) {
message = args[0];
} else if (args.length === 2) {
if (typeof args[0] === "string" && args[0]) {
[extensionId, message] = args;
sendMessage: function(...args) {
let options; // eslint-disable-line no-unused-vars
let extensionId, message, responseCallback;
if (typeof args[args.length - 1] === "function") {
responseCallback = args.pop();
}
if (!args.length) {
return Promise.reject({message: "runtime.sendMessage's message argument is missing"});
} else if (args.length === 1) {
message = args[0];
} else if (args.length === 2) {
if (typeof args[0] === "string" && args[0]) {
[extensionId, message] = args;
} else {
[message, options] = args;
}
} else if (args.length === 3) {
[extensionId, message, options] = args;
} else if (args.length === 4 && !responseCallback) {
return Promise.reject({message: "runtime.sendMessage's last argument is not a function"});
} else {
[message, options] = args;
return Promise.reject({message: "runtime.sendMessage received too many arguments"});
}
} else if (args.length === 3) {
[extensionId, message, options] = args;
} else if (args.length === 4 && !responseCallback) {
return Promise.reject({message: "runtime.sendMessage's last argument is not a function"});
} else {
return Promise.reject({message: "runtime.sendMessage received too many arguments"});
}
if (extensionId != null && typeof extensionId !== "string") {
return Promise.reject({message: "runtime.sendMessage's extensionId argument is invalid"});
}
extensionId = extensionId || extension.id;
let recipient = {extensionId};
if (options != null) {
if (typeof options !== "object") {
return Promise.reject({message: "runtime.sendMessage's options argument is invalid"});
if (extensionId != null && typeof extensionId !== "string") {
return Promise.reject({message: "runtime.sendMessage's extensionId argument is invalid"});
}
if (typeof options.toProxyScript === "boolean") {
recipient.toProxyScript = options.toProxyScript;
} else {
return Promise.reject({message: "runtime.sendMessage's options.toProxyScript argument is invalid"});
extensionId = extensionId || extension.id;
let recipient = {extensionId};
if (options != null) {
if (typeof options !== "object") {
return Promise.reject({message: "runtime.sendMessage's options argument is invalid"});
}
if (typeof options.toProxyScript === "boolean") {
recipient.toProxyScript = options.toProxyScript;
} else {
return Promise.reject({message: "runtime.sendMessage's options.toProxyScript argument is invalid"});
}
}
}
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
},
connectNative(application) {
let recipient = {
childId: context.childManager.id,
toNativeApp: application,
};
return context.messenger.connectNative(context.messageManager, "", recipient);
},
sendNativeMessage(application, message) {
let recipient = {
childId: context.childManager.id,
toNativeApp: application,
};
return context.messenger.sendNativeMessage(context.messageManager, message, recipient);
},
get lastError() {
return context.lastError;
},
getManifest() {
return Cu.cloneInto(extension.manifest, context.cloneScope);
},
id: extension.id,
getURL: function(url) {
return extension.baseURI.resolve(url);
},
},
connectNative(application) {
let recipient = {
childId: context.childManager.id,
toNativeApp: application,
};
return context.messenger.connectNative(context.messageManager, "", recipient);
},
sendNativeMessage(application, message) {
let recipient = {
childId: context.childManager.id,
toNativeApp: application,
};
return context.messenger.sendNativeMessage(context.messageManager, message, recipient);
},
get lastError() {
return context.lastError;
},
getManifest() {
return Cu.cloneInto(extension.manifest, context.cloneScope);
},
id: extension.id,
getURL: function(url) {
return extension.baseURI.resolve(url);
},
},
};
}
extensions.registerSchemaAPI("runtime", "addon_child", runtimeApiFactory);
extensions.registerSchemaAPI("runtime", "content_child", runtimeApiFactory);
extensions.registerSchemaAPI("runtime", "devtools_child", runtimeApiFactory);
extensions.registerSchemaAPI("runtime", "proxy_script", runtimeApiFactory);
};
}
};

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

@ -4,59 +4,59 @@ XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
"resource://gre/modules/ExtensionStorage.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function storageApiFactory(context) {
function sanitize(items) {
// The schema validator already takes care of arrays (which are only allowed
// to contain strings). Strings and null are safe values.
if (typeof items != "object" || items === null || Array.isArray(items)) {
return items;
this.storage = class extends ExtensionAPI {
getAPI(context) {
function sanitize(items) {
// The schema validator already takes care of arrays (which are only allowed
// to contain strings). Strings and null are safe values.
if (typeof items != "object" || items === null || Array.isArray(items)) {
return items;
}
// If we got here, then `items` is an object generated by `ObjectType`'s
// `normalize` method from Schemas.jsm. The object returned by `normalize`
// lives in this compartment, while the values live in compartment of
// `context.contentWindow`. The `sanitize` method runs with the principal
// of `context`, so we cannot just use `ExtensionStorage.sanitize` because
// it is not allowed to access properties of `items`.
// So we enumerate all properties and sanitize each value individually.
let sanitized = {};
for (let [key, value] of Object.entries(items)) {
sanitized[key] = ExtensionStorage.sanitize(value, context);
}
return sanitized;
}
// If we got here, then `items` is an object generated by `ObjectType`'s
// `normalize` method from Schemas.jsm. The object returned by `normalize`
// lives in this compartment, while the values live in compartment of
// `context.contentWindow`. The `sanitize` method runs with the principal
// of `context`, so we cannot just use `ExtensionStorage.sanitize` because
// it is not allowed to access properties of `items`.
// So we enumerate all properties and sanitize each value individually.
let sanitized = {};
for (let [key, value] of Object.entries(items)) {
sanitized[key] = ExtensionStorage.sanitize(value, context);
}
return sanitized;
}
return {
storage: {
local: {
get: function(keys) {
keys = sanitize(keys);
return context.childManager.callParentAsyncFunction("storage.local.get", [
keys,
]);
return {
storage: {
local: {
get: function(keys) {
keys = sanitize(keys);
return context.childManager.callParentAsyncFunction("storage.local.get", [
keys,
]);
},
set: function(items) {
items = sanitize(items);
return context.childManager.callParentAsyncFunction("storage.local.set", [
items,
]);
},
},
set: function(items) {
items = sanitize(items);
return context.childManager.callParentAsyncFunction("storage.local.set", [
items,
]);
},
},
sync: {
get: function(keys) {
keys = sanitize(keys);
return context.childManager.callParentAsyncFunction("storage.sync.get", [
keys,
]);
},
set: function(items) {
items = sanitize(items);
return context.childManager.callParentAsyncFunction("storage.sync.set", [
items,
]);
sync: {
get: function(keys) {
keys = sanitize(keys);
return context.childManager.callParentAsyncFunction("storage.sync.get", [
keys,
]);
},
set: function(items) {
items = sanitize(items);
return context.childManager.callParentAsyncFunction("storage.sync.set", [
items,
]);
},
},
},
},
};
}
extensions.registerSchemaAPI("storage", "addon_child", storageApiFactory);
extensions.registerSchemaAPI("storage", "content_child", storageApiFactory);
};
}
};

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

@ -1,6 +1,5 @@
"use strict";
Components.utils.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
@ -75,114 +74,112 @@ function toSource(value) {
}
}
function makeTestAPI(context) {
const {extension} = context;
this.test = class extends ExtensionAPI {
getAPI(context) {
const {extension} = context;
function getStack() {
return new context.cloneScope.Error().stack.replace(/^/gm, " ");
function getStack() {
return new context.cloneScope.Error().stack.replace(/^/gm, " ");
}
function assertTrue(value, msg) {
extension.emit("test-result", Boolean(value), String(msg), getStack());
}
return {
test: {
sendMessage(...args) {
extension.emit("test-message", ...args);
},
notifyPass(msg) {
extension.emit("test-done", true, msg, getStack());
},
notifyFail(msg) {
extension.emit("test-done", false, msg, getStack());
},
log(msg) {
extension.emit("test-log", true, msg, getStack());
},
fail(msg) {
assertTrue(false, msg);
},
succeed(msg) {
assertTrue(true, msg);
},
assertTrue(value, msg) {
assertTrue(value, msg);
},
assertFalse(value, msg) {
assertTrue(!value, msg);
},
assertEq(expected, actual, msg) {
let equal = expected === actual;
expected = String(expected);
actual = String(actual);
if (!equal && expected === actual) {
actual += " (different)";
}
extension.emit("test-eq", equal, String(msg), expected, actual, getStack());
},
assertRejects(promise, expectedError, msg) {
// Wrap in a native promise for consistency.
promise = Promise.resolve(promise);
if (msg) {
msg = `: ${msg}`;
}
return promise.then(result => {
assertTrue(false, `Promise resolved, expected rejection${msg}`);
}, error => {
let errorMessage = toSource(error && error.message);
assertTrue(errorMatches(error, expectedError, context),
`Promise rejected, expecting rejection to match ${toSource(expectedError)}, ` +
`got ${errorMessage}${msg}`);
});
},
assertThrows(func, expectedError, msg) {
if (msg) {
msg = `: ${msg}`;
}
try {
func();
assertTrue(false, `Function did not throw, expected error${msg}`);
} catch (error) {
let errorMessage = toSource(error && error.message);
assertTrue(errorMatches(error, expectedError, context),
`Function threw, expecting error to match ${toSource(expectedError)}` +
`got ${errorMessage}${msg}`);
}
},
onMessage: new SingletonEventManager(context, "test.onMessage", fire => {
let handler = (event, ...args) => {
fire.async(...args);
};
extension.on("test-harness-message", handler);
return () => {
extension.off("test-harness-message", handler);
};
}).api(),
},
};
}
function assertTrue(value, msg) {
extension.emit("test-result", Boolean(value), String(msg), getStack());
}
return {
test: {
sendMessage(...args) {
extension.emit("test-message", ...args);
},
notifyPass(msg) {
extension.emit("test-done", true, msg, getStack());
},
notifyFail(msg) {
extension.emit("test-done", false, msg, getStack());
},
log(msg) {
extension.emit("test-log", true, msg, getStack());
},
fail(msg) {
assertTrue(false, msg);
},
succeed(msg) {
assertTrue(true, msg);
},
assertTrue(value, msg) {
assertTrue(value, msg);
},
assertFalse(value, msg) {
assertTrue(!value, msg);
},
assertEq(expected, actual, msg) {
let equal = expected === actual;
expected = String(expected);
actual = String(actual);
if (!equal && expected === actual) {
actual += " (different)";
}
extension.emit("test-eq", equal, String(msg), expected, actual, getStack());
},
assertRejects(promise, expectedError, msg) {
// Wrap in a native promise for consistency.
promise = Promise.resolve(promise);
if (msg) {
msg = `: ${msg}`;
}
return promise.then(result => {
assertTrue(false, `Promise resolved, expected rejection${msg}`);
}, error => {
let errorMessage = toSource(error && error.message);
assertTrue(errorMatches(error, expectedError, context),
`Promise rejected, expecting rejection to match ${toSource(expectedError)}, ` +
`got ${errorMessage}${msg}`);
});
},
assertThrows(func, expectedError, msg) {
if (msg) {
msg = `: ${msg}`;
}
try {
func();
assertTrue(false, `Function did not throw, expected error${msg}`);
} catch (error) {
let errorMessage = toSource(error && error.message);
assertTrue(errorMatches(error, expectedError, context),
`Function threw, expecting error to match ${toSource(expectedError)}` +
`got ${errorMessage}${msg}`);
}
},
onMessage: new SingletonEventManager(context, "test.onMessage", fire => {
let handler = (event, ...args) => {
fire.async(...args);
};
extension.on("test-harness-message", handler);
return () => {
extension.off("test-harness-message", handler);
};
}).api(),
},
};
}
extensions.registerSchemaAPI("test", "addon_child", makeTestAPI);
extensions.registerSchemaAPI("test", "content_child", makeTestAPI);
extensions.registerSchemaAPI("test", "devtools_child", makeTestAPI);
};

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

@ -0,0 +1,89 @@
"use strict";
global.initializeBackgroundPage = (contentWindow) => {
// Override the `alert()` method inside background windows;
// we alias it to console.log().
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
let alertDisplayedWarning = false;
let alertOverwrite = text => {
if (!alertDisplayedWarning) {
require("devtools/client/framework/devtools-browser");
let hudservice = require("devtools/client/webconsole/hudservice");
hudservice.openBrowserConsoleOrFocus();
contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead.");
alertDisplayedWarning = true;
}
contentWindow.console.log(text);
};
Cu.exportFunction(alertOverwrite, contentWindow, {defineAs: "alert"});
};
extensions.registerModules({
backgroundPage: {
url: "chrome://extensions/content/ext-c-backgroundPage.js",
scopes: ["addon_child"],
manifest: ["background"],
paths: [
["extension", "getBackgroundPage"],
["runtime", "getBackgroundPage"],
],
},
extension: {
url: "chrome://extensions/content/ext-c-extension.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["extension"],
],
},
i18n: {
url: "chrome://extensions/content/ext-i18n.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["i18n"],
],
},
permissions: {
url: "chrome://extensions/content/ext-c-permissions.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["permissions"],
],
},
runtime: {
url: "chrome://extensions/content/ext-c-runtime.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["runtime"],
],
},
storage: {
url: "chrome://extensions/content/ext-c-storage.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["storage"],
],
},
test: {
url: "chrome://extensions/content/ext-c-test.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
paths: [
["test"],
],
},
});
if (AppConstants.MOZ_BUILD_APP === "browser") {
extensions.registerModules({
identity: {
url: "chrome://extensions/content/ext-c-identity.js",
scopes: ["addon_child"],
paths: [
["identity"],
],
},
});
}

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

@ -1,7 +1,5 @@
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
@ -16,94 +14,96 @@ function convert(identity) {
return result;
}
extensions.registerSchemaAPI("contextualIdentities", "addon_parent", context => {
let self = {
contextualIdentities: {
get(cookieStoreId) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
return Promise.resolve(convert(identity));
},
query(details) {
let identities = [];
ContextualIdentityService.getPublicIdentities().forEach(identity => {
if (details.name &&
ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
return;
this.contextualIdentities = class extends ExtensionAPI {
getAPI(context) {
let self = {
contextualIdentities: {
get(cookieStoreId) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
identities.push(convert(identity));
});
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
return Promise.resolve(convert(identity));
},
return Promise.resolve(identities);
query(details) {
let identities = [];
ContextualIdentityService.getPublicIdentities().forEach(identity => {
if (details.name &&
ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
return;
}
identities.push(convert(identity));
});
return Promise.resolve(identities);
},
create(details) {
let identity = ContextualIdentityService.create(details.name,
details.icon,
details.color);
return Promise.resolve(convert(identity));
},
update(cookieStoreId, details) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
}
if (details.name !== null) {
identity.name = details.name;
}
if (details.color !== null) {
identity.color = details.color;
}
if (details.icon !== null) {
identity.icon = details.icon;
}
if (!ContextualIdentityService.update(identity.userContextId,
identity.name, identity.icon,
identity.color)) {
return Promise.resolve(null);
}
return Promise.resolve(convert(identity));
},
remove(cookieStoreId) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
}
// We have to create the identity object before removing it.
let convertedIdentity = convert(identity);
if (!ContextualIdentityService.remove(identity.userContextId)) {
return Promise.resolve(null);
}
return Promise.resolve(convertedIdentity);
},
},
};
create(details) {
let identity = ContextualIdentityService.create(details.name,
details.icon,
details.color);
return Promise.resolve(convert(identity));
},
update(cookieStoreId, details) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
}
if (details.name !== null) {
identity.name = details.name;
}
if (details.color !== null) {
identity.color = details.color;
}
if (details.icon !== null) {
identity.icon = details.icon;
}
if (!ContextualIdentityService.update(identity.userContextId,
identity.name, identity.icon,
identity.color)) {
return Promise.resolve(null);
}
return Promise.resolve(convert(identity));
},
remove(cookieStoreId) {
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
}
// We have to create the identity object before removing it.
let convertedIdentity = convert(identity);
if (!ContextualIdentityService.remove(identity.userContextId)) {
return Promise.resolve(null);
}
return Promise.resolve(convertedIdentity);
},
},
};
return self;
});
return self;
}
};

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

@ -1,68 +1,16 @@
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
/* globals DEFAULT_STORE, PRIVATE_STORE */
var {
SingletonEventManager,
} = ExtensionUtils;
var DEFAULT_STORE = "firefox-default";
var PRIVATE_STORE = "firefox-private";
var CONTAINER_STORE = "firefox-container-";
global.getCookieStoreIdForTab = function(data, tab) {
if (data.incognito) {
return PRIVATE_STORE;
}
if (tab.userContextId) {
return getCookieStoreIdForContainer(tab.userContextId);
}
return DEFAULT_STORE;
};
global.isPrivateCookieStoreId = function(storeId) {
return storeId == PRIVATE_STORE;
};
global.isDefaultCookieStoreId = function(storeId) {
return storeId == DEFAULT_STORE;
};
global.isContainerCookieStoreId = function(storeId) {
return storeId !== null && storeId.startsWith(CONTAINER_STORE);
};
global.getCookieStoreIdForContainer = function(containerId) {
return CONTAINER_STORE + containerId;
};
global.getContainerForCookieStoreId = function(storeId) {
if (!isContainerCookieStoreId(storeId)) {
return null;
}
let containerId = storeId.substring(CONTAINER_STORE.length);
if (ContextualIdentityService.getPublicIdentityFromId(containerId)) {
return parseInt(containerId, 10);
}
return null;
};
global.isValidCookieStoreId = function(storeId) {
return isDefaultCookieStoreId(storeId) ||
isPrivateCookieStoreId(storeId) ||
isContainerCookieStoreId(storeId);
};
function convert({cookie, isPrivate}) {
let result = {
name: cookie.name,
@ -329,157 +277,159 @@ function* query(detailsIn, props, context) {
}
}
extensions.registerSchemaAPI("cookies", "addon_parent", context => {
let {extension} = context;
let self = {
cookies: {
get: function(details) {
// FIXME: We don't sort by length of path and creation time.
for (let cookie of query(details, ["url", "name", "storeId"], context)) {
return Promise.resolve(convert(cookie));
}
// Found no match.
return Promise.resolve(null);
},
getAll: function(details) {
let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"];
let result = Array.from(query(details, allowed, context), convert);
return Promise.resolve(result);
},
set: function(details) {
let uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL);
let path;
if (details.path !== null) {
path = details.path;
} else {
// This interface essentially emulates the behavior of the
// Set-Cookie header. In the case of an omitted path, the cookie
// service uses the directory path of the requesting URL, ignoring
// any filename or query parameters.
path = uri.directory;
}
let name = details.name !== null ? details.name : "";
let value = details.value !== null ? details.value : "";
let secure = details.secure !== null ? details.secure : false;
let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
let isSession = details.expirationDate === null;
let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
let isPrivate = context.incognito;
let userContextId = 0;
if (isDefaultCookieStoreId(details.storeId)) {
isPrivate = false;
} else if (isPrivateCookieStoreId(details.storeId)) {
isPrivate = true;
} else if (isContainerCookieStoreId(details.storeId)) {
let containerId = getContainerForCookieStoreId(details.storeId);
if (containerId === null) {
return Promise.reject({message: `Illegal storeId: ${details.storeId}`});
this.cookies = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
let self = {
cookies: {
get: function(details) {
// FIXME: We don't sort by length of path and creation time.
for (let cookie of query(details, ["url", "name", "storeId"], context)) {
return Promise.resolve(convert(cookie));
}
isPrivate = false;
userContextId = containerId;
} else if (details.storeId !== null) {
return Promise.reject({message: "Unknown storeId"});
}
let cookieAttrs = {host: details.domain, path: path, isSecure: secure};
if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`});
}
// Found no match.
return Promise.resolve(null);
},
// The permission check may have modified the domain, so use
// the new value instead.
Services.cookies.usePrivateMode(isPrivate, () => {
Services.cookies.add(cookieAttrs.host, path, name, value,
secure, httpOnly, isSession, expiry, {userContextId});
});
getAll: function(details) {
let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"];
let result = Array.from(query(details, allowed, context), convert);
return self.cookies.get(details);
},
return Promise.resolve(result);
},
remove: function(details) {
for (let {cookie, isPrivate, storeId} of query(details, ["url", "name", "storeId"], context)) {
set: function(details) {
let uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL);
let path;
if (details.path !== null) {
path = details.path;
} else {
// This interface essentially emulates the behavior of the
// Set-Cookie header. In the case of an omitted path, the cookie
// service uses the directory path of the requesting URL, ignoring
// any filename or query parameters.
path = uri.directory;
}
let name = details.name !== null ? details.name : "";
let value = details.value !== null ? details.value : "";
let secure = details.secure !== null ? details.secure : false;
let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
let isSession = details.expirationDate === null;
let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
let isPrivate = context.incognito;
let userContextId = 0;
if (isDefaultCookieStoreId(details.storeId)) {
isPrivate = false;
} else if (isPrivateCookieStoreId(details.storeId)) {
isPrivate = true;
} else if (isContainerCookieStoreId(details.storeId)) {
let containerId = getContainerForCookieStoreId(details.storeId);
if (containerId === null) {
return Promise.reject({message: `Illegal storeId: ${details.storeId}`});
}
isPrivate = false;
userContextId = containerId;
} else if (details.storeId !== null) {
return Promise.reject({message: "Unknown storeId"});
}
let cookieAttrs = {host: details.domain, path: path, isSecure: secure};
if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`});
}
// The permission check may have modified the domain, so use
// the new value instead.
Services.cookies.usePrivateMode(isPrivate, () => {
Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
Services.cookies.add(cookieAttrs.host, path, name, value,
secure, httpOnly, isSession, expiry, {userContextId});
});
// Todo: could there be multiple per subdomain?
return Promise.resolve({
url: details.url,
name: details.name,
storeId,
});
}
return self.cookies.get(details);
},
return Promise.resolve(null);
},
remove: function(details) {
for (let {cookie, isPrivate, storeId} of query(details, ["url", "name", "storeId"], context)) {
Services.cookies.usePrivateMode(isPrivate, () => {
Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
});
getAllCookieStores: function() {
let data = {};
for (let tab of extension.tabManager.query()) {
if (!(tab.cookieStoreId in data)) {
data[tab.cookieStoreId] = [];
// Todo: could there be multiple per subdomain?
return Promise.resolve({
url: details.url,
name: details.name,
storeId,
});
}
data[tab.cookieStoreId].push(tab.id);
}
let result = [];
for (let key in data) {
result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE});
}
return Promise.resolve(result);
},
return Promise.resolve(null);
},
onChanged: new SingletonEventManager(context, "cookies.onChanged", fire => {
let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2);
getAllCookieStores: function() {
let data = {};
for (let tab of extension.tabManager.query()) {
if (!(tab.cookieStoreId in data)) {
data[tab.cookieStoreId] = [];
}
data[tab.cookieStoreId].push(tab.id);
}
if (extension.whiteListedHosts.matchesCookie(cookie)) {
fire.async({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
let result = [];
for (let key in data) {
result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE});
}
return Promise.resolve(result);
},
onChanged: new SingletonEventManager(context, "cookies.onChanged", fire => {
let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2);
if (extension.whiteListedHosts.matchesCookie(cookie)) {
fire.async({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
}
};
// We do our best effort here to map the incompatible states.
switch (data) {
case "deleted":
notify(true, subject, "explicit");
break;
case "added":
notify(false, subject, "explicit");
break;
case "changed":
notify(true, subject, "overwrite");
notify(false, subject, "explicit");
break;
case "batch-deleted":
subject.QueryInterface(Ci.nsIArray);
for (let i = 0; i < subject.length; i++) {
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
notify(true, cookie, "expired");
} else {
notify(true, cookie, "evicted");
}
}
break;
}
};
// We do our best effort here to map the incompatible states.
switch (data) {
case "deleted":
notify(true, subject, "explicit");
break;
case "added":
notify(false, subject, "explicit");
break;
case "changed":
notify(true, subject, "overwrite");
notify(false, subject, "explicit");
break;
case "batch-deleted":
subject.QueryInterface(Ci.nsIArray);
for (let i = 0; i < subject.length; i++) {
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
notify(true, cookie, "expired");
} else {
notify(true, cookie, "evicted");
}
}
break;
}
};
Services.obs.addObserver(observer, "cookie-changed", false);
Services.obs.addObserver(observer, "private-cookie-changed", false);
return () => {
Services.obs.removeObserver(observer, "cookie-changed");
Services.obs.removeObserver(observer, "private-cookie-changed");
};
}).api(),
},
};
Services.obs.addObserver(observer, "cookie-changed", false);
Services.obs.addObserver(observer, "private-cookie-changed", false);
return () => {
Services.obs.removeObserver(observer, "cookie-changed");
Services.obs.removeObserver(observer, "private-cookie-changed");
};
}).api(),
},
};
return self;
});
return self;
}
};

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

@ -1,9 +1,5 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
@ -17,8 +13,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource://devtools/shared/event-emitter.js");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
var {
ignoreEvent,
normalizeTime,
SingletonEventManager,
@ -394,405 +389,407 @@ function queryHelper(query) {
});
}
extensions.registerSchemaAPI("downloads", "addon_parent", context => {
let {extension} = context;
return {
downloads: {
download(options) {
let {filename} = options;
if (filename && PlatformInfo.os === "win") {
// cross platform javascript code uses "/"
filename = filename.replace(/\//g, "\\");
}
if (filename != null) {
if (filename.length == 0) {
return Promise.reject({message: "filename must not be empty"});
this.downloads = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
downloads: {
download(options) {
let {filename} = options;
if (filename && PlatformInfo.os === "win") {
// cross platform javascript code uses "/"
filename = filename.replace(/\//g, "\\");
}
let path = OS.Path.split(filename);
if (path.absolute) {
return Promise.reject({message: "filename must not be an absolute path"});
}
if (filename != null) {
if (filename.length == 0) {
return Promise.reject({message: "filename must not be empty"});
}
if (path.components.some(component => component == "..")) {
return Promise.reject({message: "filename must not contain back-references (..)"});
}
}
let path = OS.Path.split(filename);
if (path.absolute) {
return Promise.reject({message: "filename must not be an absolute path"});
}
if (options.conflictAction == "prompt") {
// TODO
return Promise.reject({message: "conflictAction prompt not yet implemented"});
}
if (options.headers) {
for (let {name} of options.headers) {
if (FORBIDDEN_HEADERS.includes(name.toUpperCase()) || name.match(FORBIDDEN_PREFIXES)) {
return Promise.reject({message: "Forbidden request header name"});
if (path.components.some(component => component == "..")) {
return Promise.reject({message: "filename must not contain back-references (..)"});
}
}
}
// Handle method, headers and body options.
function adjustChannel(channel) {
if (channel instanceof Ci.nsIHttpChannel) {
const method = options.method || "GET";
channel.requestMethod = method;
if (options.conflictAction == "prompt") {
// TODO
return Promise.reject({message: "conflictAction prompt not yet implemented"});
}
if (options.headers) {
for (let {name, value} of options.headers) {
channel.setRequestHeader(name, value, false);
if (options.headers) {
for (let {name} of options.headers) {
if (FORBIDDEN_HEADERS.includes(name.toUpperCase()) || name.match(FORBIDDEN_PREFIXES)) {
return Promise.reject({message: "Forbidden request header name"});
}
}
if (options.body != null) {
const stream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stream.setData(options.body, options.body.length);
channel.QueryInterface(Ci.nsIUploadChannel2);
channel.explicitSetUploadStream(stream, null, -1, method, false);
}
}
return Promise.resolve();
}
function createTarget(downloadsDir) {
let target;
if (filename) {
target = OS.Path.join(downloadsDir, filename);
} else {
let uri = NetUtil.newURI(options.url);
let remote = "download";
if (uri instanceof Ci.nsIURL) {
remote = uri.fileName;
}
target = OS.Path.join(downloadsDir, remote);
}
// Create any needed subdirectories if required by filename.
const dir = OS.Path.dirname(target);
return OS.File.makeDir(dir, {from: downloadsDir}).then(() => {
return OS.File.exists(target);
}).then(exists => {
// This has a race, something else could come along and create
// the file between this test and them time the download code
// creates the target file. But we can't easily fix it without
// modifying DownloadCore so we live with it for now.
if (exists) {
switch (options.conflictAction) {
case "uniquify":
default:
target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path;
break;
// Handle method, headers and body options.
function adjustChannel(channel) {
if (channel instanceof Ci.nsIHttpChannel) {
const method = options.method || "GET";
channel.requestMethod = method;
case "overwrite":
break;
}
}
}).then(() => {
if (!options.saveAs) {
return Promise.resolve(target);
}
// Setup the file picker Save As dialog.
const picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
const window = Services.wm.getMostRecentWindow("navigator:browser");
picker.init(window, null, Ci.nsIFilePicker.modeSave);
picker.displayDirectory = new FileUtils.File(dir);
picker.appendFilters(Ci.nsIFilePicker.filterAll);
picker.defaultString = OS.Path.basename(target);
// Open the dialog and resolve/reject with the result.
return new Promise((resolve, reject) => {
picker.open(result => {
if (result === Ci.nsIFilePicker.returnCancel) {
reject({message: "Download canceled by the user"});
} else {
resolve(picker.file.path);
if (options.headers) {
for (let {name, value} of options.headers) {
channel.setRequestHeader(name, value, false);
}
}
if (options.body != null) {
const stream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stream.setData(options.body, options.body.length);
channel.QueryInterface(Ci.nsIUploadChannel2);
channel.explicitSetUploadStream(stream, null, -1, method, false);
}
}
return Promise.resolve();
}
function createTarget(downloadsDir) {
let target;
if (filename) {
target = OS.Path.join(downloadsDir, filename);
} else {
let uri = NetUtil.newURI(options.url);
let remote = "download";
if (uri instanceof Ci.nsIURL) {
remote = uri.fileName;
}
target = OS.Path.join(downloadsDir, remote);
}
// Create any needed subdirectories if required by filename.
const dir = OS.Path.dirname(target);
return OS.File.makeDir(dir, {from: downloadsDir}).then(() => {
return OS.File.exists(target);
}).then(exists => {
// This has a race, something else could come along and create
// the file between this test and them time the download code
// creates the target file. But we can't easily fix it without
// modifying DownloadCore so we live with it for now.
if (exists) {
switch (options.conflictAction) {
case "uniquify":
default:
target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path;
break;
case "overwrite":
break;
}
}
}).then(() => {
if (!options.saveAs) {
return Promise.resolve(target);
}
// Setup the file picker Save As dialog.
const picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
const window = Services.wm.getMostRecentWindow("navigator:browser");
picker.init(window, null, Ci.nsIFilePicker.modeSave);
picker.displayDirectory = new FileUtils.File(dir);
picker.appendFilters(Ci.nsIFilePicker.filterAll);
picker.defaultString = OS.Path.basename(target);
// Open the dialog and resolve/reject with the result.
return new Promise((resolve, reject) => {
picker.open(result => {
if (result === Ci.nsIFilePicker.returnCancel) {
reject({message: "Download canceled by the user"});
} else {
resolve(picker.file.path);
}
});
});
});
});
}
let download;
return Downloads.getPreferredDownloadsDirectory()
.then(downloadsDir => createTarget(downloadsDir))
.then(target => {
const source = {
url: options.url,
};
if (options.method || options.headers || options.body) {
source.adjustChannel = adjustChannel;
}
return Downloads.createDownload({
source,
target: {
path: target,
partFilePath: target + ".part",
},
});
}).then(dl => {
download = dl;
return DownloadMap.getDownloadList();
}).then(list => {
list.add(download);
// This is necessary to make pause/resume work.
download.tryToKeepPartialData = true;
download.start();
const item = DownloadMap.newFromDownload(download, extension);
return item.id;
});
},
removeFile(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.state !== "complete") {
return Promise.reject({message: `Cannot remove incomplete download id ${id}`});
}
return OS.File.remove(item.filename, {ignoreAbsent: false}).catch((err) => {
return Promise.reject({message: `Could not remove download id ${item.id} because the file doesn't exist`});
});
});
},
search(query) {
return queryHelper(query)
.then(items => items.map(item => item.serialize()));
},
pause(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.state != "in_progress") {
return Promise.reject({message: `Download ${id} cannot be paused since it is in state ${item.state}`});
}
return item.download.cancel();
});
},
resume(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (!item.canResume) {
return Promise.reject({message: `Download ${id} cannot be resumed`});
}
return item.download.start();
});
},
cancel(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.download.succeeded) {
return Promise.reject({message: `Download ${id} is already complete`});
}
return item.download.finalize(true);
});
},
showDefaultFolder() {
Downloads.getPreferredDownloadsDirectory().then(dir => {
let dirobj = new FileUtils.File(dir);
if (dirobj.isDirectory()) {
dirobj.launch();
} else {
throw new Error(`Download directory ${dirobj.path} is not actually a directory`);
}
}).catch(Cu.reportError);
},
erase(query) {
return queryHelper(query).then(items => {
let results = [];
let promises = [];
for (let item of items) {
promises.push(DownloadMap.erase(item));
results.push(item.id);
}
return Promise.all(promises).then(() => results);
});
},
open(downloadId) {
return DownloadMap.lazyInit().then(() => {
let download = DownloadMap.fromId(downloadId).download;
if (download.succeeded) {
return download.launch();
}
return Promise.reject({message: "Download has not completed."});
}).catch((error) => {
return Promise.reject({message: error.message});
});
},
show(downloadId) {
return DownloadMap.lazyInit().then(() => {
let download = DownloadMap.fromId(downloadId);
return download.download.showContainingDirectory();
}).then(() => {
return true;
}).catch(error => {
return Promise.reject({message: error.message});
});
},
getFileIcon(downloadId, options) {
return DownloadMap.lazyInit().then(() => {
let size = options && options.size ? options.size : 32;
let download = DownloadMap.fromId(downloadId).download;
let pathPrefix = "";
let path;
if (download.succeeded) {
let file = FileUtils.File(download.target.path);
path = Services.io.newFileURI(file).spec;
} else {
path = OS.Path.basename(download.target.path);
pathPrefix = "//";
}
return new Promise((resolve, reject) => {
let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
chromeWebNav
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
let img = chromeWebNav.document.createElement("img");
img.width = size;
img.height = size;
let handleLoad;
let handleError;
const cleanup = () => {
img.removeEventListener("load", handleLoad);
img.removeEventListener("error", handleError);
chromeWebNav.close();
chromeWebNav = null;
};
handleLoad = () => {
let canvas = chromeWebNav.document.createElement("canvas");
canvas.width = size;
canvas.height = size;
let context = canvas.getContext("2d");
context.drawImage(img, 0, 0, size, size);
let dataURL = canvas.toDataURL("image/png");
cleanup();
resolve(dataURL);
};
handleError = (error) => {
Cu.reportError(error);
cleanup();
reject(new Error("An unexpected error occurred"));
};
img.addEventListener("load", handleLoad);
img.addEventListener("error", handleError);
img.src = `moz-icon:${pathPrefix}${path}?size=${size}`;
});
}).catch((error) => {
return Promise.reject({message: error.message});
});
},
// When we do setShelfEnabled(), check for additional "downloads.shelf" permission.
// i.e.:
// setShelfEnabled(enabled) {
// if (!extension.hasPermission("downloads.shelf")) {
// throw new context.cloneScope.Error("Permission denied because 'downloads.shelf' permission is missing.");
// }
// ...
// }
onChanged: new SingletonEventManager(context, "downloads.onChanged", fire => {
const handler = (what, item) => {
let changes = {};
const noundef = val => (val === undefined) ? null : val;
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
if (item[fld] != item.prechange[fld]) {
changes[fld] = {
previous: noundef(item.prechange[fld]),
current: noundef(item[fld]),
let download;
return Downloads.getPreferredDownloadsDirectory()
.then(downloadsDir => createTarget(downloadsDir))
.then(target => {
const source = {
url: options.url,
};
if (options.method || options.headers || options.body) {
source.adjustChannel = adjustChannel;
}
return Downloads.createDownload({
source,
target: {
path: target,
partFilePath: target + ".part",
},
});
}).then(dl => {
download = dl;
return DownloadMap.getDownloadList();
}).then(list => {
list.add(download);
// This is necessary to make pause/resume work.
download.tryToKeepPartialData = true;
download.start();
const item = DownloadMap.newFromDownload(download, extension);
return item.id;
});
},
removeFile(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.state !== "complete") {
return Promise.reject({message: `Cannot remove incomplete download id ${id}`});
}
return OS.File.remove(item.filename, {ignoreAbsent: false}).catch((err) => {
return Promise.reject({message: `Could not remove download id ${item.id} because the file doesn't exist`});
});
});
if (Object.keys(changes).length > 0) {
changes.id = item.id;
fire.async(changes);
}
};
},
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("change", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
search(query) {
return queryHelper(query)
.then(items => items.map(item => item.serialize()));
},
pause(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.state != "in_progress") {
return Promise.reject({message: `Download ${id} cannot be paused since it is in state ${item.state}`});
}
return item.download.cancel();
});
};
}).api(),
},
onCreated: new SingletonEventManager(context, "downloads.onCreated", fire => {
const handler = (what, item) => {
fire.async(item.serialize());
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("create", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("create", handler);
resume(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (!item.canResume) {
return Promise.reject({message: `Download ${id} cannot be resumed`});
}
return item.download.start();
});
};
}).api(),
},
onErased: new SingletonEventManager(context, "downloads.onErased", fire => {
const handler = (what, item) => {
fire.async(item.id);
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("erase", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("erase", handler);
cancel(id) {
return DownloadMap.lazyInit().then(() => {
let item;
try {
item = DownloadMap.fromId(id);
} catch (err) {
return Promise.reject({message: `Invalid download id ${id}`});
}
if (item.download.succeeded) {
return Promise.reject({message: `Download ${id} is already complete`});
}
return item.download.finalize(true);
});
};
}).api(),
},
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
},
};
});
showDefaultFolder() {
Downloads.getPreferredDownloadsDirectory().then(dir => {
let dirobj = new FileUtils.File(dir);
if (dirobj.isDirectory()) {
dirobj.launch();
} else {
throw new Error(`Download directory ${dirobj.path} is not actually a directory`);
}
}).catch(Cu.reportError);
},
erase(query) {
return queryHelper(query).then(items => {
let results = [];
let promises = [];
for (let item of items) {
promises.push(DownloadMap.erase(item));
results.push(item.id);
}
return Promise.all(promises).then(() => results);
});
},
open(downloadId) {
return DownloadMap.lazyInit().then(() => {
let download = DownloadMap.fromId(downloadId).download;
if (download.succeeded) {
return download.launch();
}
return Promise.reject({message: "Download has not completed."});
}).catch((error) => {
return Promise.reject({message: error.message});
});
},
show(downloadId) {
return DownloadMap.lazyInit().then(() => {
let download = DownloadMap.fromId(downloadId);
return download.download.showContainingDirectory();
}).then(() => {
return true;
}).catch(error => {
return Promise.reject({message: error.message});
});
},
getFileIcon(downloadId, options) {
return DownloadMap.lazyInit().then(() => {
let size = options && options.size ? options.size : 32;
let download = DownloadMap.fromId(downloadId).download;
let pathPrefix = "";
let path;
if (download.succeeded) {
let file = FileUtils.File(download.target.path);
path = Services.io.newFileURI(file).spec;
} else {
path = OS.Path.basename(download.target.path);
pathPrefix = "//";
}
return new Promise((resolve, reject) => {
let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
chromeWebNav
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
let img = chromeWebNav.document.createElement("img");
img.width = size;
img.height = size;
let handleLoad;
let handleError;
const cleanup = () => {
img.removeEventListener("load", handleLoad);
img.removeEventListener("error", handleError);
chromeWebNav.close();
chromeWebNav = null;
};
handleLoad = () => {
let canvas = chromeWebNav.document.createElement("canvas");
canvas.width = size;
canvas.height = size;
let context = canvas.getContext("2d");
context.drawImage(img, 0, 0, size, size);
let dataURL = canvas.toDataURL("image/png");
cleanup();
resolve(dataURL);
};
handleError = (error) => {
Cu.reportError(error);
cleanup();
reject(new Error("An unexpected error occurred"));
};
img.addEventListener("load", handleLoad);
img.addEventListener("error", handleError);
img.src = `moz-icon:${pathPrefix}${path}?size=${size}`;
});
}).catch((error) => {
return Promise.reject({message: error.message});
});
},
// When we do setShelfEnabled(), check for additional "downloads.shelf" permission.
// i.e.:
// setShelfEnabled(enabled) {
// if (!extension.hasPermission("downloads.shelf")) {
// throw new context.cloneScope.Error("Permission denied because 'downloads.shelf' permission is missing.");
// }
// ...
// }
onChanged: new SingletonEventManager(context, "downloads.onChanged", fire => {
const handler = (what, item) => {
let changes = {};
const noundef = val => (val === undefined) ? null : val;
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
if (item[fld] != item.prechange[fld]) {
changes[fld] = {
previous: noundef(item.prechange[fld]),
current: noundef(item[fld]),
};
}
});
if (Object.keys(changes).length > 0) {
changes.id = item.id;
fire.async(changes);
}
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("change", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
});
};
}).api(),
onCreated: new SingletonEventManager(context, "downloads.onCreated", fire => {
const handler = (what, item) => {
fire.async(item.serialize());
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("create", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("create", handler);
});
};
}).api(),
onErased: new SingletonEventManager(context, "downloads.onErased", fire => {
const handler = (what, item) => {
fire.async(item.id);
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("erase", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("erase", handler);
});
};
}).api(),
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
},
};
}
};

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

@ -1,20 +1,22 @@
"use strict";
extensions.registerSchemaAPI("extension", "addon_parent", context => {
return {
extension: {
get lastError() {
return context.lastError;
},
this.extension = class extends ExtensionAPI {
getAPI(context) {
return {
extension: {
get lastError() {
return context.lastError;
},
isAllowedIncognitoAccess() {
return Promise.resolve(true);
},
isAllowedIncognitoAccess() {
return Promise.resolve(true);
},
isAllowedFileSchemeAccess() {
return Promise.resolve(false);
isAllowedFileSchemeAccess() {
return Promise.resolve(false);
},
},
},
};
});
};
}
};

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

@ -1,7 +1,5 @@
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -10,19 +8,23 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
// permission if it is not set (unknown_action), and we only remove the
// permission on shutdown if it is always allow.
/* eslint-disable mozilla/balanced-listeners */
extensions.on("startup", (type, extension) => {
if (extension.hasPermission("geolocation") &&
Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.UNKNOWN_ACTION) {
Services.perms.add(extension.principal.URI, "geo",
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_SESSION);
}
});
this.geolocation = class extends ExtensionAPI {
onStartup() {
let {extension} = this;
extensions.on("shutdown", (type, extension) => {
if (extension.hasPermission("geolocation") &&
Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.ALLOW_ACTION) {
Services.perms.remove(extension.principal.URI, "geo");
if (extension.hasPermission("geolocation") &&
Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.UNKNOWN_ACTION) {
Services.perms.add(extension.principal.URI, "geo",
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_SESSION);
}
}
});
onShutdown() {
let {extension} = this;
if (extension.hasPermission("geolocation") &&
Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.ALLOW_ACTION) {
Services.perms.remove(extension.principal.URI, "geo");
}
}
};

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

@ -1,35 +1,31 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
detectLanguage,
} = ExtensionUtils;
function i18nApiFactory(context) {
let {extension} = context;
return {
i18n: {
getMessage: function(messageName, substitutions) {
return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
},
this.i18n = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
i18n: {
getMessage: function(messageName, substitutions) {
return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
},
getAcceptLanguages: function() {
let result = extension.localeData.acceptLanguages;
return Promise.resolve(result);
},
getAcceptLanguages: function() {
let result = extension.localeData.acceptLanguages;
return Promise.resolve(result);
},
getUILanguage: function() {
return extension.localeData.uiLocale;
},
getUILanguage: function() {
return extension.localeData.uiLocale;
},
detectLanguage: function(text) {
return detectLanguage(text);
detectLanguage: function(text) {
return detectLanguage(text);
},
},
},
};
}
extensions.registerSchemaAPI("i18n", "addon_child", i18nApiFactory);
extensions.registerSchemaAPI("i18n", "content_child", i18nApiFactory);
extensions.registerSchemaAPI("i18n", "devtools_child", i18nApiFactory);
};
}
};

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

@ -1,20 +1,16 @@
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource://devtools/shared/event-emitter.js");
XPCOMUtils.defineLazyServiceGetter(this, "idleService",
"@mozilla.org/widget/idleservice;1",
"nsIIdleService");
const {
var {
SingletonEventManager,
} = ExtensionUtils;
// WeakMap[Extension -> Object]
var observersMap = new WeakMap();
let observersMap = new WeakMap();
function getObserverInfo(extension, context) {
let observerInfo = observersMap.get(extension);
@ -66,29 +62,31 @@ function setDetectionInterval(extension, context, newInterval) {
observerInfo.detectionInterval = newInterval;
}
extensions.registerSchemaAPI("idle", "addon_parent", context => {
let {extension} = context;
return {
idle: {
queryState: function(detectionIntervalInSeconds) {
if (idleService.idleTime < detectionIntervalInSeconds * 1000) {
return Promise.resolve("active");
}
return Promise.resolve("idle");
},
setDetectionInterval: function(detectionIntervalInSeconds) {
setDetectionInterval(extension, context, detectionIntervalInSeconds);
},
onStateChanged: new SingletonEventManager(context, "idle.onStateChanged", fire => {
let listener = (event, data) => {
fire.sync(data);
};
this.idle = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
idle: {
queryState: function(detectionIntervalInSeconds) {
if (idleService.idleTime < detectionIntervalInSeconds * 1000) {
return Promise.resolve("active");
}
return Promise.resolve("idle");
},
setDetectionInterval: function(detectionIntervalInSeconds) {
setDetectionInterval(extension, context, detectionIntervalInSeconds);
},
onStateChanged: new SingletonEventManager(context, "idle.onStateChanged", fire => {
let listener = (event, data) => {
fire.sync(data);
};
getObserver(extension, context).on("stateChanged", listener);
return () => {
getObserver(extension, context).off("stateChanged", listener);
};
}).api(),
},
};
});
getObserver(extension, context).on("stateChanged", listener);
return () => {
getObserver(extension, context).off("stateChanged", listener);
};
}).api(),
},
};
}
};

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

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
return stringSvc.createBundle("chrome://global/locale/extensions.properties");
@ -32,78 +30,80 @@ function installType(addon) {
return "normal";
}
extensions.registerSchemaAPI("management", "addon_parent", context => {
let {extension} = context;
return {
management: {
getSelf: function() {
return new Promise((resolve, reject) => AddonManager.getAddonByID(extension.id, addon => {
try {
let m = extension.manifest;
let extInfo = {
id: extension.id,
name: addon.name,
shortName: m.short_name || "",
description: addon.description || "",
version: addon.version,
mayDisable: !!(addon.permissions & AddonManager.PERM_CAN_DISABLE),
enabled: addon.isActive,
optionsUrl: addon.optionsURL || "",
permissions: Array.from(extension.permissions).filter(perm => {
return !extension.whiteListedHosts.pat.includes(perm);
}),
hostPermissions: extension.whiteListedHosts.pat,
installType: installType(addon),
};
if (addon.homepageURL) {
extInfo.homepageUrl = addon.homepageURL;
}
if (addon.updateURL) {
extInfo.updateUrl = addon.updateURL;
}
if (m.icons) {
extInfo.icons = Object.keys(m.icons).map(key => {
return {size: Number(key), url: m.icons[key]};
});
}
resolve(extInfo);
} catch (err) {
reject(err);
}
}));
},
uninstallSelf: function(options) {
return new Promise((resolve, reject) => {
if (options && options.showConfirmDialog) {
let message = _("uninstall.confirmation.message", extension.name);
if (options.dialogMessage) {
message = `${options.dialogMessage}\n${message}`;
}
let title = _("uninstall.confirmation.title", extension.name);
let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING +
promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING;
let button0Title = _("uninstall.confirmation.button-0.label");
let button1Title = _("uninstall.confirmation.button-1.label");
let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0});
if (response == 1) {
return reject({message: "User cancelled uninstall of extension"});
}
}
AddonManager.getAddonByID(extension.id, addon => {
let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
if (!canUninstall) {
return reject({message: "The add-on cannot be uninstalled"});
}
this.management = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
management: {
getSelf: function() {
return new Promise((resolve, reject) => AddonManager.getAddonByID(extension.id, addon => {
try {
addon.uninstall();
let m = extension.manifest;
let extInfo = {
id: extension.id,
name: addon.name,
shortName: m.short_name || "",
description: addon.description || "",
version: addon.version,
mayDisable: !!(addon.permissions & AddonManager.PERM_CAN_DISABLE),
enabled: addon.isActive,
optionsUrl: addon.optionsURL || "",
permissions: Array.from(extension.permissions).filter(perm => {
return !extension.whiteListedHosts.pat.includes(perm);
}),
hostPermissions: extension.whiteListedHosts.pat,
installType: installType(addon),
};
if (addon.homepageURL) {
extInfo.homepageUrl = addon.homepageURL;
}
if (addon.updateURL) {
extInfo.updateUrl = addon.updateURL;
}
if (m.icons) {
extInfo.icons = Object.keys(m.icons).map(key => {
return {size: Number(key), url: m.icons[key]};
});
}
resolve(extInfo);
} catch (err) {
return reject(err);
reject(err);
}
}));
},
uninstallSelf: function(options) {
return new Promise((resolve, reject) => {
if (options && options.showConfirmDialog) {
let message = _("uninstall.confirmation.message", extension.name);
if (options.dialogMessage) {
message = `${options.dialogMessage}\n${message}`;
}
let title = _("uninstall.confirmation.title", extension.name);
let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING +
promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING;
let button0Title = _("uninstall.confirmation.button-0.label");
let button1Title = _("uninstall.confirmation.button-1.label");
let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0});
if (response == 1) {
return reject({message: "User cancelled uninstall of extension"});
}
}
AddonManager.getAddonByID(extension.id, addon => {
let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
if (!canUninstall) {
return reject({message: "The add-on cannot be uninstalled"});
}
try {
addon.uninstall();
} catch (err) {
return reject(err);
}
});
});
});
},
},
},
};
});
};
}
};

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

@ -1,9 +1,5 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource://devtools/shared/event-emitter.js");
@ -13,7 +9,7 @@ var {
} = ExtensionUtils;
// WeakMap[Extension -> Map[id -> Notification]]
var notificationsMap = new WeakMap();
let notificationsMap = new WeakMap();
// Manages a notification popup (notifications API) created by the extension.
function Notification(extension, id, options) {
@ -73,89 +69,94 @@ Notification.prototype = {
},
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("startup", (type, extension) => {
let map = new Map();
EventEmitter.decorate(map);
notificationsMap.set(extension, map);
});
this.notifications = class extends ExtensionAPI {
constructor(extension) {
super(extension);
extensions.on("shutdown", (type, extension) => {
if (notificationsMap.has(extension)) {
for (let notification of notificationsMap.get(extension).values()) {
notification.clear();
}
notificationsMap.delete(extension);
this.nextId = 0;
}
});
/* eslint-enable mozilla/balanced-listeners */
var nextId = 0;
onShutdown() {
let {extension} = this;
extensions.registerSchemaAPI("notifications", "addon_parent", context => {
let {extension} = context;
return {
notifications: {
create: function(notificationId, options) {
if (!notificationId) {
notificationId = String(nextId++);
}
if (notificationsMap.has(extension)) {
for (let notification of notificationsMap.get(extension).values()) {
notification.clear();
}
notificationsMap.delete(extension);
}
}
let notifications = notificationsMap.get(extension);
if (notifications.has(notificationId)) {
notifications.get(notificationId).clear();
}
getAPI(context) {
let {extension} = context;
// FIXME: Lots of options still aren't supported, especially
// buttons.
let notification = new Notification(extension, notificationId, options);
notificationsMap.get(extension).set(notificationId, notification);
let map = new Map();
EventEmitter.decorate(map);
notificationsMap.set(extension, map);
return Promise.resolve(notificationId);
return {
notifications: {
create: (notificationId, options) => {
if (!notificationId) {
notificationId = String(this.nextId++);
}
let notifications = notificationsMap.get(extension);
if (notifications.has(notificationId)) {
notifications.get(notificationId).clear();
}
// FIXME: Lots of options still aren't supported, especially
// buttons.
let notification = new Notification(extension, notificationId, options);
notificationsMap.get(extension).set(notificationId, notification);
return Promise.resolve(notificationId);
},
clear: function(notificationId) {
let notifications = notificationsMap.get(extension);
if (notifications.has(notificationId)) {
notifications.get(notificationId).clear();
return Promise.resolve(true);
}
return Promise.resolve(false);
},
getAll: function() {
let result = {};
notificationsMap.get(extension).forEach((value, key) => {
result[key] = value.options;
});
return Promise.resolve(result);
},
onClosed: new SingletonEventManager(context, "notifications.onClosed", fire => {
let listener = (event, notificationId) => {
// FIXME: Support the byUser argument.
fire.async(notificationId, true);
};
notificationsMap.get(extension).on("closed", listener);
return () => {
notificationsMap.get(extension).off("closed", listener);
};
}).api(),
onClicked: new SingletonEventManager(context, "notifications.onClicked", fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.get(extension).on("clicked", listener);
return () => {
notificationsMap.get(extension).off("clicked", listener);
};
}).api(),
// Intend to implement this later: https://bugzilla.mozilla.org/show_bug.cgi?id=1190681
onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"),
},
clear: function(notificationId) {
let notifications = notificationsMap.get(extension);
if (notifications.has(notificationId)) {
notifications.get(notificationId).clear();
return Promise.resolve(true);
}
return Promise.resolve(false);
},
getAll: function() {
let result = {};
notificationsMap.get(extension).forEach((value, key) => {
result[key] = value.options;
});
return Promise.resolve(result);
},
onClosed: new SingletonEventManager(context, "notifications.onClosed", fire => {
let listener = (event, notificationId) => {
// FIXME: Support the byUser argument.
fire.async(notificationId, true);
};
notificationsMap.get(extension).on("closed", listener);
return () => {
notificationsMap.get(extension).off("closed", listener);
};
}).api(),
onClicked: new SingletonEventManager(context, "notifications.onClicked", fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.get(extension).on("clicked", listener);
return () => {
notificationsMap.get(extension).off("clicked", listener);
};
}).api(),
// Intend to implement this later: https://bugzilla.mozilla.org/show_bug.cgi?id=1190681
onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"),
},
};
});
};
}
};

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

@ -1,10 +1,5 @@
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
"resource://gre/modules/ExtensionPermissions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
@ -12,84 +7,86 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
const {
var {
ExtensionError,
} = ExtensionUtils;
XPCOMUtils.defineLazyPreferenceGetter(this, "promptsEnabled",
"extensions.webextOptionalPermissionPrompts");
extensions.registerSchemaAPI("permission", "addon_parent", context => {
return {
permissions: {
async request_parent(perms) {
let {permissions, origins} = perms;
this.permissions = class extends ExtensionAPI {
getAPI(context) {
return {
permissions: {
async request_parent(perms) {
let {permissions, origins} = perms;
let manifestPermissions = context.extension.manifest.optional_permissions;
for (let perm of permissions) {
if (!manifestPermissions.includes(perm)) {
throw new ExtensionError(`Cannot request permission ${perm} since it was not declared in optional_permissions`);
let manifestPermissions = context.extension.manifest.optional_permissions;
for (let perm of permissions) {
if (!manifestPermissions.includes(perm)) {
throw new ExtensionError(`Cannot request permission ${perm} since it was not declared in optional_permissions`);
}
}
}
let optionalOrigins = context.extension.optionalOrigins;
for (let origin of origins) {
if (!optionalOrigins.subsumes(origin)) {
throw new ExtensionError(`Cannot request origin permission for ${origin} since it was not declared in optional_permissions`);
let optionalOrigins = context.extension.optionalOrigins;
for (let origin of origins) {
if (!optionalOrigins.subsumes(origin)) {
throw new ExtensionError(`Cannot request origin permission for ${origin} since it was not declared in optional_permissions`);
}
}
}
if (promptsEnabled) {
let allow = await new Promise(resolve => {
let subject = {
wrappedJSObject: {
browser: context.xulBrowser,
name: context.extension.name,
icon: context.extension.iconURL,
permissions: {permissions, origins},
resolve,
},
};
Services.obs.notifyObservers(subject, "webextension-optional-permission-prompt", null);
});
if (!allow) {
return false;
if (promptsEnabled) {
let allow = await new Promise(resolve => {
let subject = {
wrappedJSObject: {
browser: context.xulBrowser,
name: context.extension.name,
icon: context.extension.iconURL,
permissions: {permissions, origins},
resolve,
},
};
Services.obs.notifyObservers(subject, "webextension-optional-permission-prompt", null);
});
if (!allow) {
return false;
}
}
}
await ExtensionPermissions.add(context.extension, perms);
return true;
await ExtensionPermissions.add(context.extension, perms);
return true;
},
async getAll() {
let perms = context.extension.userPermissions;
delete perms.apis;
return perms;
},
async contains(permissions) {
for (let perm of permissions.permissions) {
if (!context.extension.hasPermission(perm)) {
return false;
}
}
for (let origin of permissions.origins) {
if (!context.extension.whiteListedHosts.subsumes(origin)) {
return false;
}
}
return true;
},
async remove(permissions) {
await ExtensionPermissions.remove(context.extension, permissions);
return true;
},
},
async getAll() {
let perms = context.extension.userPermissions;
delete perms.apis;
return perms;
},
async contains(permissions) {
for (let perm of permissions.permissions) {
if (!context.extension.hasPermission(perm)) {
return false;
}
}
for (let origin of permissions.origins) {
if (!context.extension.whiteListedHosts.subsumes(origin)) {
return false;
}
}
return true;
},
async remove(permissions) {
await ExtensionPermissions.remove(context.extension, permissions);
return true;
},
},
};
});
};
}
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("uninstall", extension => {

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

@ -2,14 +2,11 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
var {
ExtensionError,
} = ExtensionUtils;
@ -108,45 +105,47 @@ ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", {
},
});
extensions.registerSchemaAPI("privacy.network", "addon_parent", context => {
let {extension} = context;
return {
privacy: {
network: {
networkPredictionEnabled: getAPI(extension,
"network.networkPredictionEnabled",
() => {
return Preferences.get("network.predictor.enabled") &&
Preferences.get("network.prefetch-next") &&
Preferences.get("network.http.speculative-parallel-limit") > 0 &&
!Preferences.get("network.dns.disablePrefetch");
}),
webRTCIPHandlingPolicy: getAPI(extension,
"network.webRTCIPHandlingPolicy",
() => {
if (Preferences.get("media.peerconnection.ice.proxy_only")) {
return "disable_non_proxied_udp";
}
let default_address_only =
Preferences.get("media.peerconnection.ice.default_address_only");
if (default_address_only) {
if (Preferences.get("media.peerconnection.ice.no_host")) {
return "default_public_interface_only";
this.privacy = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
privacy: {
network: {
networkPredictionEnabled: getAPI(extension,
"network.networkPredictionEnabled",
() => {
return Preferences.get("network.predictor.enabled") &&
Preferences.get("network.prefetch-next") &&
Preferences.get("network.http.speculative-parallel-limit") > 0 &&
!Preferences.get("network.dns.disablePrefetch");
}),
webRTCIPHandlingPolicy: getAPI(extension,
"network.webRTCIPHandlingPolicy",
() => {
if (Preferences.get("media.peerconnection.ice.proxy_only")) {
return "disable_non_proxied_udp";
}
return "default_public_and_private_interfaces";
}
return "default";
}),
let default_address_only =
Preferences.get("media.peerconnection.ice.default_address_only");
if (default_address_only) {
if (Preferences.get("media.peerconnection.ice.no_host")) {
return "default_public_interface_only";
}
return "default_public_and_private_interfaces";
}
return "default";
}),
},
websites: {
hyperlinkAuditingEnabled: getAPI(extension,
"websites.hyperlinkAuditingEnabled",
() => {
return Preferences.get("browser.send_pings");
}),
},
},
websites: {
hyperlinkAuditingEnabled: getAPI(extension,
"websites.hyperlinkAuditingEnabled",
() => {
return Preferences.get("browser.send_pings");
}),
},
},
};
});
};
}
};

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

@ -25,45 +25,51 @@ function hasHandlerApp(handlerConfig) {
return false;
}
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_protocol_handlers", (type, directive, extension, manifest) => {
for (let handlerConfig of manifest.protocol_handlers) {
if (hasHandlerApp(handlerConfig)) {
continue;
this.protocolHandlers = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
for (let handlerConfig of manifest.protocol_handlers) {
if (hasHandlerApp(handlerConfig)) {
continue;
}
let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]
.createInstance(Ci.nsIWebHandlerApp);
handler.name = handlerConfig.name;
handler.uriTemplate = handlerConfig.uriTemplate;
let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
protoInfo.possibleApplicationHandlers.appendElement(handler, false);
handlerService.store(protoInfo);
}
let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]
.createInstance(Ci.nsIWebHandlerApp);
handler.name = handlerConfig.name;
handler.uriTemplate = handlerConfig.uriTemplate;
let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
protoInfo.possibleApplicationHandlers.appendElement(handler, false);
handlerService.store(protoInfo);
handlers.set(extension, manifest.protocol_handlers);
}
handlers.set(extension, manifest.protocol_handlers);
});
extensions.on("shutdown", (type, extension) => {
if (!handlers.has(extension) || extension.shutdownReason === "APP_SHUTDOWN") {
return;
}
for (let handlerConfig of handlers.get(extension)) {
let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
let appHandlers = protoInfo.possibleApplicationHandlers;
for (let i = 0; i < appHandlers.length; i++) {
let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
if (handler instanceof Ci.nsIWebHandlerApp &&
handler.uriTemplate === handlerConfig.uriTemplate) {
appHandlers.removeElementAt(i);
if (protoInfo.preferredApplicationHandler === handler) {
protoInfo.preferredApplicationHandler = null;
protoInfo.alwaysAskBeforeHandling = true;
onShutdown() {
let {extension} = this;
if (!handlers.has(extension) || extension.shutdownReason === "APP_SHUTDOWN") {
return;
}
for (let handlerConfig of handlers.get(extension)) {
let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
let appHandlers = protoInfo.possibleApplicationHandlers;
for (let i = 0; i < appHandlers.length; i++) {
let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
if (handler instanceof Ci.nsIWebHandlerApp &&
handler.uriTemplate === handlerConfig.uriTemplate) {
appHandlers.removeElementAt(i);
if (protoInfo.preferredApplicationHandler === handler) {
protoInfo.preferredApplicationHandler = null;
protoInfo.alwaysAskBeforeHandling = true;
}
handlerService.store(protoInfo);
break;
}
handlerService.store(protoInfo);
break;
}
}
handlers.delete(extension);
}
handlers.delete(extension);
});
};

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

@ -7,10 +7,6 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProxyScriptContext",
"resource://gre/modules/ProxyScriptContext.jsm");
@ -21,42 +17,44 @@ var {
// WeakMap[Extension -> ProxyScriptContext]
let proxyScriptContextMap = new WeakMap();
/* eslint-disable mozilla/balanced-listeners */
extensions.on("shutdown", (type, extension) => {
let proxyScriptContext = proxyScriptContextMap.get(extension);
if (proxyScriptContext) {
proxyScriptContext.unload();
proxyScriptContextMap.delete(extension);
this.proxy = class extends ExtensionAPI {
onShutdown() {
let {extension} = this;
let proxyScriptContext = proxyScriptContextMap.get(extension);
if (proxyScriptContext) {
proxyScriptContext.unload();
proxyScriptContextMap.delete(extension);
}
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("proxy", "addon_parent", context => {
let {extension} = context;
return {
proxy: {
registerProxyScript: (url) => {
// Unload the current proxy script if one is loaded.
if (proxyScriptContextMap.has(extension)) {
proxyScriptContextMap.get(extension).unload();
proxyScriptContextMap.delete(extension);
}
getAPI(context) {
let {extension} = context;
return {
proxy: {
registerProxyScript: (url) => {
// Unload the current proxy script if one is loaded.
if (proxyScriptContextMap.has(extension)) {
proxyScriptContextMap.get(extension).unload();
proxyScriptContextMap.delete(extension);
}
let proxyScriptContext = new ProxyScriptContext(extension, url);
if (proxyScriptContext.load()) {
proxyScriptContextMap.set(extension, proxyScriptContext);
}
let proxyScriptContext = new ProxyScriptContext(extension, url);
if (proxyScriptContext.load()) {
proxyScriptContextMap.set(extension, proxyScriptContext);
}
},
onProxyError: new SingletonEventManager(context, "proxy.onProxyError", fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
}).api(),
},
onProxyError: new SingletonEventManager(context, "proxy.onProxyError", fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
}).api(),
},
};
});
};
}
};

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

@ -1,134 +1,132 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
"resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
extensions.registerSchemaAPI("runtime", "addon_parent", context => {
let {extension} = context;
return {
runtime: {
onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
if (context.incognito) {
// This event should not fire if we are operating in a private profile.
return () => {};
}
let listener = () => {
if (extension.startupReason === "APP_STARTUP") {
fire.sync();
this.runtime = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
runtime: {
onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
if (context.incognito) {
// This event should not fire if we are operating in a private profile.
return () => {};
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
let listener = () => {
switch (extension.startupReason) {
case "APP_STARTUP":
if (Extension.browserUpdated) {
fire.sync({reason: "browser_update"});
}
break;
case "ADDON_INSTALL":
fire.sync({reason: "install"});
break;
case "ADDON_UPGRADE":
fire.sync({reason: "update", previousVersion: extension.addonData.oldVersion});
break;
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => {
let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => {
extension.upgrade = upgrade;
let details = {
version: upgrade.version,
let listener = () => {
if (extension.startupReason === "APP_STARTUP") {
fire.sync();
}
};
fire.sync(details);
});
return () => {
AddonManager.removeUpgradeListener(instanceID);
};
}).api(),
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
reload: () => {
if (extension.upgrade) {
// If there is a pending update, install it now.
extension.upgrade.install();
} else {
// Otherwise, reload the current extension.
AddonManager.getAddonByID(extension.id, addon => {
addon.reload();
onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
let listener = () => {
switch (extension.startupReason) {
case "APP_STARTUP":
if (Extension.browserUpdated) {
fire.sync({reason: "browser_update"});
}
break;
case "ADDON_INSTALL":
fire.sync({reason: "install"});
break;
case "ADDON_UPGRADE":
fire.sync({reason: "update", previousVersion: extension.addonData.oldVersion});
break;
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => {
let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => {
extension.upgrade = upgrade;
let details = {
version: upgrade.version,
};
fire.sync(details);
});
}
},
return () => {
AddonManager.removeUpgradeListener(instanceID);
};
}).api(),
get lastError() {
// TODO(robwu): Figure out how to make sure that errors in the parent
// process are propagated to the child process.
// lastError should not be accessed from the parent.
return context.lastError;
},
reload: () => {
if (extension.upgrade) {
// If there is a pending update, install it now.
extension.upgrade.install();
} else {
// Otherwise, reload the current extension.
AddonManager.getAddonByID(extension.id, addon => {
addon.reload();
});
}
},
getBrowserInfo: function() {
const {name, vendor, version, appBuildID} = Services.appinfo;
const info = {name, vendor, version, buildID: appBuildID};
return Promise.resolve(info);
},
get lastError() {
// TODO(robwu): Figure out how to make sure that errors in the parent
// process are propagated to the child process.
// lastError should not be accessed from the parent.
return context.lastError;
},
getPlatformInfo: function() {
return Promise.resolve(ExtensionUtils.PlatformInfo);
},
getBrowserInfo: function() {
const {name, vendor, version, appBuildID} = Services.appinfo;
const info = {name, vendor, version, buildID: appBuildID};
return Promise.resolve(info);
},
openOptionsPage: function() {
if (!extension.manifest.options_ui) {
return Promise.reject({message: "No `options_ui` declared"});
}
getPlatformInfo: function() {
return Promise.resolve(ExtensionUtils.PlatformInfo);
},
return openOptionsPage(extension).then(() => {});
},
openOptionsPage: function() {
if (!extension.manifest.options_ui) {
return Promise.reject({message: "No `options_ui` declared"});
}
setUninstallURL: function(url) {
if (url.length == 0) {
return openOptionsPage(extension).then(() => {});
},
setUninstallURL: function(url) {
if (url.length == 0) {
return Promise.resolve();
}
let uri;
try {
uri = NetUtil.newURI(url);
} catch (e) {
return Promise.reject({message: `Invalid URL: ${JSON.stringify(url)}`});
}
if (uri.scheme != "http" && uri.scheme != "https") {
return Promise.reject({message: "url must have the scheme http or https"});
}
extension.uninstallURL = url;
return Promise.resolve();
}
let uri;
try {
uri = NetUtil.newURI(url);
} catch (e) {
return Promise.reject({message: `Invalid URL: ${JSON.stringify(url)}`});
}
if (uri.scheme != "http" && uri.scheme != "https") {
return Promise.reject({message: "url must have the scheme http or https"});
}
extension.uninstallURL = url;
return Promise.resolve();
},
},
},
};
});
};
}
};

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

@ -1,7 +1,5 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
"resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "extensionStorageSync",
@ -9,7 +7,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "extensionStorageSync",
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
"resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
ExtensionError,
SingletonEventManager,
@ -25,62 +22,61 @@ function enforceNoTemporaryAddon(extensionId) {
}
}
function storageApiFactory(context) {
let {extension} = context;
return {
storage: {
local: {
get: function(spec) {
return ExtensionStorage.get(extension.id, spec);
this.storage = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
return {
storage: {
local: {
get: function(spec) {
return ExtensionStorage.get(extension.id, spec);
},
set: function(items) {
return ExtensionStorage.set(extension.id, items, context);
},
remove: function(keys) {
return ExtensionStorage.remove(extension.id, keys);
},
clear: function() {
return ExtensionStorage.clear(extension.id);
},
},
set: function(items) {
return ExtensionStorage.set(extension.id, items, context);
},
remove: function(keys) {
return ExtensionStorage.remove(extension.id, keys);
},
clear: function() {
return ExtensionStorage.clear(extension.id);
sync: {
get: function(spec) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.get(extension, spec, context);
},
set: function(items) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.set(extension, items, context);
},
remove: function(keys) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.remove(extension, keys, context);
},
clear: function() {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.clear(extension, context);
},
},
onChanged: new SingletonEventManager(context, "storage.onChanged", fire => {
let listenerLocal = changes => {
fire.async(changes, "local");
};
let listenerSync = changes => {
fire.async(changes, "sync");
};
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
return () => {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
};
}).api(),
},
sync: {
get: function(spec) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.get(extension, spec, context);
},
set: function(items) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.set(extension, items, context);
},
remove: function(keys) {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.remove(extension, keys, context);
},
clear: function() {
enforceNoTemporaryAddon(extension.id);
return extensionStorageSync.clear(extension, context);
},
},
onChanged: new SingletonEventManager(context, "storage.onChanged", fire => {
let listenerLocal = changes => {
fire.async(changes, "local");
};
let listenerSync = changes => {
fire.async(changes, "sync");
};
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
return () => {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
};
}).api(),
},
};
}
extensions.registerSchemaAPI("storage", "addon_parent", storageApiFactory);
extensions.registerSchemaAPI("storage", "content_parent", storageApiFactory);
extensions.registerSchemaAPI("storage", "devtools_parent", storageApiFactory);
};
}
};

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

@ -239,52 +239,57 @@ class Theme {
}
}
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_theme", (type, directive, extension, manifest) => {
if (!gThemesEnabled) {
// Return early if themes are disabled.
return;
this.theme = class extends ExtensionAPI {
onManifestEntry(entryName) {
if (!gThemesEnabled) {
// Return early if themes are disabled.
return;
}
let {extension} = this;
let {manifest} = extension;
let theme = new Theme(extension.baseURI, extension.logger);
theme.load(manifest.theme);
themeMap.set(extension, theme);
}
let theme = new Theme(extension.baseURI, extension.logger);
theme.load(manifest.theme);
themeMap.set(extension, theme);
});
onShutdown() {
let {extension} = this;
extensions.on("shutdown", (type, extension) => {
let theme = themeMap.get(extension);
let theme = themeMap.get(extension);
if (!theme) {
// We won't have a theme if themes are disabled.
return;
if (!theme) {
// We won't have a theme if themes are disabled.
return;
}
theme.unload();
}
theme.unload();
});
/* eslint-enable mozilla/balanced-listeners */
getAPI(context) {
let {extension} = context;
return {
theme: {
update(details) {
if (!gThemesEnabled) {
// Return early if themes are disabled.
return;
}
extensions.registerSchemaAPI("theme", "addon_parent", context => {
let {extension} = context;
return {
theme: {
update(details) {
if (!gThemesEnabled) {
// Return early if themes are disabled.
return;
}
let theme = themeMap.get(extension);
let theme = themeMap.get(extension);
if (!theme) {
// WebExtensions using the Theme API will not have a theme defined
// in the manifest. Therefore, we need to initialize the theme the
// first time browser.theme.update is called.
theme = new Theme(extension.baseURI, extension.logger);
themeMap.set(extension, theme);
}
if (!theme) {
// WebExtensions using the Theme API will not have a theme defined
// in the manifest. Therefore, we need to initialize the theme the
// first time browser.theme.update is called.
theme = new Theme(extension.baseURI, extension.logger);
themeMap.set(extension, theme);
}
theme.load(details);
theme.load(details);
},
},
},
};
});
};
}
};

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

@ -0,0 +1,237 @@
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */
global.DEFAULT_STORE = "firefox-default";
global.PRIVATE_STORE = "firefox-private";
global.CONTAINER_STORE = "firefox-container-";
global.getCookieStoreIdForTab = function(data, tab) {
if (data.incognito) {
return PRIVATE_STORE;
}
if (tab.userContextId) {
return getCookieStoreIdForContainer(tab.userContextId);
}
return DEFAULT_STORE;
};
global.isPrivateCookieStoreId = function(storeId) {
return storeId == PRIVATE_STORE;
};
global.isDefaultCookieStoreId = function(storeId) {
return storeId == DEFAULT_STORE;
};
global.isContainerCookieStoreId = function(storeId) {
return storeId !== null && storeId.startsWith(CONTAINER_STORE);
};
global.getCookieStoreIdForContainer = function(containerId) {
return CONTAINER_STORE + containerId;
};
global.getContainerForCookieStoreId = function(storeId) {
if (!isContainerCookieStoreId(storeId)) {
return null;
}
let containerId = storeId.substring(CONTAINER_STORE.length);
if (ContextualIdentityService.getPublicIdentityFromId(containerId)) {
return parseInt(containerId, 10);
}
return null;
};
global.isValidCookieStoreId = function(storeId) {
return isDefaultCookieStoreId(storeId) ||
isPrivateCookieStoreId(storeId) ||
isContainerCookieStoreId(storeId);
};
extensions.registerModules({
manifest: {
schema: "chrome://extensions/content/schemas/extension_types.json",
scopes: [],
},
alarms: {
url: "chrome://extensions/content/ext-alarms.js",
schema: "chrome://extensions/content/schemas/alarms.json",
scopes: ["addon_parent"],
paths: [
["alarms"],
],
},
backgroundPage: {
url: "chrome://extensions/content/ext-backgroundPage.js",
scopes: ["addon_parent"],
manifest: ["background"],
},
contextualIdentities: {
url: "chrome://extensions/content/ext-contextualIdentities.js",
schema: "chrome://extensions/content/schemas/contextual_identities.json",
scopes: ["addon_parent"],
paths: [
["contextualIdentities"],
],
},
cookies: {
url: "chrome://extensions/content/ext-cookies.js",
schema: "chrome://extensions/content/schemas/cookies.json",
scopes: ["addon_parent"],
paths: [
["cookies"],
],
},
downloads: {
url: "chrome://extensions/content/ext-downloads.js",
schema: "chrome://extensions/content/schemas/downloads.json",
scopes: ["addon_parent"],
paths: [
["downloads"],
],
},
extension: {
url: "chrome://extensions/content/ext-extension.js",
schema: "chrome://extensions/content/schemas/extension.json",
scopes: ["addon_parent"],
paths: [
["extension"],
],
},
geolocation: {
url: "chrome://extensions/content/ext-geolocation.js",
events: ["startup"],
},
i18n: {
url: "chrome://extensions/content/ext-i18n.js",
schema: "chrome://extensions/content/schemas/i18n.json",
scopes: ["addon_parent", "content_child", "devtools_child"],
paths: [
["i18n"],
],
},
idle: {
url: "chrome://extensions/content/ext-idle.js",
schema: "chrome://extensions/content/schemas/idle.json",
scopes: ["addon_parent"],
paths: [
["idle"],
],
},
management: {
url: "chrome://extensions/content/ext-management.js",
schema: "chrome://extensions/content/schemas/management.json",
scopes: ["addon_parent"],
paths: [
["management"],
],
},
notifications: {
url: "chrome://extensions/content/ext-notifications.js",
schema: "chrome://extensions/content/schemas/notifications.json",
scopes: ["addon_parent"],
paths: [
["notifications"],
],
},
permissions: {
url: "chrome://extensions/content/ext-permissions.js",
schema: "chrome://extensions/content/schemas/permissions.json",
scopes: ["addon_parent"],
paths: [
["permissions"],
],
},
privacy: {
url: "chrome://extensions/content/ext-privacy.js",
schema: "chrome://extensions/content/schemas/privacy.json",
scopes: ["addon_parent"],
paths: [
["privacy"],
],
},
protocolHandlers: {
url: "chrome://extensions/content/ext-protocolHandlers.js",
schema: "chrome://extensions/content/schemas/extension_protocol_handlers.json",
scopes: ["addon_parent"],
manifest: ["protocol_handlers"],
},
proxy: {
url: "chrome://extensions/content/ext-proxy.js",
schema: "chrome://extensions/content/schemas/proxy.json",
scopes: ["addon_parent"],
paths: [
["proxy"],
],
},
runtime: {
url: "chrome://extensions/content/ext-runtime.js",
schema: "chrome://extensions/content/schemas/runtime.json",
scopes: ["addon_parent", "content_parent", "devtools_parent"],
paths: [
["runtime"],
],
},
storage: {
url: "chrome://extensions/content/ext-storage.js",
schema: "chrome://extensions/content/schemas/storage.json",
scopes: ["addon_parent", "content_parent", "devtools_parent"],
paths: [
["storage"],
],
},
test: {
schema: "chrome://extensions/content/schemas/test.json",
scopes: [],
},
theme: {
url: "chrome://extensions/content/ext-theme.js",
schema: "chrome://extensions/content/schemas/theme.json",
scopes: ["addon_parent"],
manifest: ["theme"],
paths: [
["theme"],
],
},
topSites: {
url: "chrome://extensions/content/ext-topSites.js",
schema: "chrome://extensions/content/schemas/top_sites.json",
scopes: ["addon_parent"],
paths: [
["topSites"],
],
},
webNavigation: {
url: "chrome://extensions/content/ext-webNavigation.js",
schema: "chrome://extensions/content/schemas/web_navigation.json",
scopes: ["addon_parent"],
paths: [
["webNavigation"],
],
},
webRequest: {
url: "chrome://extensions/content/ext-webRequest.js",
schema: "chrome://extensions/content/schemas/web_request.json",
scopes: ["addon_parent"],
paths: [
["webRequest"],
],
},
});
if (AppConstants.MOZ_BUILD_APP === "browser") {
extensions.registerModules({
identity: {
schema: "chrome://extensions/content/schemas/identity.json",
scopes: ["addon_parent"],
},
});
}

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

@ -5,20 +5,22 @@
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
extensions.registerSchemaAPI("topSites", "addon_parent", context => {
return {
topSites: {
get: function() {
let urls = NewTabUtils.links.getLinks()
.filter(link => !!link)
.map(link => {
return {
url: link.url,
title: link.title,
};
});
return Promise.resolve(urls);
this.topSites = class extends ExtensionAPI {
getAPI(context) {
return {
topSites: {
get: function() {
let urls = NewTabUtils.links.getLinks()
.filter(link => !!link)
.map(link => {
return {
url: link.url,
title: link.title,
};
});
return Promise.resolve(urls);
},
},
},
};
});
};
}
};

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

@ -1,9 +1,5 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
@ -11,7 +7,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
"resource://gre/modules/WebNavigation.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
@ -163,46 +158,48 @@ function convertGetFrameResult(tabId, data) {
};
}
extensions.registerSchemaAPI("webNavigation", "addon_parent", context => {
let {tabManager} = context.extension;
this.webNavigation = class extends ExtensionAPI {
getAPI(context) {
let {tabManager} = context.extension;
return {
webNavigation: {
onTabReplaced: new SingletonEventManager(context, "webNavigation.onTabReplaced", fire => {
return () => {};
}).api(),
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(),
onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
getAllFrames(details) {
let tab = tabManager.get(details.tabId);
return {
webNavigation: {
onTabReplaced: new SingletonEventManager(context, "webNavigation.onTabReplaced", fire => {
return () => {};
}).api(),
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(),
onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
getAllFrames(details) {
let tab = tabManager.get(details.tabId);
let {innerWindowID, messageManager} = tab.browser;
let recipient = {innerWindowID};
let {innerWindowID, messageManager} = tab.browser;
let recipient = {innerWindowID};
return context.sendMessage(messageManager, "WebNavigation:GetAllFrames", {}, {recipient})
.then((results) => results.map(convertGetFrameResult.bind(null, details.tabId)));
return context.sendMessage(messageManager, "WebNavigation:GetAllFrames", {}, {recipient})
.then((results) => results.map(convertGetFrameResult.bind(null, details.tabId)));
},
getFrame(details) {
let tab = tabManager.get(details.tabId);
let recipient = {
innerWindowID: tab.browser.innerWindowID,
};
let mm = tab.browser.messageManager;
return context.sendMessage(mm, "WebNavigation:GetFrame", {options: details}, {recipient})
.then((result) => {
return result ?
convertGetFrameResult(details.tabId, result) :
Promise.reject({message: `No frame found with frameId: ${details.frameId}`});
});
},
},
getFrame(details) {
let tab = tabManager.get(details.tabId);
let recipient = {
innerWindowID: tab.browser.innerWindowID,
};
let mm = tab.browser.messageManager;
return context.sendMessage(mm, "WebNavigation:GetFrame", {options: details}, {recipient})
.then((result) => {
return result ?
convertGetFrameResult(details.tabId, result) :
Promise.reject({message: `No frame found with frameId: ${details.frameId}`});
});
},
},
};
});
};
}
};

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

@ -1,16 +1,13 @@
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
"resource://gre/modules/WebRequest.jsm");
Cu.import("resource://gre/modules/ExtensionManagement.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
@ -119,21 +116,23 @@ function WebRequestEventManager(context, eventName) {
WebRequestEventManager.prototype = Object.create(SingletonEventManager.prototype);
extensions.registerSchemaAPI("webRequest", "addon_parent", context => {
return {
webRequest: {
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
handlerBehaviorChanged: function() {
// TODO: Flush all caches.
this.webRequest = class extends ExtensionAPI {
getAPI(context) {
return {
webRequest: {
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
handlerBehaviorChanged: function() {
// TODO: Flush all caches.
},
},
},
};
});
};
}
};

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

@ -1,78 +1,9 @@
# scripts
category webextension-scripts alarms chrome://extensions/content/ext-alarms.js
category webextension-scripts backgroundPage chrome://extensions/content/ext-backgroundPage.js
category webextension-scripts contextualIdentities chrome://extensions/content/ext-contextualIdentities.js
category webextension-scripts cookies chrome://extensions/content/ext-cookies.js
category webextension-scripts downloads chrome://extensions/content/ext-downloads.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts geolocation chrome://extensions/content/ext-geolocation.js
category webextension-scripts handlers chrome://extensions/content/ext-protocolHandlers.js
category webextension-scripts i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts idle chrome://extensions/content/ext-idle.js
category webextension-scripts management chrome://extensions/content/ext-management.js
category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
category webextension-scripts permissions chrome://extensions/content/ext-permissions.js
category webextension-scripts privacy chrome://extensions/content/ext-privacy.js
category webextension-scripts proxy chrome://extensions/content/ext-proxy.js
category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts theme chrome://extensions/content/ext-theme.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
category webextension-scripts toolkit chrome://extensions/content/ext-toolkit.js
category webextension-scripts-content toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-devtools toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-addon toolkit chrome://extensions/content/ext-c-toolkit.js
# scripts specific for content process.
category webextension-scripts-content extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-content i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-content permissions chrome://extensions/content/ext-c-permissions.js
category webextension-scripts-content runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-content storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-content test chrome://extensions/content/ext-c-test.js
# scripts specific for devtools extension contexts.
category webextension-scripts-devtools extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-devtools i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-devtools runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-devtools storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-devtools test chrome://extensions/content/ext-c-test.js
# scripts that must run in the same process as addon code.
category webextension-scripts-addon backgroundPage chrome://extensions/content/ext-c-backgroundPage.js
category webextension-scripts-addon extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-addon i18n chrome://extensions/content/ext-i18n.js
#ifndef ANDROID
category webextension-scripts-addon identity chrome://extensions/content/ext-c-identity.js
#endif
category webextension-scripts-addon permissions chrome://extensions/content/ext-c-permissions.js
category webextension-scripts-addon runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-addon storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-addon test chrome://extensions/content/ext-c-test.js
# schemas
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
category webextension-schemas contextualIdentities chrome://extensions/content/schemas/contextual_identities.json
category webextension-schemas cookies chrome://extensions/content/schemas/cookies.json
category webextension-schemas downloads chrome://extensions/content/schemas/downloads.json
category webextension-schemas events chrome://extensions/content/schemas/events.json
category webextension-schemas extension chrome://extensions/content/schemas/extension.json
category webextension-schemas extension_types chrome://extensions/content/schemas/extension_types.json
category webextension-schemas handlers chrome://extensions/content/schemas/extension_protocol_handlers.json
category webextension-schemas i18n chrome://extensions/content/schemas/i18n.json
#ifndef ANDROID
category webextension-schemas identity chrome://extensions/content/schemas/identity.json
#endif
category webextension-schemas idle chrome://extensions/content/schemas/idle.json
category webextension-schemas management chrome://extensions/content/schemas/management.json
category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
category webextension-schemas notifications chrome://extensions/content/schemas/notifications.json
category webextension-schemas permissions chrome://extensions/content/schemas/permissions.json
category webextension-schemas privacy chrome://extensions/content/schemas/privacy.json
category webextension-schemas proxy chrome://extensions/content/schemas/proxy.json
category webextension-schemas runtime chrome://extensions/content/schemas/runtime.json
category webextension-schemas storage chrome://extensions/content/schemas/storage.json
category webextension-schemas test chrome://extensions/content/schemas/test.json
category webextension-schemas theme chrome://extensions/content/schemas/theme.json
category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
category webextension-schemas web_navigation chrome://extensions/content/schemas/web_navigation.json
category webextension-schemas web_request chrome://extensions/content/schemas/web_request.json

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

@ -23,6 +23,7 @@ toolkit.jar:
content/extensions/ext-runtime.js
content/extensions/ext-storage.js
content/extensions/ext-theme.js
content/extensions/ext-toolkit.js
content/extensions/ext-topSites.js
content/extensions/ext-webRequest.js
content/extensions/ext-webNavigation.js
@ -37,3 +38,4 @@ toolkit.jar:
content/extensions/ext-c-runtime.js
content/extensions/ext-c-storage.js
content/extensions/ext-c-test.js
content/extensions/ext-c-toolkit.js

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

@ -26,7 +26,7 @@ EXTRA_JS_MODULES += [
'Schemas.jsm',
]
EXTRA_PP_COMPONENTS += [
EXTRA_COMPONENTS += [
'extensions-toolkit.manifest',
]

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

@ -16,7 +16,11 @@ add_task(function* test_storage_api_without_permissions() {
let extension = ExtensionTestUtils.loadExtension({
background() {
// Force API initialization.
void browser.storage;
try {
browser.storage.onChanged.addListener(() => {});
} catch (e) {
// Ignore.
}
},
manifest: {
@ -41,7 +45,7 @@ add_task(function* test_storage_api_without_permissions() {
add_task(function* test_storage_api_with_permissions() {
let extension = ExtensionTestUtils.loadExtension({
background() {
void browser.storage;
browser.storage.onChanged.addListener(() => {});
},
manifest: {