devtools_extension_api.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. (function() {
  2. /*
  3. * Copyright (C) 2012 Google Inc. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Google Inc. nor the names of its
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. function defineCommonExtensionSymbols(apiPrivate) {
  32. if (!apiPrivate.panels) {
  33. apiPrivate.panels = {};
  34. }
  35. apiPrivate.panels.SearchAction = {
  36. CancelSearch: 'cancelSearch',
  37. PerformSearch: 'performSearch',
  38. NextSearchResult: 'nextSearchResult',
  39. PreviousSearchResult: 'previousSearchResult'
  40. };
  41. /** @enum {string} */
  42. apiPrivate.Events = {
  43. ButtonClicked: 'button-clicked-',
  44. PanelObjectSelected: 'panel-objectSelected-',
  45. NetworkRequestFinished: 'network-request-finished',
  46. OpenResource: 'open-resource',
  47. PanelSearch: 'panel-search-',
  48. RecordingStarted: 'trace-recording-started-',
  49. RecordingStopped: 'trace-recording-stopped-',
  50. ResourceAdded: 'resource-added',
  51. ResourceContentCommitted: 'resource-content-committed',
  52. ViewShown: 'view-shown-',
  53. ViewHidden: 'view-hidden-'
  54. };
  55. /** @enum {string} */
  56. apiPrivate.Commands = {
  57. AddRequestHeaders: 'addRequestHeaders',
  58. AddTraceProvider: 'addTraceProvider',
  59. ApplyStyleSheet: 'applyStyleSheet',
  60. CompleteTraceSession: 'completeTraceSession',
  61. CreatePanel: 'createPanel',
  62. CreateSidebarPane: 'createSidebarPane',
  63. CreateToolbarButton: 'createToolbarButton',
  64. EvaluateOnInspectedPage: 'evaluateOnInspectedPage',
  65. ForwardKeyboardEvent: '_forwardKeyboardEvent',
  66. GetHAR: 'getHAR',
  67. GetPageResources: 'getPageResources',
  68. GetRequestContent: 'getRequestContent',
  69. GetResourceContent: 'getResourceContent',
  70. InspectedURLChanged: 'inspectedURLChanged',
  71. OpenResource: 'openResource',
  72. Reload: 'Reload',
  73. Subscribe: 'subscribe',
  74. SetOpenResourceHandler: 'setOpenResourceHandler',
  75. SetResourceContent: 'setResourceContent',
  76. SetSidebarContent: 'setSidebarContent',
  77. SetSidebarHeight: 'setSidebarHeight',
  78. SetSidebarPage: 'setSidebarPage',
  79. ShowPanel: 'showPanel',
  80. Unsubscribe: 'unsubscribe',
  81. UpdateButton: 'updateButton'
  82. };
  83. }
  84. /**
  85. * @param {!ExtensionDescriptor} extensionInfo
  86. * @param {string} inspectedTabId
  87. * @param {string} themeName
  88. * @param {!Array<number>} keysToForward
  89. * @param {number} injectedScriptId
  90. * @param {function(!Object, !Object)} testHook
  91. * @suppressGlobalPropertiesCheck
  92. */
  93. self.injectedExtensionAPI = function(
  94. extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
  95. const keysToForwardSet = new Set(keysToForward);
  96. const chrome = window.chrome || {};
  97. const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
  98. if (devtools_descriptor) {
  99. return;
  100. }
  101. const apiPrivate = {};
  102. defineCommonExtensionSymbols(apiPrivate);
  103. const commands = apiPrivate.Commands;
  104. const events = apiPrivate.Events;
  105. let userAction = false;
  106. // Here and below, all constructors are private to API implementation.
  107. // For a public type Foo, if internal fields are present, these are on
  108. // a private FooImpl type, an instance of FooImpl is used in a closure
  109. // by Foo consutrctor to re-bind publicly exported members to an instance
  110. // of Foo.
  111. /**
  112. * @constructor
  113. */
  114. function EventSinkImpl(type, customDispatch) {
  115. this._type = type;
  116. this._listeners = [];
  117. this._customDispatch = customDispatch;
  118. }
  119. EventSinkImpl.prototype = {
  120. addListener: function(callback) {
  121. if (typeof callback !== 'function') {
  122. throw 'addListener: callback is not a function';
  123. }
  124. if (this._listeners.length === 0) {
  125. extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
  126. }
  127. this._listeners.push(callback);
  128. extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
  129. },
  130. removeListener: function(callback) {
  131. const listeners = this._listeners;
  132. for (let i = 0; i < listeners.length; ++i) {
  133. if (listeners[i] === callback) {
  134. listeners.splice(i, 1);
  135. break;
  136. }
  137. }
  138. if (this._listeners.length === 0) {
  139. extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
  140. }
  141. },
  142. /**
  143. * @param {...} vararg
  144. */
  145. _fire: function(vararg) {
  146. const listeners = this._listeners.slice();
  147. for (let i = 0; i < listeners.length; ++i) {
  148. listeners[i].apply(null, arguments);
  149. }
  150. },
  151. _dispatch: function(request) {
  152. if (this._customDispatch) {
  153. this._customDispatch.call(this, request);
  154. } else {
  155. this._fire.apply(this, request.arguments);
  156. }
  157. }
  158. };
  159. /**
  160. * @constructor
  161. */
  162. function InspectorExtensionAPI() {
  163. this.inspectedWindow = new InspectedWindow();
  164. this.panels = new Panels();
  165. this.network = new Network();
  166. this.timeline = new Timeline();
  167. defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
  168. }
  169. /**
  170. * @constructor
  171. */
  172. function Network() {
  173. /**
  174. * @this {EventSinkImpl}
  175. */
  176. function dispatchRequestEvent(message) {
  177. const request = message.arguments[1];
  178. request.__proto__ = new Request(message.arguments[0]);
  179. this._fire(request);
  180. }
  181. this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
  182. defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
  183. this.onNavigated = new EventSink(events.InspectedURLChanged);
  184. }
  185. Network.prototype = {
  186. getHAR: function(callback) {
  187. function callbackWrapper(result) {
  188. const entries = (result && result.entries) || [];
  189. for (let i = 0; i < entries.length; ++i) {
  190. entries[i].__proto__ = new Request(entries[i]._requestId);
  191. delete entries[i]._requestId;
  192. }
  193. callback(result);
  194. }
  195. extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
  196. },
  197. addRequestHeaders: function(headers) {
  198. extensionServer.sendRequest(
  199. {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
  200. }
  201. };
  202. /**
  203. * @constructor
  204. */
  205. function RequestImpl(id) {
  206. this._id = id;
  207. }
  208. RequestImpl.prototype = {
  209. getContent: function(callback) {
  210. function callbackWrapper(response) {
  211. callback(response.content, response.encoding);
  212. }
  213. extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
  214. }
  215. };
  216. /**
  217. * @constructor
  218. */
  219. function Panels() {
  220. const panels = {
  221. elements: new ElementsPanel(),
  222. sources: new SourcesPanel(),
  223. };
  224. function panelGetter(name) {
  225. return panels[name];
  226. }
  227. for (const panel in panels) {
  228. Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
  229. }
  230. this.applyStyleSheet = function(styleSheet) {
  231. extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
  232. };
  233. }
  234. Panels.prototype = {
  235. create: function(title, icon, page, callback) {
  236. const id = 'extension-panel-' + extensionServer.nextObjectId();
  237. const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
  238. extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
  239. },
  240. setOpenResourceHandler: function(callback) {
  241. const hadHandler = extensionServer.hasHandler(events.OpenResource);
  242. function callbackWrapper(message) {
  243. // Allow the panel to show itself when handling the event.
  244. userAction = true;
  245. try {
  246. callback.call(null, new Resource(message.resource), message.lineNumber);
  247. } finally {
  248. userAction = false;
  249. }
  250. }
  251. if (!callback) {
  252. extensionServer.unregisterHandler(events.OpenResource);
  253. } else {
  254. extensionServer.registerHandler(events.OpenResource, callbackWrapper);
  255. }
  256. // Only send command if we either removed an existing handler or added handler and had none before.
  257. if (hadHandler === !callback) {
  258. extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
  259. }
  260. },
  261. openResource: function(url, lineNumber, callback) {
  262. extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
  263. },
  264. get SearchAction() {
  265. return apiPrivate.panels.SearchAction;
  266. }
  267. };
  268. /**
  269. * @constructor
  270. */
  271. function ExtensionViewImpl(id) {
  272. this._id = id;
  273. /**
  274. * @this {EventSinkImpl}
  275. */
  276. function dispatchShowEvent(message) {
  277. const frameIndex = message.arguments[0];
  278. if (typeof frameIndex === 'number') {
  279. this._fire(window.parent.frames[frameIndex]);
  280. } else {
  281. this._fire();
  282. }
  283. }
  284. if (id) {
  285. this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
  286. this.onHidden = new EventSink(events.ViewHidden + id);
  287. }
  288. }
  289. /**
  290. * @constructor
  291. * @extends {ExtensionViewImpl}
  292. * @param {string} hostPanelName
  293. */
  294. function PanelWithSidebarImpl(hostPanelName) {
  295. ExtensionViewImpl.call(this, null);
  296. this._hostPanelName = hostPanelName;
  297. this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
  298. }
  299. PanelWithSidebarImpl.prototype = {
  300. createSidebarPane: function(title, callback) {
  301. const id = 'extension-sidebar-' + extensionServer.nextObjectId();
  302. const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
  303. function callbackWrapper() {
  304. callback(new ExtensionSidebarPane(id));
  305. }
  306. extensionServer.sendRequest(request, callback && callbackWrapper);
  307. },
  308. __proto__: ExtensionViewImpl.prototype
  309. };
  310. function declareInterfaceClass(implConstructor) {
  311. return function() {
  312. const impl = {__proto__: implConstructor.prototype};
  313. implConstructor.apply(impl, arguments);
  314. populateInterfaceClass(this, impl);
  315. };
  316. }
  317. function defineDeprecatedProperty(object, className, oldName, newName) {
  318. let warningGiven = false;
  319. function getter() {
  320. if (!warningGiven) {
  321. console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
  322. warningGiven = true;
  323. }
  324. return object[newName];
  325. }
  326. object.__defineGetter__(oldName, getter);
  327. }
  328. function extractCallbackArgument(args) {
  329. const lastArgument = args[args.length - 1];
  330. return typeof lastArgument === 'function' ? lastArgument : undefined;
  331. }
  332. const Button = declareInterfaceClass(ButtonImpl);
  333. const EventSink = declareInterfaceClass(EventSinkImpl);
  334. const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
  335. const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
  336. /**
  337. * @constructor
  338. * @param {string} hostPanelName
  339. */
  340. const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
  341. const Request = declareInterfaceClass(RequestImpl);
  342. const Resource = declareInterfaceClass(ResourceImpl);
  343. const TraceSession = declareInterfaceClass(TraceSessionImpl);
  344. class ElementsPanel extends PanelWithSidebarClass {
  345. constructor() {
  346. super('elements');
  347. }
  348. }
  349. class SourcesPanel extends PanelWithSidebarClass {
  350. constructor() {
  351. super('sources');
  352. }
  353. }
  354. /**
  355. * @constructor
  356. * @extends {ExtensionViewImpl}
  357. */
  358. function ExtensionPanelImpl(id) {
  359. ExtensionViewImpl.call(this, id);
  360. this.onSearch = new EventSink(events.PanelSearch + id);
  361. }
  362. ExtensionPanelImpl.prototype = {
  363. /**
  364. * @return {!Object}
  365. */
  366. createStatusBarButton: function(iconPath, tooltipText, disabled) {
  367. const id = 'button-' + extensionServer.nextObjectId();
  368. const request = {
  369. command: commands.CreateToolbarButton,
  370. panel: this._id,
  371. id: id,
  372. icon: iconPath,
  373. tooltip: tooltipText,
  374. disabled: !!disabled
  375. };
  376. extensionServer.sendRequest(request);
  377. return new Button(id);
  378. },
  379. show: function() {
  380. if (!userAction) {
  381. return;
  382. }
  383. const request = {command: commands.ShowPanel, id: this._id};
  384. extensionServer.sendRequest(request);
  385. },
  386. __proto__: ExtensionViewImpl.prototype
  387. };
  388. /**
  389. * @constructor
  390. * @extends {ExtensionViewImpl}
  391. */
  392. function ExtensionSidebarPaneImpl(id) {
  393. ExtensionViewImpl.call(this, id);
  394. }
  395. ExtensionSidebarPaneImpl.prototype = {
  396. setHeight: function(height) {
  397. extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
  398. },
  399. setExpression: function(expression, rootTitle, evaluateOptions) {
  400. const request = {
  401. command: commands.SetSidebarContent,
  402. id: this._id,
  403. expression: expression,
  404. rootTitle: rootTitle,
  405. evaluateOnPage: true,
  406. };
  407. if (typeof evaluateOptions === 'object') {
  408. request.evaluateOptions = evaluateOptions;
  409. }
  410. extensionServer.sendRequest(request, extractCallbackArgument(arguments));
  411. },
  412. setObject: function(jsonObject, rootTitle, callback) {
  413. extensionServer.sendRequest(
  414. {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
  415. },
  416. setPage: function(page) {
  417. extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
  418. },
  419. __proto__: ExtensionViewImpl.prototype
  420. };
  421. /**
  422. * @constructor
  423. */
  424. function ButtonImpl(id) {
  425. this._id = id;
  426. this.onClicked = new EventSink(events.ButtonClicked + id);
  427. }
  428. ButtonImpl.prototype = {
  429. update: function(iconPath, tooltipText, disabled) {
  430. const request =
  431. {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
  432. extensionServer.sendRequest(request);
  433. }
  434. };
  435. /**
  436. * @constructor
  437. */
  438. function Timeline() {
  439. }
  440. Timeline.prototype = {
  441. /**
  442. * @param {string} categoryName
  443. * @param {string} categoryTooltip
  444. * @return {!TraceProvider}
  445. */
  446. addTraceProvider: function(categoryName, categoryTooltip) {
  447. const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
  448. extensionServer.sendRequest(
  449. {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
  450. return new TraceProvider(id);
  451. }
  452. };
  453. /**
  454. * @constructor
  455. * @param {string} id
  456. */
  457. function TraceSessionImpl(id) {
  458. this._id = id;
  459. }
  460. TraceSessionImpl.prototype = {
  461. /**
  462. * @param {string=} url
  463. * @param {number=} timeOffset
  464. */
  465. complete: function(url, timeOffset) {
  466. const request =
  467. {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
  468. extensionServer.sendRequest(request);
  469. }
  470. };
  471. /**
  472. * @constructor
  473. * @param {string} id
  474. */
  475. function TraceProvider(id) {
  476. /**
  477. * @this {EventSinkImpl}
  478. */
  479. function dispatchRecordingStarted(message) {
  480. const sessionId = message.arguments[0];
  481. this._fire(new TraceSession(sessionId));
  482. }
  483. this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
  484. this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
  485. }
  486. /**
  487. * @constructor
  488. */
  489. function InspectedWindow() {
  490. /**
  491. * @this {EventSinkImpl}
  492. */
  493. function dispatchResourceEvent(message) {
  494. this._fire(new Resource(message.arguments[0]));
  495. }
  496. /**
  497. * @this {EventSinkImpl}
  498. */
  499. function dispatchResourceContentEvent(message) {
  500. this._fire(new Resource(message.arguments[0]), message.arguments[1]);
  501. }
  502. this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
  503. this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
  504. }
  505. InspectedWindow.prototype = {
  506. reload: function(optionsOrUserAgent) {
  507. let options = null;
  508. if (typeof optionsOrUserAgent === 'object') {
  509. options = optionsOrUserAgent;
  510. } else if (typeof optionsOrUserAgent === 'string') {
  511. options = {userAgent: optionsOrUserAgent};
  512. console.warn(
  513. 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
  514. 'Use inspectedWindow.reload({ userAgent: value}) instead.');
  515. }
  516. extensionServer.sendRequest({command: commands.Reload, options: options});
  517. },
  518. /**
  519. * @return {?Object}
  520. */
  521. eval: function(expression, evaluateOptions) {
  522. const callback = extractCallbackArgument(arguments);
  523. function callbackWrapper(result) {
  524. if (result.isError || result.isException) {
  525. callback(undefined, result);
  526. } else {
  527. callback(result.value);
  528. }
  529. }
  530. const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
  531. if (typeof evaluateOptions === 'object') {
  532. request.evaluateOptions = evaluateOptions;
  533. }
  534. extensionServer.sendRequest(request, callback && callbackWrapper);
  535. return null;
  536. },
  537. getResources: function(callback) {
  538. function wrapResource(resourceData) {
  539. return new Resource(resourceData);
  540. }
  541. function callbackWrapper(resources) {
  542. callback(resources.map(wrapResource));
  543. }
  544. extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
  545. }
  546. };
  547. /**
  548. * @constructor
  549. */
  550. function ResourceImpl(resourceData) {
  551. this._url = resourceData.url;
  552. this._type = resourceData.type;
  553. }
  554. ResourceImpl.prototype = {
  555. get url() {
  556. return this._url;
  557. },
  558. get type() {
  559. return this._type;
  560. },
  561. getContent: function(callback) {
  562. function callbackWrapper(response) {
  563. callback(response.content, response.encoding);
  564. }
  565. extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
  566. },
  567. setContent: function(content, commit, callback) {
  568. extensionServer.sendRequest(
  569. {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
  570. }
  571. };
  572. function getTabId() {
  573. return inspectedTabId;
  574. }
  575. let keyboardEventRequestQueue = [];
  576. let forwardTimer = null;
  577. /**
  578. * @suppressGlobalPropertiesCheck
  579. */
  580. function forwardKeyboardEvent(event) {
  581. // Check if the event should be forwarded.
  582. // This is a workaround for crbug.com/923338.
  583. const focused = document.activeElement;
  584. if (focused) {
  585. const isInput = focused.nodeName === 'INPUT' || focused.nodeName === 'TEXTAREA';
  586. if (isInput && !(event.ctrlKey || event.altKey || event.metaKey)) {
  587. return;
  588. }
  589. }
  590. let modifiers = 0;
  591. if (event.shiftKey) {
  592. modifiers |= 1;
  593. }
  594. if (event.ctrlKey) {
  595. modifiers |= 2;
  596. }
  597. if (event.altKey) {
  598. modifiers |= 4;
  599. }
  600. if (event.metaKey) {
  601. modifiers |= 8;
  602. }
  603. const num = (event.keyCode & 255) | (modifiers << 8);
  604. // We only care about global hotkeys, not about random text
  605. if (!keysToForwardSet.has(num)) {
  606. return;
  607. }
  608. event.preventDefault();
  609. const requestPayload = {
  610. eventType: event.type,
  611. ctrlKey: event.ctrlKey,
  612. altKey: event.altKey,
  613. metaKey: event.metaKey,
  614. shiftKey: event.shiftKey,
  615. keyIdentifier: event.keyIdentifier,
  616. key: event.key,
  617. code: event.code,
  618. location: event.location,
  619. keyCode: event.keyCode
  620. };
  621. keyboardEventRequestQueue.push(requestPayload);
  622. if (!forwardTimer) {
  623. forwardTimer = setTimeout(forwardEventQueue, 0);
  624. }
  625. }
  626. function forwardEventQueue() {
  627. forwardTimer = null;
  628. const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
  629. extensionServer.sendRequest(request);
  630. keyboardEventRequestQueue = [];
  631. }
  632. document.addEventListener('keydown', forwardKeyboardEvent, false);
  633. /**
  634. * @constructor
  635. */
  636. function ExtensionServerClient() {
  637. this._callbacks = {};
  638. this._handlers = {};
  639. this._lastRequestId = 0;
  640. this._lastObjectId = 0;
  641. this.registerHandler('callback', this._onCallback.bind(this));
  642. const channel = new MessageChannel();
  643. this._port = channel.port1;
  644. this._port.addEventListener('message', this._onMessage.bind(this), false);
  645. this._port.start();
  646. window.parent.postMessage('registerExtension', '*', [channel.port2]);
  647. }
  648. ExtensionServerClient.prototype = {
  649. /**
  650. * @param {!Object} message
  651. * @param {function()=} callback
  652. */
  653. sendRequest: function(message, callback) {
  654. if (typeof callback === 'function') {
  655. message.requestId = this._registerCallback(callback);
  656. }
  657. this._port.postMessage(message);
  658. },
  659. /**
  660. * @return {boolean}
  661. */
  662. hasHandler: function(command) {
  663. return !!this._handlers[command];
  664. },
  665. registerHandler: function(command, handler) {
  666. this._handlers[command] = handler;
  667. },
  668. unregisterHandler: function(command) {
  669. delete this._handlers[command];
  670. },
  671. /**
  672. * @return {string}
  673. */
  674. nextObjectId: function() {
  675. return injectedScriptId.toString() + '_' + ++this._lastObjectId;
  676. },
  677. _registerCallback: function(callback) {
  678. const id = ++this._lastRequestId;
  679. this._callbacks[id] = callback;
  680. return id;
  681. },
  682. _onCallback: function(request) {
  683. if (request.requestId in this._callbacks) {
  684. const callback = this._callbacks[request.requestId];
  685. delete this._callbacks[request.requestId];
  686. callback(request.result);
  687. }
  688. },
  689. _onMessage: function(event) {
  690. const request = event.data;
  691. const handler = this._handlers[request.command];
  692. if (handler) {
  693. handler.call(this, request);
  694. }
  695. }
  696. };
  697. function populateInterfaceClass(interfaze, implementation) {
  698. for (const member in implementation) {
  699. if (member.charAt(0) === '_') {
  700. continue;
  701. }
  702. let descriptor = null;
  703. // Traverse prototype chain until we find the owner.
  704. for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
  705. descriptor = Object.getOwnPropertyDescriptor(owner, member);
  706. }
  707. if (!descriptor) {
  708. continue;
  709. }
  710. if (typeof descriptor.value === 'function') {
  711. interfaze[member] = descriptor.value.bind(implementation);
  712. } else if (typeof descriptor.get === 'function') {
  713. interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
  714. } else {
  715. Object.defineProperty(interfaze, member, descriptor);
  716. }
  717. }
  718. }
  719. const extensionServer = new ExtensionServerClient();
  720. const coreAPI = new InspectorExtensionAPI();
  721. Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
  722. // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
  723. chrome.devtools.inspectedWindow = {};
  724. Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
  725. chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
  726. chrome.devtools.network = coreAPI.network;
  727. chrome.devtools.panels = coreAPI.panels;
  728. chrome.devtools.panels.themeName = themeName;
  729. // default to expose experimental APIs for now.
  730. if (extensionInfo.exposeExperimentalAPIs !== false) {
  731. chrome.experimental = chrome.experimental || {};
  732. chrome.experimental.devtools = chrome.experimental.devtools || {};
  733. const properties = Object.getOwnPropertyNames(coreAPI);
  734. for (let i = 0; i < properties.length; ++i) {
  735. const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
  736. if (descriptor) {
  737. Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
  738. }
  739. }
  740. chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
  741. }
  742. if (extensionInfo.exposeWebInspectorNamespace) {
  743. window.webInspector = coreAPI;
  744. }
  745. testHook(extensionServer, coreAPI);
  746. };
  747. /**
  748. * @param {!ExtensionDescriptor} extensionInfo
  749. * @param {string} inspectedTabId
  750. * @param {string} themeName
  751. * @param {!Array<number>} keysToForward
  752. * @param {function(!Object, !Object)|undefined} testHook
  753. * @return {string}
  754. */
  755. self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
  756. const argumentsJSON = [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
  757. if (!testHook) {
  758. testHook = () => {};
  759. }
  760. return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
  761. '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
  762. '})';
  763. };
  764. /* Legacy exported object */
  765. self.Extensions = self.Extensions || {};
  766. /* Legacy exported object */
  767. Extensions = Extensions || {};
  768. Extensions.extensionAPI = {};
  769. defineCommonExtensionSymbols(Extensions.extensionAPI);
  770. var tabId;
  771. var extensionInfo = {};
  772. var extensionServer;
  773. platformExtensionAPI(self.injectedExtensionAPI("remote-" + window.parent.frames.length));
  774. })();