Bug 876397 - Inter-App Communication API (part 6, post message). r=nsm,bent

This commit is contained in:
Gene Lian 2013-08-16 17:50:37 +08:00
Родитель d45f276627
Коммит 89c5d85d7b
5 изменённых файлов: 428 добавлений и 13 удалений

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

@ -13,3 +13,6 @@ category profile-after-change InterAppCommService @mozilla.org/inter-app-communi
component {d7c7a466-f91d-11e2-812a-6fab12ece58e} InterAppConnection.js
contract @mozilla.org/dom/system-messages/wrapper/connection;1 {d7c7a466-f91d-11e2-812a-6fab12ece58e}
component {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad} InterAppMessagePort.js
contract @mozilla.org/dom/inter-app-message-event;1 {33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}

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

@ -32,7 +32,11 @@ XPCOMUtils.defineLazyServiceGetter(this, "messenger",
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel"];
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
function InterAppCommService() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
@ -167,6 +171,44 @@ function InterAppCommService() {
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
}
InterAppCommService.prototype = {
@ -306,9 +348,11 @@ InterAppCommService.prototype = {
return true;
},
_dispatchMessagePorts: function(aKeyword, aAllowedSubAppManifestURLs,
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
if (aAllowedSubAppManifestURLs.length == 0) {
@ -335,7 +379,20 @@ InterAppCommService.prototype = {
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
@ -345,7 +402,7 @@ InterAppCommService.prototype = {
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
});
}, this);
if (messagePortIDs.length == 0) {
debug("No apps are subscribed to connect. Returning.");
@ -373,7 +430,8 @@ InterAppCommService.prototype = {
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
debug("No apps are subscribed for this connection. Returning.")
this._dispatchMessagePorts(keyword, [], aTarget, outerWindowID, requestID);
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
@ -417,7 +475,8 @@ InterAppCommService.prototype = {
if (appsToSelect.length == 0) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
this._dispatchMessagePorts(keyword, allowedSubAppManifestURLs,
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
@ -514,6 +573,143 @@ InterAppCommService.prototype = {
delete this._allowedConnections[keyword];
}
}
debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
debug("Cannot identify the message port. Failed to register.");
return;
}
debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
debug("Checking if the other port used to send messages but queued.");
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
debug("Cannot identify the message port. Failed to unregister.");
return;
}
debug("Unregistering message port for " + manifestURL);
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
debug("Error! aTarget cannot be null/undefined in any way.");
return
}
debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
debug("The receiver's target is not ready yet. Queuing the message.");
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
@ -536,7 +732,8 @@ InterAppCommService.prototype = {
if (selectedApps.length == 0) {
debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, [], target, outerWindowID, requestID);
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
@ -560,7 +757,7 @@ InterAppCommService.prototype = {
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, allowedSubAppManifestURLs,
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
@ -571,7 +768,8 @@ InterAppCommService.prototype = {
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (kMessages.indexOf(aMessage.name) != -1) {
if (aMessage.name !== "child-process-shutdown" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
debug("Got message from a child process carrying illegal manifest URL.");
return null;
@ -588,6 +786,18 @@ InterAppCommService.prototype = {
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},

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

@ -2,57 +2,242 @@
* 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/. */
// TODO Bug 907060 Per off-line discussion, after the MessagePort is done
// at Bug 643325, we will start to refactorize the common logic of both
// Inter-App Communication and Shared Worker. For now, we hope to design an
// MozInterAppMessagePort to meet the timeline, which still follows exactly
// the same interface and semantic as the MessagePort is. In the future,
// we can then align it back to MessagePort with backward compatibility.
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
function debug(aMsg) {
// dump("-- InterAppMessagePort: " + Date.now() + ": " + aMsg + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
const kMessages = ["InterAppMessagePort:OnMessage"];
function InterAppMessageEvent() {
this.type = this.data = null;
};
InterAppMessageEvent.prototype = {
classDescription: "MozInterAppMessageEvent",
classID: Components.ID("{33b4dff4-edf8-11e2-ae9c-77f99f99c3ad}"),
contractID: "@mozilla.org/dom/inter-app-message-event;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
__init: function(aType, aDict) {
this.type = aType;
this.__DOM_IMPL__.initEvent(aType, aDict.bubbles || false,
aDict.cancelable || false);
this.data = aDict.data;
}
};
function InterAppMessagePort() {
debug("InterAppMessagePort()");
};
InterAppMessagePort.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: "MozInterAppMessagePort",
classID: Components.ID("{c66e0f8c-e3cb-11e2-9e85-43ef6244b884}"),
contractID: "@mozilla.org/dom/inter-app-message-port;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference]),
// Ci.nsIDOMGlobalPropertyInitializer implementation.
init: function(aWindow) {
debug("Calling init().");
this.initDOMRequestHelper(aWindow, kMessages);
let principal = aWindow.document.nodePrincipal;
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
this._pageURL = principal.URI.spec;
this._started = false;
this._closed = false;
this._messageQueue = [];
},
// WebIDL implementation for constructor.
__init: function(aKeyword, aMessagePortID, aIsPublisher) {
debug("Calling __init(): aKeyword: " + aKeyword +
" aMessagePortID: " + aMessagePortID +
" aIsPublisher: " + aIsPublisher);
this._keyword = aKeyword;
this._messagePortID = aMessagePortID;
this._isPublisher = aIsPublisher;
cpmm.sendAsyncMessage("InterAppMessagePort:Register",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL,
pageURL: this._pageURL });
},
// DOMRequestIpcHelper implementation.
uninit: function() {
debug("Calling uninit().");
// When the message port is uninitialized, we need to disentangle the
// coupling ports, as if the close() method had been called.
if (this._closed) {
debug("close() has been called. Don't need to close again.");
return;
}
this.close();
},
postMessage: function(aMessage) {
// TODO
debug("Calling postMessage().");
if (this._closed) {
debug("close() has been called. Cannot post message.");
return;
}
cpmm.sendAsyncMessage("InterAppMessagePort:PostMessage",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL,
message: aMessage });
},
start: function() {
// TODO
// Begin dispatching messages received on the port.
debug("Calling start().");
if (this._closed) {
debug("close() has been called. Cannot call start().");
return;
}
if (this._started) {
debug("start() has been called. Don't need to start again.");
return;
}
// When a port's port message queue is enabled, the event loop must use it
// as one of its task sources.
this._started = true;
while (this._messageQueue.length) {
let message = this._messageQueue.shift();
this._dispatchMessage(message);
}
},
close: function() {
// TODO
// Disconnecting the port, so that it is no longer active.
debug("Calling close().");
if (this._closed) {
debug("close() has been called. Don't need to close again.");
return;
}
this._closed = true;
this._messageQueue.length = 0;
// When this method called on a local port that is entangled with another
// port, must cause the user agent to disentangle the coupling ports.
cpmm.sendAsyncMessage("InterAppMessagePort:Unregister",
{ messagePortID: this._messagePortID,
manifestURL: this._manifestURL });
},
get onmessage() {
debug("Getting onmessage handler.");
return this.__DOM_IMPL__.getEventHandler("onmessage");
},
set onmessage(aHandler) {
debug("Setting onmessage handler.");
this.__DOM_IMPL__.setEventHandler("onmessage", aHandler);
// The first time a MessagePort object's onmessage IDL attribute is set,
// the port's message queue must be enabled, as if the start() method had
// been called.
if (this._started) {
debug("start() has been called. Don't need to start again.");
return;
}
this.start();
},
_dispatchMessage: function _dispatchMessage(aMessage) {
let wrappedMessage = ObjectWrapper.wrap(aMessage, this._window);
debug("_dispatchMessage: wrappedMessage: " + JSON.stringify(wrappedMessage));
let event = new this._window
.MozInterAppMessageEvent("message",
{ data: wrappedMessage });
this.__DOM_IMPL__.dispatchEvent(event);
},
receiveMessage: function(aMessage) {
debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
if (message.manifestURL != this._manifestURL ||
message.pageURL != this._pageURL ||
message.messagePortID != this._messagePortID) {
debug("The message doesn't belong to this page. Returning.");
return;
}
switch (aMessage.name) {
case "InterAppMessagePort:OnMessage":
if (this._closed) {
debug("close() has been called. Drop the message.");
return;
}
if (!this._started) {
debug("Not yet called start(). Queue up the message.");
this._messageQueue.push(message.message);
return;
}
this._dispatchMessage(message.message);
break;
default:
debug("Error! Shouldn't fall into this case.");
break;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppMessagePort]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppMessagePort,
InterAppMessageEvent]);

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

@ -0,0 +1,16 @@
/* 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/. */
dictionary MozInterAppMessageEventInit : EventInit {
any data;
};
[HeaderFile="mozilla/dom/InterAppComm.h",
Func="mozilla::dom::InterAppComm::EnabledForScope",
Constructor(DOMString type,
optional MozInterAppMessageEventInit eventInitDict),
JSImplementation="@mozilla.org/dom/inter-app-message-event;1"]
interface MozInterAppMessageEvent : Event {
readonly attribute any data;
};

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

@ -188,6 +188,7 @@ WEBIDL_FILES = [
'InspectorUtils.webidl',
'InterAppConnection.webidl',
'InterAppConnectionRequest.webidl',
'InterAppMessageEvent.webidl',
'InterAppMessagePort.webidl',
'KeyboardEvent.webidl',
'KeyEvent.webidl',