forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			276 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| /**
 | |
|  * WebChannel is an abstraction that uses the Message Manager and Custom Events
 | |
|  * to create a two-way communication channel between chrome and content code.
 | |
|  */
 | |
| 
 | |
| var EXPORTED_SYMBOLS = ["WebChannel", "WebChannelBroker"];
 | |
| 
 | |
| const ERRNO_UNKNOWN_ERROR = 999;
 | |
| const ERROR_UNKNOWN = "UNKNOWN_ERROR";
 | |
| 
 | |
| /**
 | |
|  * WebChannelBroker is a global object that helps manage WebChannel objects.
 | |
|  * This object handles channel registration, origin validation and message multiplexing.
 | |
|  */
 | |
| 
 | |
| var WebChannelBroker = Object.create({
 | |
|   /**
 | |
|    * Register a new channel that callbacks messages
 | |
|    * based on proper origin and channel name
 | |
|    *
 | |
|    * @param channel {WebChannel}
 | |
|    */
 | |
|   registerChannel(channel) {
 | |
|     if (!this._channelMap.has(channel)) {
 | |
|       this._channelMap.set(channel);
 | |
|     } else {
 | |
|       Cu.reportError("Failed to register the channel. Channel already exists.");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Unregister a channel
 | |
|    *
 | |
|    * @param channelToRemove {WebChannel}
 | |
|    *        WebChannel to remove from the channel map
 | |
|    *
 | |
|    * Removes the specified channel from the channel map
 | |
|    */
 | |
|   unregisterChannel(channelToRemove) {
 | |
|     if (!this._channelMap.delete(channelToRemove)) {
 | |
|       Cu.reportError("Failed to unregister the channel. Channel not found.");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Object to store pairs of message origins and callback functions
 | |
|    */
 | |
|   _channelMap: new Map(),
 | |
| 
 | |
|   /**
 | |
|    * Deliver a message to a registered channel.
 | |
|    *
 | |
|    * @returns bool whether we managed to find a registered channel.
 | |
|    */
 | |
|   tryToDeliver(data, sendingContext) {
 | |
|     let validChannelFound = false;
 | |
|     data.message = data.message || {};
 | |
| 
 | |
|     for (var channel of this._channelMap.keys()) {
 | |
|       if (
 | |
|         channel.id === data.id &&
 | |
|         channel._originCheckCallback(sendingContext.principal)
 | |
|       ) {
 | |
|         validChannelFound = true;
 | |
|         channel.deliver(data, sendingContext);
 | |
|       }
 | |
|     }
 | |
|     return validChannelFound;
 | |
|   },
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Creates a new WebChannel that listens and sends messages over some channel 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.
 | |
|  * @constructor
 | |
|  */
 | |
| 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 <browser> object that captured the
 | |
|    *                      WebChannelMessageToChrome.
 | |
|    *               @param sendingContext.eventTarget {EventTarget}
 | |
|    *                      The <EventTarget> where the message was sent.
 | |
|    *               @param sendingContext.principal {Principal}
 | |
|    *                      The <Principal> 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 <target> 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) {
 | |
|         Cu.reportError(
 | |
|           "Failed to send a WebChannel message. No currentWindowGlobal."
 | |
|         );
 | |
|         return;
 | |
|       }
 | |
|       currentWindowGlobal
 | |
|         .getActor("WebChannel")
 | |
|         .sendAsyncMessage("WebChannelMessageToContent", {
 | |
|           id: this.id,
 | |
|           message,
 | |
|           eventTarget,
 | |
|           principal,
 | |
|         });
 | |
|     } else if (!message) {
 | |
|       Cu.reportError("Failed to send a WebChannel message. Message not set.");
 | |
|     } else {
 | |
|       Cu.reportError("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 <EventTarget> where the message was sent.
 | |
|    *               Can be null; if not null, should be a ContentDOMReference.
 | |
|    *        @param sendingContext.principal {Principal}
 | |
|    *               The <Principal> 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
 | |
|         );
 | |
|         Cu.reportError("Failed to execute WebChannel callback:");
 | |
|         Cu.reportError(ex);
 | |
|       }
 | |
|     } else {
 | |
|       Cu.reportError("No callback set for this channel.");
 | |
|     }
 | |
|   },
 | |
| };
 | 
