Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / base / js / ipc.js
blob16a34cfc600a2cc625668a87c1bf1c65f288dca8
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview
8 * In Chrome Apps, some platform APIs can only be called from the background
9 * page (e.g. reloading a chrome.app.AppWindow).  Likewise, some chrome API's
10 * must be initiated by user interaction, which can only be called from the
11 * foreground.
13 * This class provides helper functions to invoke methods on different pages
14 * using chrome.runtime.sendMessage.  Messages are passed in the following
15 * format:
16 *     {methodName:{string}, params:{Array}}
18 * chrome.runtime.sendMessage allows multiple handlers to be registered on a
19 * document, but only one handler can send a response.
20 * This class uniquely identifies a method with the |methodName| and enforces
21 * that only one handler can be registered per |methodName| in the document.
23 * For example, to call method foo() in the background page from the foreground
24 * chrome.app.AppWindow, you can do the following.
25 * In the background page:
26 *     base.Ipc.getInstance().register('my.service.name', foo);
28 * In the AppWindow document:
29 *     base.Ipc.invoke('my.service.name', arg1, arg2, ...).then(
30 *       function(result) {
31 *         console.log('The result is ' + result);
32 *     });
34 * This will invoke foo() with the arg1, arg2, ....
35 * The return value of foo() will be passed back to the caller in the
36 * form of a promise.
39 /** @suppress {duplicate} */
40 var base = base || {};
42 (function() {
44 'use strict';
46 /**
47  * @constructor
48  * @private
49  */
50 base.Ipc = function() {
51   console.assert(instance_ === null, 'Duplicate base.Ipc constructor.');
52   /** @private {!Object<Function>} */
53   this.handlers_ = {};
54   this.onMessageHandler_ =
55       /** @type {function(*, MessageSender, function (*))} */ (
56           this.onMessage_.bind(this));
57   chrome.runtime.onMessage.addListener(this.onMessageHandler_);
60 /** @private */
61 base.Ipc.prototype.dispose_ = function() {
62   chrome.runtime.onMessage.removeListener(this.onMessageHandler_);
65 /**
66  * The error strings are only used for debugging purposes and are not localized.
67  *
68  * @enum {string}
69  */
70 base.Ipc.Error = {
71   UNSUPPORTED_REQUEST_TYPE: 'Unsupported method name.',
72   INVALID_REQUEST_ORIGIN:
73       'base.Ipc only accept incoming requests from the same extension.'
76 /**
77  * @constructor
78  * @param {string} methodName
79  * @param {?Array} params
80  * @struct
81  * @private
82  */
83 base.Ipc.Request_ = function(methodName, params) {
84   this.methodName = methodName;
85   this.params = params;
89 /**
90  * @param {string} methodName
91  * @param {Function} handler The handler can be invoked by calling
92  *   base.Ipc.invoke(|methodName|, arg1, arg2, ...)
93  * Async handlers that return promises are currently not supported.
94  * @return {boolean} Whether the handler is successfully registered.
95  */
96 base.Ipc.prototype.register = function(methodName, handler) {
97   if (methodName in this.handlers_) {
98     console.error('service ' + methodName + ' is already registered.');
99     return false;
100   }
101   this.handlers_[methodName] = handler;
102   return true;
106  * @param {string} methodName
107  */
108 base.Ipc.prototype.unregister = function(methodName) {
109   delete this.handlers_[methodName];
113  * @param {base.Ipc.Request_} message
114  * @param {!MessageSender} sender
115  * @param {function(*): void} sendResponse
116  */
117 base.Ipc.prototype.onMessage_ = function(message, sender, sendResponse) {
118   var methodName = message.methodName;
119   if (typeof methodName !== 'string') {
120     return;
121   }
123   if (sender.id !== chrome.runtime.id) {
124     sendResponse({error: base.Ipc.Error.INVALID_REQUEST_ORIGIN});
125     return;
126   }
128   var remoteMethod =
129       /** @type {function(*):void} */ (this.handlers_[methodName]);
130   if (!remoteMethod) {
131     sendResponse({error: base.Ipc.Error.UNSUPPORTED_REQUEST_TYPE});
132     return;
133   }
135   try {
136     sendResponse(remoteMethod.apply(null, message.params));
137   } catch (/** @type {Error} */ e) {
138     sendResponse({error: e.message});
139   }
143  * Invokes a method on a remote page
145  * @param {string} methodName
146  * @param {...} var_args
147  * @return {Promise} A Promise that would resolve to the return value of the
148  *   handler or reject if the handler throws an exception.
149  * @suppress {reportUnknownTypes}
150  */
151 base.Ipc.invoke = function(methodName, var_args) {
152   var params = Array.prototype.slice.call(arguments, 1);
153   var sendMessage = base.Promise.as(
154       chrome.runtime.sendMessage,
155       [null, new base.Ipc.Request_(methodName, params)]);
157   return sendMessage.then(
158     /** @param {?{error: Error}} response */
159     function(response) {
160       if (response && response.error) {
161         return Promise.reject(response.error);
162       } else {
163         return Promise.resolve(response);
164       }
165   });
169 /** @type {base.Ipc} */
170 var instance_ = null;
172 /** @return {base.Ipc} */
173 base.Ipc.getInstance = function() {
174   if (!instance_) {
175     instance_ = new base.Ipc();
176   }
177   return instance_;
180 base.Ipc.deleteInstance = function() {
181   if (instance_) {
182     instance_.dispose_();
183     instance_ = null;
184   }
187 })();