mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 05:15:45 +00:00
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:
parent
4ef4a153de
commit
bb351ebfca
@ -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);
|
||||
};
|
||||
|
89
toolkit/components/extensions/ext-c-toolkit.js
Normal file
89
toolkit/components/extensions/ext-c-toolkit.js
Normal file
@ -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);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
237
toolkit/components/extensions/ext-toolkit.js
Normal file
237
toolkit/components/extensions/ext-toolkit.js
Normal file
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user