Bug 1350522: Part 3 - Convert android APIs to lazy loading. r=aswan

MozReview-Commit-ID: GaxLICqzdXz

--HG--
extra : source : 42015d3bfe49a09a28435ca3691358848c9dc2e5
This commit is contained in:
Kris Maglione 2017-03-31 18:45:45 -07:00
Родитель bb351ebfca
Коммит 0bde08bed9
7 изменённых файлов: 426 добавлений и 389 удалений

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

@ -0,0 +1,22 @@
"use strict";
extensions.registerModules({
pageAction: {
url: "chrome://browser/content/ext-pageAction.js",
schema: "chrome://browser/content/schemas/page_action.json",
scopes: ["addon_parent"],
manifest: ["page_action"],
paths: [
["pageAction"],
],
},
tabs: {
url: "chrome://browser/content/ext-tabs.js",
schema: "chrome://browser/content/schemas/tabs.json",
scopes: ["addon_parent"],
paths: [
["tabs"],
],
},
});

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

@ -0,0 +1,11 @@
"use strict";
extensions.registerModules({
tabs: {
url: "chrome://browser/content/ext-c-tabs.js",
scopes: ["addon_child"],
paths: [
["tabs"],
],
},
});

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

@ -2,34 +2,36 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
extensions.registerSchemaAPI("tabs", "addon_child", context => {
return {
tabs: {
connect: function(tabId, connectInfo) {
let name = "";
if (connectInfo && connectInfo.name !== null) {
name = connectInfo.name;
}
let recipient = {
extensionId: context.extension.id,
tabId,
};
if (connectInfo && connectInfo.frameId !== null) {
recipient.frameId = connectInfo.frameId;
}
return context.messenger.connect(context.messageManager, name, recipient);
},
this.tabs = class extends ExtensionAPI {
getAPI(context) {
return {
tabs: {
connect: function(tabId, connectInfo) {
let name = "";
if (connectInfo && connectInfo.name !== null) {
name = connectInfo.name;
}
let recipient = {
extensionId: context.extension.id,
tabId,
};
if (connectInfo && connectInfo.frameId !== null) {
recipient.frameId = connectInfo.frameId;
}
return context.messenger.connect(context.messageManager, name, recipient);
},
sendMessage: function(tabId, message, options, responseCallback) {
let recipient = {
extensionId: context.extension.id,
tabId: tabId,
};
if (options && options.frameId !== null) {
recipient.frameId = options.frameId;
}
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
sendMessage: function(tabId, message, options, responseCallback) {
let recipient = {
extensionId: context.extension.id,
tabId: tabId,
};
if (options && options.frameId !== null) {
recipient.frameId = options.frameId;
}
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
},
},
},
};
});
};
}
};

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

@ -112,60 +112,65 @@ PageAction.prototype = {
},
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
let pageAction = new PageAction(manifest.page_action, extension);
pageActionMap.set(extension, pageAction);
});
this.pageAction = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
extensions.on("shutdown", (type, extension) => {
if (pageActionMap.has(extension)) {
pageActionMap.get(extension).shutdown();
pageActionMap.delete(extension);
let pageAction = new PageAction(manifest.page_action, extension);
pageActionMap.set(extension, pageAction);
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
const {extension} = context;
const {tabManager} = extension;
onShutdown(reason) {
let {extension} = this;
return {
pageAction: {
onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
}).api(),
if (pageActionMap.has(extension)) {
pageActionMap.get(extension).shutdown();
pageActionMap.delete(extension);
}
}
show(tabId) {
return pageActionMap.get(extension)
.show(tabId, context)
.then(() => {});
getAPI(context) {
const {extension} = context;
const {tabManager} = extension;
return {
pageAction: {
onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
}).api(),
show(tabId) {
return pageActionMap.get(extension)
.show(tabId, context)
.then(() => {});
},
hide(tabId) {
pageActionMap.get(extension).hide(tabId);
return Promise.resolve();
},
setPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let url = details.popup && context.uri.resolve(details.popup);
pageActionMap.get(extension).setPopup(tab, url);
},
getPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let popup = pageActionMap.get(extension).getPopup(tab);
return Promise.resolve(popup);
},
},
hide(tabId) {
pageActionMap.get(extension).hide(tabId);
return Promise.resolve();
},
setPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let url = details.popup && context.uri.resolve(details.popup);
pageActionMap.get(extension).setPopup(tab, url);
},
getPopup(details) {
// TODO: Use the Tabs API to get the tab from details.tabId.
let tab = null;
let popup = pageActionMap.get(extension).getPopup(tab);
return Promise.resolve(popup);
},
},
};
});
};
}
};

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

@ -15,8 +15,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
} = ExtensionUtils;
@ -128,309 +126,311 @@ let tabListener = {
},
};
extensions.registerSchemaAPI("tabs", "addon_parent", context => {
let {extension} = context;
this.tabs = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
let {tabManager} = extension;
let {tabManager} = extension;
function getTabOrActive(tabId) {
if (tabId !== null) {
return tabTracker.getTab(tabId);
}
return tabTracker.activeTab;
}
async function promiseTabWhenReady(tabId) {
let tab;
if (tabId !== null) {
tab = tabManager.get(tabId);
} else {
tab = tabManager.getWrapper(tabTracker.activeTab);
function getTabOrActive(tabId) {
if (tabId !== null) {
return tabTracker.getTab(tabId);
}
return tabTracker.activeTab;
}
await tabListener.awaitTabReady(tab.nativeTab);
async function promiseTabWhenReady(tabId) {
let tab;
if (tabId !== null) {
tab = tabManager.get(tabId);
} else {
tab = tabManager.getWrapper(tabTracker.activeTab);
}
return tab;
await tabListener.awaitTabReady(tab.nativeTab);
return tab;
}
let self = {
tabs: {
onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabId: tab.id, windowId: tab.windowId});
}).api(),
onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
}).api(),
/**
* Since multiple tabs currently can't be highlighted, onHighlighted
* essentially acts an alias for self.tabs.onActivated but returns
* the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/
onHighlighted: new GlobalEventManager(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabIds: [tab.id], windowId: tab.windowId});
}).api(),
onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
return () => {};
}).api(),
onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
return () => {};
}).api(),
onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
}).api(),
onReplaced: new SingletonEventManager(context, "tabs.onReplaced", fire => {
return () => {};
}).api(),
onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
return () => {};
}).api(),
onUpdated: new SingletonEventManager(context, "tabs.onUpdated", fire => {
const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
for (let prop in changeInfo) {
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return [nonempty, result];
}
let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
let nativeTab;
switch (event.type) {
case "DOMTitleChanged": {
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
needed.push("title");
break;
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
let {BrowserApp} = event.target.ownerGlobal;
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
needed.push("audible");
break;
}
}
if (!nativeTab) {
return;
}
let tab = tabManager.getWrapper(nativeTab);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {BrowserApp} = browser.ownerGlobal;
let nativeTab = BrowserApp.getTabForBrowser(browser);
if (nativeTab) {
let changed = {status};
if (url) {
changed.url = url;
}
fireForTab(tabManager.wrapTab(nativeTab), changed);
}
};
windowTracker.addListener("status", statusListener);
windowTracker.addListener("DOMTitleChanged", listener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("DOMTitleChanged", listener);
};
}).api(),
async create(createProperties) {
let window = createProperties.windowId !== null ?
windowTracker.getWindow(createProperties.windowId, context) :
windowTracker.topWindow;
let {BrowserApp} = window;
let url;
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
}
}
let options = {};
let active = true;
if (createProperties.active !== null) {
active = createProperties.active;
}
options.selected = active;
if (createProperties.index !== null) {
options.tabIndex = createProperties.index;
}
// Make sure things like about:blank and data: URIs never inherit,
// and instead always get a NullPrincipal.
options.disallowInheritPrincipal = true;
tabListener.initTabReady();
let nativeTab = BrowserApp.addTab(url, options);
if (createProperties.url) {
tabListener.initializingTabs.add(nativeTab);
}
return tabManager.convert(nativeTab);
},
async remove(tabs) {
if (!Array.isArray(tabs)) {
tabs = [tabs];
}
for (let tabId of tabs) {
let nativeTab = tabTracker.getTab(tabId);
nativeTab.browser.ownerGlobal.BrowserApp.closeTab(nativeTab);
}
},
async update(tabId, updateProperties) {
let nativeTab = getTabOrActive(tabId);
let {BrowserApp} = nativeTab.browser.ownerGlobal;
if (updateProperties.url !== null) {
let url = context.uri.resolve(updateProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
}
nativeTab.browser.loadURI(url);
}
if (updateProperties.active !== null) {
if (updateProperties.active) {
BrowserApp.selectTab(nativeTab);
} else {
// Not sure what to do here? Which tab should we select?
}
}
// FIXME: highlighted/selected, muted, pinned, openerTabId
return tabManager.convert(nativeTab);
},
async reload(tabId, reloadProperties) {
let nativeTab = getTabOrActive(tabId);
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (reloadProperties && reloadProperties.bypassCache) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
}
nativeTab.browser.reloadWithFlags(flags);
},
async get(tabId) {
return tabManager.get(tabId).convert();
},
async getCurrent() {
if (context.tabId) {
return tabManager.get(context.tabId).convert();
}
},
async query(queryInfo) {
if (queryInfo.url !== null) {
if (!extension.hasPermission("tabs")) {
return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
}
queryInfo = Object.assign({}, queryInfo);
queryInfo.url = new MatchPattern(queryInfo.url);
}
return Array.from(tabManager.query(queryInfo, context),
tab => tab.convert());
},
async captureVisibleTab(windowId, options) {
let window = windowId == null ?
windowTracker.topWindow :
windowTracker.getWindow(windowId, context);
let tab = tabManager.wrapTab(window.BrowserApp.selectedTab);
await tabListener.awaitTabReady(tab.nativeTab);
return tab.capture(context, options);
},
async executeScript(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.executeScript(context, details);
},
async insertCSS(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.insertCSS(context, details);
},
async removeCSS(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.removeCSS(context, details);
},
},
};
return self;
}
let self = {
tabs: {
onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabId: tab.id, windowId: tab.windowId});
}).api(),
onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
}).api(),
/**
* Since multiple tabs currently can't be highlighted, onHighlighted
* essentially acts an alias for self.tabs.onActivated but returns
* the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/
onHighlighted: new GlobalEventManager(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabIds: [tab.id], windowId: tab.windowId});
}).api(),
onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
return () => {};
}).api(),
onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
return () => {};
}).api(),
onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
}).api(),
onReplaced: new SingletonEventManager(context, "tabs.onReplaced", fire => {
return () => {};
}).api(),
onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
return () => {};
}).api(),
onUpdated: new SingletonEventManager(context, "tabs.onUpdated", fire => {
const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
for (let prop in changeInfo) {
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return [nonempty, result];
}
let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
let nativeTab;
switch (event.type) {
case "DOMTitleChanged": {
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
needed.push("title");
break;
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
let {BrowserApp} = event.target.ownerGlobal;
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
needed.push("audible");
break;
}
}
if (!nativeTab) {
return;
}
let tab = tabManager.getWrapper(nativeTab);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {BrowserApp} = browser.ownerGlobal;
let nativeTab = BrowserApp.getTabForBrowser(browser);
if (nativeTab) {
let changed = {status};
if (url) {
changed.url = url;
}
fireForTab(tabManager.wrapTab(nativeTab), changed);
}
};
windowTracker.addListener("status", statusListener);
windowTracker.addListener("DOMTitleChanged", listener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("DOMTitleChanged", listener);
};
}).api(),
async create(createProperties) {
let window = createProperties.windowId !== null ?
windowTracker.getWindow(createProperties.windowId, context) :
windowTracker.topWindow;
let {BrowserApp} = window;
let url;
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
}
}
let options = {};
let active = true;
if (createProperties.active !== null) {
active = createProperties.active;
}
options.selected = active;
if (createProperties.index !== null) {
options.tabIndex = createProperties.index;
}
// Make sure things like about:blank and data: URIs never inherit,
// and instead always get a NullPrincipal.
options.disallowInheritPrincipal = true;
tabListener.initTabReady();
let nativeTab = BrowserApp.addTab(url, options);
if (createProperties.url) {
tabListener.initializingTabs.add(nativeTab);
}
return tabManager.convert(nativeTab);
},
async remove(tabs) {
if (!Array.isArray(tabs)) {
tabs = [tabs];
}
for (let tabId of tabs) {
let nativeTab = tabTracker.getTab(tabId);
nativeTab.browser.ownerGlobal.BrowserApp.closeTab(nativeTab);
}
},
async update(tabId, updateProperties) {
let nativeTab = getTabOrActive(tabId);
let {BrowserApp} = nativeTab.browser.ownerGlobal;
if (updateProperties.url !== null) {
let url = context.uri.resolve(updateProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
}
nativeTab.browser.loadURI(url);
}
if (updateProperties.active !== null) {
if (updateProperties.active) {
BrowserApp.selectTab(nativeTab);
} else {
// Not sure what to do here? Which tab should we select?
}
}
// FIXME: highlighted/selected, muted, pinned, openerTabId
return tabManager.convert(nativeTab);
},
async reload(tabId, reloadProperties) {
let nativeTab = getTabOrActive(tabId);
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (reloadProperties && reloadProperties.bypassCache) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
}
nativeTab.browser.reloadWithFlags(flags);
},
async get(tabId) {
return tabManager.get(tabId).convert();
},
async getCurrent() {
if (context.tabId) {
return tabManager.get(context.tabId).convert();
}
},
async query(queryInfo) {
if (queryInfo.url !== null) {
if (!extension.hasPermission("tabs")) {
return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
}
queryInfo = Object.assign({}, queryInfo);
queryInfo.url = new MatchPattern(queryInfo.url);
}
return Array.from(tabManager.query(queryInfo, context),
tab => tab.convert());
},
async captureVisibleTab(windowId, options) {
let window = windowId == null ?
windowTracker.topWindow :
windowTracker.getWindow(windowId, context);
let tab = tabManager.wrapTab(window.BrowserApp.selectedTab);
await tabListener.awaitTabReady(tab.nativeTab);
return tab.capture(context, options);
},
async executeScript(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.executeScript(context, details);
},
async insertCSS(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.insertCSS(context, details);
},
async removeCSS(tabId, details) {
let tab = await promiseTabWhenReady(tabId);
return tab.removeCSS(context, details);
},
},
};
return self;
});
};

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

@ -1,9 +1,4 @@
# scripts
category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
category webextension-scripts tabs chrome://browser/content/ext-tabs.js
category webextension-scripts android chrome://browser/content/ext-android.js
category webextension-scripts utils chrome://browser/content/ext-utils.js
category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
# schemas
category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
category webextension-scripts-addon android chrome://browser/content/ext-c-android.js

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

@ -3,6 +3,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
chrome.jar:
content/ext-android.js
content/ext-c-android.js
content/ext-c-tabs.js
content/ext-pageAction.js
content/ext-tabs.js