OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 /** |
| 6 * @fileoverview |
| 7 * |
| 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. |
| 12 * |
| 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}} |
| 17 * |
| 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. |
| 22 * |
| 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); |
| 27 * |
| 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 * }); |
| 33 * |
| 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. |
| 37 */ |
| 38 |
| 39 /** @suppress {duplicate} */ |
| 40 var base = base || {}; |
| 41 |
| 42 (function() { |
| 43 |
| 44 'use strict'; |
| 45 |
| 46 /** |
| 47 * @constructor |
| 48 * @private |
| 49 */ |
| 50 base.Ipc = function() { |
| 51 base.debug.assert(instance_ === null); |
| 52 /** |
| 53 * @type {!Object.<Function>} |
| 54 * @private |
| 55 */ |
| 56 this.handlers_ = {}; |
| 57 this.onMessageHandler_ = this.onMessage_.bind(this); |
| 58 chrome.runtime.onMessage.addListener(this.onMessageHandler_); |
| 59 }; |
| 60 |
| 61 /** @private */ |
| 62 base.Ipc.prototype.dispose_ = function() { |
| 63 chrome.runtime.onMessage.removeListener(this.onMessageHandler_); |
| 64 }; |
| 65 |
| 66 /** |
| 67 * The error strings are only used for debugging purposes and are not localized. |
| 68 * |
| 69 * @enum {string} |
| 70 */ |
| 71 base.Ipc.Error = { |
| 72 UNSUPPORTED_REQUEST_TYPE: 'Unsupported method name.', |
| 73 INVALID_REQUEST_ORIGIN: |
| 74 'base.Ipc only accept incoming requests from the same extension.' |
| 75 }; |
| 76 |
| 77 /** |
| 78 * @constructor |
| 79 * @param {string} methodName |
| 80 * @param {?Array} params |
| 81 * @struct |
| 82 * @private |
| 83 */ |
| 84 base.Ipc.Request_ = function(methodName, params) { |
| 85 this.methodName = methodName; |
| 86 this.params = params; |
| 87 }; |
| 88 |
| 89 |
| 90 /** |
| 91 * @param {string} methodName |
| 92 * @param {function(*)} handler The handler can be invoked by calling |
| 93 * base.Ipc.invoke(|methodName|, arg1, arg2, ...) |
| 94 * Async handlers that return promises are currently not supported. |
| 95 * @return {boolean} Whether the handler is successfully registered. |
| 96 */ |
| 97 base.Ipc.prototype.register = function(methodName, handler) { |
| 98 if (methodName in this.handlers_) { |
| 99 console.error('service ' + methodName + ' is already registered.'); |
| 100 return false; |
| 101 } |
| 102 this.handlers_[methodName] = handler; |
| 103 return true; |
| 104 }; |
| 105 |
| 106 /** |
| 107 * @param {string} methodName |
| 108 */ |
| 109 base.Ipc.prototype.unregister = function(methodName) { |
| 110 delete this.handlers_[methodName]; |
| 111 }; |
| 112 |
| 113 /** |
| 114 * @param {base.Ipc.Request_} message |
| 115 * @param {chrome.runtime.MessageSender} sender |
| 116 * @param {function(*): void} sendResponse |
| 117 */ |
| 118 base.Ipc.prototype.onMessage_ = function(message, sender, sendResponse) { |
| 119 var methodName = message.methodName; |
| 120 if (typeof methodName !== 'string') { |
| 121 return; |
| 122 } |
| 123 |
| 124 if (sender.id !== chrome.runtime.id) { |
| 125 sendResponse({error: base.Ipc.Error.INVALID_REQUEST_ORIGIN}); |
| 126 return; |
| 127 } |
| 128 |
| 129 var remoteMethod = |
| 130 /** @type {function(*):void} */ (this.handlers_[methodName]); |
| 131 if (!remoteMethod) { |
| 132 sendResponse({error: base.Ipc.Error.UNSUPPORTED_REQUEST_TYPE}); |
| 133 return; |
| 134 } |
| 135 |
| 136 try { |
| 137 sendResponse(remoteMethod.apply(null, message.params)); |
| 138 } catch (/** @type {Error} */ e) { |
| 139 sendResponse({error: e.message}); |
| 140 } |
| 141 }; |
| 142 |
| 143 /** |
| 144 * Invokes a method on a remote page |
| 145 * |
| 146 * @param {string} methodName |
| 147 * @param {...} var_args |
| 148 * @return A Promise that would resolve to the return value of the handler or |
| 149 * reject if the handler throws an exception. |
| 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)]); |
| 156 |
| 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 }); |
| 166 }; |
| 167 |
| 168 |
| 169 /** @type {base.Ipc} */ |
| 170 var instance_ = null; |
| 171 |
| 172 /** @return {base.Ipc} */ |
| 173 base.Ipc.getInstance = function() { |
| 174 if (!instance_) { |
| 175 instance_ = new base.Ipc(); |
| 176 } |
| 177 return instance_; |
| 178 }; |
| 179 |
| 180 base.Ipc.deleteInstance = function() { |
| 181 if (instance_) { |
| 182 instance_.dispose_(); |
| 183 instance_ = null; |
| 184 } |
| 185 }; |
| 186 |
| 187 })(); |
OLD | NEW |