Bug 1287010, 1286712 - Use schema-generated runtime API, split ext-runtime.js r=billm

- Use schema-generated runtime API for content scripts instead of
  untyped API.
- Move logic that cannot be run in the main process to a new file.

Together with the previous patch that migrated the i18n API, this
concludes the fix for bug 1286712.

MozReview-Commit-ID: A3yG0x1kjwx

--HG--
extra : rebase_source : 19efe95149c423c0f9284bb70e289a282fb758c1
This commit is contained in:
Rob Wu 2016-08-18 18:15:37 -07:00
Родитель 289e3b1ea7
Коммит cf5257b11d
6 изменённых файлов: 104 добавлений и 108 удалений

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

@ -94,54 +94,6 @@ var apiManager = new class extends SchemaAPIManager {
// scripts.
var api = context => {
return {
runtime: {
connect: function(extensionId, connectInfo) {
if (!connectInfo) {
connectInfo = extensionId;
extensionId = null;
}
extensionId = extensionId || context.extension.id;
let name = connectInfo && connectInfo.name || "";
let recipient = {extensionId};
return context.messenger.connect(context.messageManager, name, recipient);
},
get id() {
return context.extension.id;
},
get lastError() {
return context.lastError;
},
getManifest: function() {
return Cu.cloneInto(context.extension.manifest, context.cloneScope);
},
getURL: function(url) {
return context.extension.baseURI.resolve(url);
},
onConnect: context.messenger.onConnect("runtime.onConnect"),
onMessage: context.messenger.onMessage("runtime.onMessage"),
sendMessage: function(...args) {
let options; // eslint-disable-line no-unused-vars
let extensionId, message, responseCallback;
if (args.length == 1) {
message = args[0];
} else if (args.length == 2) {
[message, responseCallback] = args;
} else {
[extensionId, message, options, responseCallback] = args;
}
extensionId = extensionId || context.extension.id;
let recipient = {extensionId};
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
},
},
extension: {
getURL: function(url) {

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

@ -0,0 +1,82 @@
"use strict";
function runtimeApiFactory(context) {
let {extension} = context;
// TODO(robwu): Investigate which message-manager to use once we start with
// reworking Messenger and ExtensionContext to be usable in a child process
// instead of the parent process.
// For now use exactly the original behavior out of caution.
let mm = context.envType == "content_child" ? context.messageManager : Services.cpmm;
return {
runtime: {
onConnect: context.messenger.onConnect("runtime.onConnect"),
onMessage: context.messenger.onMessage("runtime.onMessage"),
connect: function(extensionId, connectInfo) {
let name = connectInfo !== null && connectInfo.name || "";
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.connect(mm, 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;
} 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 {
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"});
}
if (options != null && typeof options != "object") {
return Promise.reject({message: "runtime.sendMessage's options argument is invalid"});
}
// TODO(robwu): Validate option keys and values when we support it.
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.sendMessage(mm, message, recipient, responseCallback);
},
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);

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

@ -33,10 +33,6 @@ extensions.registerSchemaAPI("runtime", "addon_parent", context => {
onInstalled: ignoreEvent(context, "runtime.onInstalled"),
onMessage: context.messenger.onMessage("runtime.onMessage"),
onConnect: context.messenger.onConnect("runtime.onConnect"),
onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => {
let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => {
@ -63,52 +59,6 @@ extensions.registerSchemaAPI("runtime", "addon_parent", context => {
}
},
connect: function(extensionId, connectInfo) {
let name = connectInfo !== null && connectInfo.name || "";
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.connect(Services.cpmm, 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;
} 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 {
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"});
}
if (options != null && typeof options != "object") {
return Promise.reject({message: "runtime.sendMessage's options argument is invalid"});
}
// TODO(robwu): Validate option keys and values when we support it.
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.sendMessage(Services.cpmm, message, recipient, responseCallback);
},
connectNative(application) {
let app = new NativeApp(extension, context, application);
return app.portAPI();
@ -120,19 +70,12 @@ extensions.registerSchemaAPI("runtime", "addon_parent", context => {
},
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;
},
getManifest() {
return Cu.cloneInto(extension.manifest, context.cloneScope);
},
id: extension.id,
getURL: function(url) {
return extension.baseURI.resolve(url);
},
getPlatformInfo: function() {
return Promise.resolve(ExtensionUtils.PlatformInfo);
},

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

@ -16,6 +16,7 @@ category webextension-scripts test chrome://extensions/content/ext-test.js
# scripts specific for content process.
category webextension-scripts-content i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-content runtime chrome://extensions/content/ext-c-runtime.js
# schemas
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json

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

@ -18,3 +18,4 @@ toolkit.jar:
content/extensions/ext-extension.js
content/extensions/ext-storage.js
content/extensions/ext-test.js
content/extensions/ext-c-runtime.js

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

@ -19,11 +19,13 @@
},
{
"namespace": "runtime",
"restrictions": ["content"],
"description": "Use the <code>browser.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.",
"types": [
{
"id": "Port",
"type": "object",
"restrictions": ["content"],
"description": "An object which allows two way communication with other pages.",
"properties": {
"name": {"type": "string"},
@ -42,6 +44,7 @@
{
"id": "MessageSender",
"type": "object",
"restrictions": ["content"],
"description": "An object containing information about the script context that sent a message or request.",
"properties": {
"tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab (including content scripts), and <strong>only</strong> if the receiver is an extension, not an app."},
@ -54,6 +57,7 @@
{
"id": "PlatformOs",
"type": "string",
"restrictions": ["content"],
"description": "The operating system the browser is running on.",
"enum": ["mac", "win", "android", "cros", "linux", "openbsd"]
},
@ -61,11 +65,13 @@
"id": "PlatformArch",
"type": "string",
"enum": ["arm", "x86-32", "x86-64"],
"restrictions": ["content"],
"description": "The machine's processor architecture."
},
{
"id": "PlatformInfo",
"type": "object",
"restrictions": ["content"],
"description": "An object containing information about the current platform.",
"properties": {
"os": {
@ -87,17 +93,20 @@
"id": "RequestUpdateCheckStatus",
"type": "string",
"enum": ["throttled", "no_update", "update_available"],
"restrictions": ["content"],
"description": "Result of the update check."
},
{
"id": "OnInstalledReason",
"type": "string",
"enum": ["install", "update", "chrome_update", "shared_module_update"],
"restrictions": ["content"],
"description": "The reason that this event is being dispatched."
},
{
"id": "OnRestartRequiredReason",
"type": "string",
"restrictions": ["content"],
"description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.",
"enum": ["app_update", "os_update", "periodic"]
}
@ -106,6 +115,7 @@
"lastError": {
"type": "object",
"optional": true,
"restrictions": ["content"],
"description": "This will be defined during an API method callback if there was an error",
"properties": {
"message": {
@ -120,6 +130,7 @@
},
"id": {
"type": "string",
"restrictions": ["content"],
"description": "The ID of the extension/app."
}
},
@ -160,6 +171,7 @@
},
{
"name": "getManifest",
"restrictions": ["content"],
"description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full $(topic:manifest)[manifest file].",
"type": "function",
"parameters": [],
@ -173,6 +185,7 @@
{
"name": "getURL",
"type": "function",
"restrictions": ["content"],
"description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.",
"parameters": [
{
@ -255,6 +268,7 @@
{
"name": "connect",
"type": "function",
"restrictions": ["content"],
"description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and $(topic:manifest/externally_connectable)[web messaging]. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).",
"parameters": [
{"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."},
@ -294,6 +308,7 @@
"name": "sendMessage",
"type": "function",
"allowAmbiguousOptionalArguments": true,
"restrictions": ["content"],
"description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
"async": "responseCallback",
"parameters": [
@ -470,6 +485,7 @@
{
"name": "onConnect",
"type": "function",
"restrictions": ["content"],
"description": "Fired when a connection is made from either an extension process or a content script.",
"parameters": [
{"$ref": "Port", "name": "port"}
@ -487,6 +503,7 @@
{
"name": "onMessage",
"type": "function",
"restrictions": ["content"],
"description": "Fired when a message is sent from either an extension process or a content script.",
"parameters": [
{"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},