hannel id * * @param id {String} * WebChannel id * @param originOrPermission {nsIURI/string} * If an nsIURI, incoming events will be accepted from any origin matching * that URI's origin. * If a string, it names a permission, and incoming events will be accepted * from any https:// origin that has been granted that permission by the * permission manager. * @class */ export var WebChannel = function (id, originOrPermission) { if (!id || !originOrPermission) { throw new Error("WebChannel id and originOrPermission are required."); } this.id = id; // originOrPermission can be either an nsIURI or a string representing a // permission name. if (typeof originOrPermission == "string") { this._originCheckCallback = requestPrincipal => { // Accept events from any secure origin having the named permission. // The permission manager operates on domain names rather than true // origins (bug 1066517). To mitigate that, we explicitly check that // the scheme is https://. let uri = Services.io.newURI(requestPrincipal.originNoSuffix); if (uri.scheme != "https") { return false; } // OK - we have https - now we can check the permission. let perm = Services.perms.testExactPermissionFromPrincipal( requestPrincipal, originOrPermission ); return perm == Ci.nsIPermissionManager.ALLOW_ACTION; }; } else { // Accept events from any origin matching the given URI. // We deliberately use `originNoSuffix` here because we only want to // restrict based on the site's origin, not on other origin attributes // such as containers or private browsing. this._originCheckCallback = requestPrincipal => { return originOrPermission.prePath === requestPrincipal.originNoSuffix; }; } this._originOrPermission = originOrPermission; }; WebChannel.prototype = { /** * WebChannel id */ id: null, /** * The originOrPermission value passed to the constructor, mainly for * debugging and tests. */ _originOrPermission: null, /** * Callback that will be called with the principal of an incoming message * to check if the request should be dispatched to the listeners. */ _originCheckCallback: null, /** * WebChannelBroker that manages WebChannels */ _broker: WebChannelBroker, /** * Callback that will be called with the contents of an incoming message */ _deliverCallback: null, /** * Registers the callback for messages on this channel * Registers the channel itself with the WebChannelBroker * * @param callback {Function} * Callback that will be called when there is a message * @param {string} id * The WebChannel id that was used for this message * @param {object} message * The message itself * @param sendingContext {Object} * The sending context of the source of the message. Can be passed to * `send` to respond to a message. * @param sendingContext.browser {browser} * The object that captured the * WebChannelMessageToChrome. * @param sendingContext.eventTarget {EventTarget} * The where the message was sent. * @param sendingContext.principal {Principal} * The of the EventTarget where the * message was sent. */ listen(callback) { if (this._deliverCallback) { throw new Error("Failed to listen. Listener already attached."); } else if (!callback) { throw new Error("Failed to listen. Callback argument missing."); } else { this._deliverCallback = callback; this._broker.registerChannel(this); } }, /** * Resets the callback for messages on this channel * Removes the channel from the WebChannelBroker */ stopListening() { this._broker.unregisterChannel(this); this._deliverCallback = null; }, /** * Sends messages over the WebChannel id using the "WebChannelMessageToContent" event * * @param message {Object} * The message object that will be sent * @param target {Object} * A with the information of where to send the message. * @param target.browsingContext {BrowsingContext} * The browsingContext we should send the message to. * @param target.principal {Principal} * Principal of the target. Prevents messages from * being dispatched to unexpected origins. The system principal * can be specified to send to any target. * @param [target.eventTarget] {EventTarget} * Optional eventTarget within the browser, use to send to a * specific element. Can be null; if not null, should be * a ContentDOMReference. */ send(message, target) { let { browsingContext, principal, eventTarget } = target; if (message && browsingContext && principal) { let { currentWindowGlobal } = browsingContext; if (!currentWindowGlobal) { console.error( "Failed to send a WebChannel message. No currentWindowGlobal." ); return; } currentWindowGlobal .getActor("WebChannel") .sendAsyncMessage("WebChannelMessageToContent", { id: this.id, message, eventTarget, principal, }); } else if (!message) { console.error("Failed to send a WebChannel message. Message not set."); } else { console.error("Failed to send a WebChannel message. Target invalid."); } }, /** * Deliver WebChannel messages to the set "_channelCallback" * * @param data {Object} * Message data * @param sendingContext {Object} * Message sending context. * @param sendingContext.browsingContext {BrowsingContext} * The browsingcontext from which the * WebChannelMessageToChrome was sent. * @param sendingContext.eventTarget {EventTarget} * The where the message was sent. * Can be null; if not null, should be a ContentDOMReference. * @param sendingContext.principal {Principal} * The of the EventTarget where the message was sent. */ deliver(data, sendingContext) { if (this._deliverCallback) { try { this._deliverCallback(data.id, data.message, sendingContext); } catch (ex) { this.send( { errno: ERRNO_UNKNOWN_ERROR, error: ex.message ? ex.message : ERROR_UNKNOWN, }, sendingContext ); console.error("Failed to execute WebChannel callback:"); console.error(ex); } } else { console.error("No callback set for this channel."); } }, }; PK