chrome-api.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
  2. import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
  3. import * as url from 'url';
  4. import { Event } from '@electron/internal/renderer/extensions/event';
  5. class Tab {
  6. public id: number
  7. constructor (tabId: number) {
  8. this.id = tabId;
  9. }
  10. }
  11. class MessageSender {
  12. public tab: Tab | null
  13. public id: string
  14. public url: string
  15. constructor (tabId: number, extensionId: string) {
  16. this.tab = tabId ? new Tab(tabId) : null;
  17. this.id = extensionId;
  18. this.url = `chrome-extension://${extensionId}`;
  19. }
  20. }
  21. class Port {
  22. public disconnected: boolean = false
  23. public onDisconnect = new Event()
  24. public onMessage = new Event()
  25. public sender: MessageSender
  26. constructor (public tabId: number, public portId: number, extensionId: string, public name: string) {
  27. this.onDisconnect = new Event();
  28. this.onMessage = new Event();
  29. this.sender = new MessageSender(tabId, extensionId);
  30. ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
  31. this._onDisconnect();
  32. });
  33. ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (
  34. _event: Electron.Event, message: any
  35. ) => {
  36. const sendResponse = function () { console.error('sendResponse is not implemented'); };
  37. this.onMessage.emit(JSON.parse(message), this.sender, sendResponse);
  38. });
  39. }
  40. disconnect () {
  41. if (this.disconnected) return;
  42. ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`);
  43. this._onDisconnect();
  44. }
  45. postMessage (message: any) {
  46. ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message));
  47. }
  48. _onDisconnect () {
  49. this.disconnected = true;
  50. ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`);
  51. this.onDisconnect.emit();
  52. }
  53. }
  54. // Inject chrome API to the |context|
  55. export function injectTo (extensionId: string, context: any) {
  56. if (process.electronBinding('features').isExtensionsEnabled()) {
  57. throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled');
  58. }
  59. const chrome = context.chrome = context.chrome || {};
  60. ipcRendererInternal.onMessageFromMain(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (
  61. _event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string }
  62. ) => {
  63. chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name));
  64. });
  65. ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (
  66. _event: Electron.Event, tabId: number, message: string
  67. ) => {
  68. return new Promise(resolve => {
  69. chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve);
  70. });
  71. });
  72. ipcRendererInternal.onMessageFromMain('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => {
  73. chrome.tabs.onCreated.emit(new Tab(tabId));
  74. });
  75. ipcRendererInternal.onMessageFromMain('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => {
  76. chrome.tabs.onRemoved.emit(tabId);
  77. });
  78. chrome.runtime = {
  79. id: extensionId,
  80. // https://developer.chrome.com/extensions/runtime#method-getURL
  81. getURL: function (path: string) {
  82. return url.format({
  83. protocol: 'chrome-extension',
  84. slashes: true,
  85. hostname: extensionId,
  86. pathname: path
  87. });
  88. },
  89. // https://developer.chrome.com/extensions/runtime#method-getManifest
  90. getManifest: function () {
  91. const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId);
  92. return manifest;
  93. },
  94. // https://developer.chrome.com/extensions/runtime#method-connect
  95. connect (...args: Array<any>) {
  96. // Parse the optional args.
  97. let targetExtensionId = extensionId;
  98. let connectInfo = { name: '' };
  99. if (args.length === 1) {
  100. if (typeof args[0] === 'string') {
  101. targetExtensionId = args[0];
  102. } else {
  103. connectInfo = args[0];
  104. }
  105. } else if (args.length === 2) {
  106. [targetExtensionId, connectInfo] = args;
  107. }
  108. const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo);
  109. return new Port(tabId, portId, extensionId, connectInfo.name);
  110. },
  111. // https://developer.chrome.com/extensions/runtime#method-sendMessage
  112. sendMessage (...args: Array<any>) {
  113. // Parse the optional args.
  114. const targetExtensionId = extensionId;
  115. let message: string;
  116. let options: Object | undefined;
  117. let responseCallback: Chrome.Tabs.SendMessageCallback = () => {};
  118. if (typeof args[args.length - 1] === 'function') {
  119. responseCallback = args.pop();
  120. }
  121. if (args.length === 1) {
  122. [message] = args;
  123. } else if (args.length === 2) {
  124. if (typeof args[0] === 'string') {
  125. [extensionId, message] = args;
  126. } else {
  127. [message, options] = args;
  128. }
  129. } else {
  130. [extensionId, message, options] = args;
  131. }
  132. if (options) {
  133. console.error('options are not supported');
  134. }
  135. ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback);
  136. },
  137. onConnect: new Event(),
  138. onMessage: new Event(),
  139. onInstalled: new Event()
  140. };
  141. chrome.tabs = {
  142. // https://developer.chrome.com/extensions/tabs#method-executeScript
  143. executeScript (
  144. tabId: number,
  145. details: Chrome.Tabs.ExecuteScriptDetails,
  146. resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {}
  147. ) {
  148. ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details)
  149. .then((result: any) => resultCallback([result]));
  150. },
  151. // https://developer.chrome.com/extensions/tabs#method-sendMessage
  152. sendMessage (
  153. tabId: number,
  154. message: any,
  155. _options: Chrome.Tabs.SendMessageDetails,
  156. responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
  157. ) {
  158. ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback);
  159. },
  160. onUpdated: new Event(),
  161. onCreated: new Event(),
  162. onRemoved: new Event()
  163. };
  164. chrome.extension = {
  165. getURL: chrome.runtime.getURL,
  166. connect: chrome.runtime.connect,
  167. onConnect: chrome.runtime.onConnect,
  168. sendMessage: chrome.runtime.sendMessage,
  169. onMessage: chrome.runtime.onMessage
  170. };
  171. chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId);
  172. chrome.pageAction = {
  173. show () {},
  174. hide () {},
  175. setTitle () {},
  176. getTitle () {},
  177. setIcon () {},
  178. setPopup () {},
  179. getPopup () {}
  180. };
  181. chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId);
  182. chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup();
  183. // Electron has no concept of a browserAction but we should stub these APIs for compatibility
  184. chrome.browserAction = {
  185. setIcon () {},
  186. setPopup () {}
  187. };
  188. }