1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 function defineCommonExtensionSymbols(apiPrivate) 32 { 33 if (!apiPrivate.audits) 34 apiPrivate.audits = {}; 35 apiPrivate.audits.Severity = { 36 Info: "info", 37 Warning: "warning", 38 Severe: "severe" 39 }; 40 41 if (!apiPrivate.console) 42 apiPrivate.console = {}; 43 apiPrivate.console.Severity = { 44 Debug: "debug", 45 Log: "log", 46 Warning: "warning", 47 Error: "error" 48 }; 49 50 if (!apiPrivate.panels) 51 apiPrivate.panels = {}; 52 apiPrivate.panels.SearchAction = { 53 CancelSearch: "cancelSearch", 54 PerformSearch: "performSearch", 55 NextSearchResult: "nextSearchResult", 56 PreviousSearchResult: "previousSearchResult" 57 }; 58 59 apiPrivate.Events = { 60 AuditStarted: "audit-started-", 61 ButtonClicked: "button-clicked-", 62 ConsoleMessageAdded: "console-message-added", 63 PanelObjectSelected: "panel-objectSelected-", 64 NetworkRequestFinished: "network-request-finished", 65 OpenResource: "open-resource", 66 PanelSearch: "panel-search-", 67 ResourceAdded: "resource-added", 68 ResourceContentCommitted: "resource-content-committed", 69 ViewShown: "view-shown-", 70 ViewHidden: "view-hidden-" 71 }; 72 73 apiPrivate.Commands = { 74 AddAuditCategory: "addAuditCategory", 75 AddAuditResult: "addAuditResult", 76 AddConsoleMessage: "addConsoleMessage", 77 AddRequestHeaders: "addRequestHeaders", 78 ApplyStyleSheet: "applyStyleSheet", 79 CreatePanel: "createPanel", 80 CreateSidebarPane: "createSidebarPane", 81 CreateStatusBarButton: "createStatusBarButton", 82 EvaluateOnInspectedPage: "evaluateOnInspectedPage", 83 ForwardKeyboardEvent: "_forwardKeyboardEvent", 84 GetConsoleMessages: "getConsoleMessages", 85 GetHAR: "getHAR", 86 GetPageResources: "getPageResources", 87 GetRequestContent: "getRequestContent", 88 GetResourceContent: "getResourceContent", 89 InspectedURLChanged: "inspectedURLChanged", 90 OpenResource: "openResource", 91 Reload: "Reload", 92 Subscribe: "subscribe", 93 SetOpenResourceHandler: "setOpenResourceHandler", 94 SetResourceContent: "setResourceContent", 95 SetSidebarContent: "setSidebarContent", 96 SetSidebarHeight: "setSidebarHeight", 97 SetSidebarPage: "setSidebarPage", 98 ShowPanel: "showPanel", 99 StopAuditCategoryRun: "stopAuditCategoryRun", 100 Unsubscribe: "unsubscribe", 101 UpdateAuditProgress: "updateAuditProgress", 102 UpdateButton: "updateButton" 103 }; 104 } 105 106 /** 107 * @param {number} injectedScriptId 108 * @return {!Object} 109 */ 110 function injectedExtensionAPI(injectedScriptId) 111 { 112 113 var apiPrivate = {}; 114 115 defineCommonExtensionSymbols(apiPrivate); 116 117 var commands = apiPrivate.Commands; 118 var events = apiPrivate.Events; 119 var userAction = false; 120 121 // Here and below, all constructors are private to API implementation. 122 // For a public type Foo, if internal fields are present, these are on 123 // a private FooImpl type, an instance of FooImpl is used in a closure 124 // by Foo consutrctor to re-bind publicly exported members to an instance 125 // of Foo. 126 127 /** 128 * @constructor 129 */ 130 function EventSinkImpl(type, customDispatch) 131 { 132 this._type = type; 133 this._listeners = []; 134 this._customDispatch = customDispatch; 135 } 136 137 EventSinkImpl.prototype = { 138 addListener: function(callback) 139 { 140 if (typeof callback !== "function") 141 throw "addListener: callback is not a function"; 142 if (this._listeners.length === 0) 143 extensionServer.sendRequest({ command: commands.Subscribe, type: this._type }); 144 this._listeners.push(callback); 145 extensionServer.registerHandler("notify-" + this._type, this._dispatch.bind(this)); 146 }, 147 148 removeListener: function(callback) 149 { 150 var listeners = this._listeners; 151 152 for (var i = 0; i < listeners.length; ++i) { 153 if (listeners[i] === callback) { 154 listeners.splice(i, 1); 155 break; 156 } 157 } 158 if (this._listeners.length === 0) 159 extensionServer.sendRequest({ command: commands.Unsubscribe, type: this._type }); 160 }, 161 162 /** 163 * @param {...} vararg 164 */ 165 _fire: function(vararg) 166 { 167 var listeners = this._listeners.slice(); 168 for (var i = 0; i < listeners.length; ++i) 169 listeners[i].apply(null, arguments); 170 }, 171 172 _dispatch: function(request) 173 { 174 if (this._customDispatch) 175 this._customDispatch.call(this, request); 176 else 177 this._fire.apply(this, request.arguments); 178 } 179 } 180 181 /** 182 * @constructor 183 */ 184 function InspectorExtensionAPI() 185 { 186 this.audits = new Audits(); 187 this.inspectedWindow = new InspectedWindow(); 188 this.panels = new Panels(); 189 this.network = new Network(); 190 defineDeprecatedProperty(this, "webInspector", "resources", "network"); 191 this.console = new ConsoleAPI(); 192 } 193 194 /** 195 * @constructor 196 */ 197 function ConsoleAPI() 198 { 199 this.onMessageAdded = new EventSink(events.ConsoleMessageAdded); 200 } 201 202 ConsoleAPI.prototype = { 203 getMessages: function(callback) 204 { 205 extensionServer.sendRequest({ command: commands.GetConsoleMessages }, callback); 206 }, 207 208 addMessage: function(severity, text, url, line) 209 { 210 extensionServer.sendRequest({ command: commands.AddConsoleMessage, severity: severity, text: text, url: url, line: line }); 211 }, 212 213 get Severity() 214 { 215 return apiPrivate.console.Severity; 216 } 217 } 218 219 /** 220 * @constructor 221 */ 222 function Network() 223 { 224 /** 225 * @this {EventSinkImpl} 226 */ 227 function dispatchRequestEvent(message) 228 { 229 var request = message.arguments[1]; 230 request.__proto__ = new Request(message.arguments[0]); 231 this._fire(request); 232 } 233 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent); 234 defineDeprecatedProperty(this, "network", "onFinished", "onRequestFinished"); 235 this.onNavigated = new EventSink(events.InspectedURLChanged); 236 } 237 238 Network.prototype = { 239 getHAR: function(callback) 240 { 241 function callbackWrapper(result) 242 { 243 var entries = (result && result.entries) || []; 244 for (var i = 0; i < entries.length; ++i) { 245 entries[i].__proto__ = new Request(entries[i]._requestId); 246 delete entries[i]._requestId; 247 } 248 callback(result); 249 } 250 extensionServer.sendRequest({ command: commands.GetHAR }, callback && callbackWrapper); 251 }, 252 253 addRequestHeaders: function(headers) 254 { 255 extensionServer.sendRequest({ command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname }); 256 } 257 } 258 259 /** 260 * @constructor 261 */ 262 function RequestImpl(id) 263 { 264 this._id = id; 265 } 266 267 RequestImpl.prototype = { 268 getContent: function(callback) 269 { 270 function callbackWrapper(response) 271 { 272 callback(response.content, response.encoding); 273 } 274 extensionServer.sendRequest({ command: commands.GetRequestContent, id: this._id }, callback && callbackWrapper); 275 } 276 } 277 278 /** 279 * @constructor 280 */ 281 function Panels() 282 { 283 var panels = { 284 elements: new ElementsPanel(), 285 sources: new SourcesPanel(), 286 }; 287 288 function panelGetter(name) 289 { 290 return panels[name]; 291 } 292 for (var panel in panels) 293 this.__defineGetter__(panel, panelGetter.bind(null, panel)); 294 this.applyStyleSheet = function(styleSheet) { extensionServer.sendRequest({ command: commands.ApplyStyleSheet, styleSheet: styleSheet }); }; 295 } 296 297 Panels.prototype = { 298 create: function(title, icon, page, callback) 299 { 300 var id = "extension-panel-" + extensionServer.nextObjectId(); 301 var request = { 302 command: commands.CreatePanel, 303 id: id, 304 title: title, 305 icon: icon, 306 page: page 307 }; 308 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id))); 309 }, 310 311 setOpenResourceHandler: function(callback) 312 { 313 var hadHandler = extensionServer.hasHandler(events.OpenResource); 314 315 function callbackWrapper(message) 316 { 317 // Allow the panel to show itself when handling the event. 318 userAction = true; 319 try { 320 callback.call(null, new Resource(message.resource), message.lineNumber); 321 } finally { 322 userAction = false; 323 } 324 } 325 326 if (!callback) 327 extensionServer.unregisterHandler(events.OpenResource); 328 else 329 extensionServer.registerHandler(events.OpenResource, callbackWrapper); 330 331 // Only send command if we either removed an existing handler or added handler and had none before. 332 if (hadHandler === !callback) 333 extensionServer.sendRequest({ command: commands.SetOpenResourceHandler, "handlerPresent": !!callback }); 334 }, 335 336 openResource: function(url, lineNumber, callback) 337 { 338 extensionServer.sendRequest({ command: commands.OpenResource, "url": url, "lineNumber": lineNumber }, callback); 339 }, 340 341 get SearchAction() 342 { 343 return apiPrivate.panels.SearchAction; 344 } 345 } 346 347 /** 348 * @constructor 349 */ 350 function ExtensionViewImpl(id) 351 { 352 this._id = id; 353 354 /** 355 * @this {EventSinkImpl} 356 */ 357 function dispatchShowEvent(message) 358 { 359 var frameIndex = message.arguments[0]; 360 if (typeof frameIndex === "number") 361 this._fire(window.parent.frames[frameIndex]); 362 else 363 this._fire(); 364 } 365 366 if (id) { 367 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent); 368 this.onHidden = new EventSink(events.ViewHidden + id); 369 } 370 } 371 372 /** 373 * @constructor 374 * @extends {ExtensionViewImpl} 375 * @param {string} hostPanelName 376 */ 377 function PanelWithSidebarImpl(hostPanelName) 378 { 379 ExtensionViewImpl.call(this, null); 380 this._hostPanelName = hostPanelName; 381 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName); 382 } 383 384 PanelWithSidebarImpl.prototype = { 385 createSidebarPane: function(title, callback) 386 { 387 var id = "extension-sidebar-" + extensionServer.nextObjectId(); 388 var request = { 389 command: commands.CreateSidebarPane, 390 panel: this._hostPanelName, 391 id: id, 392 title: title 393 }; 394 function callbackWrapper() 395 { 396 callback(new ExtensionSidebarPane(id)); 397 } 398 extensionServer.sendRequest(request, callback && callbackWrapper); 399 }, 400 401 __proto__: ExtensionViewImpl.prototype 402 } 403 404 function declareInterfaceClass(implConstructor) 405 { 406 return function() 407 { 408 var impl = { __proto__: implConstructor.prototype }; 409 implConstructor.apply(impl, arguments); 410 populateInterfaceClass(this, impl); 411 } 412 } 413 414 function defineDeprecatedProperty(object, className, oldName, newName) 415 { 416 var warningGiven = false; 417 function getter() 418 { 419 if (!warningGiven) { 420 console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead"); 421 warningGiven = true; 422 } 423 return object[newName]; 424 } 425 object.__defineGetter__(oldName, getter); 426 } 427 428 function extractCallbackArgument(args) 429 { 430 var lastArgument = args[args.length - 1]; 431 return typeof lastArgument === "function" ? lastArgument : undefined; 432 } 433 434 var AuditCategory = declareInterfaceClass(AuditCategoryImpl); 435 var AuditResult = declareInterfaceClass(AuditResultImpl); 436 var Button = declareInterfaceClass(ButtonImpl); 437 var EventSink = declareInterfaceClass(EventSinkImpl); 438 var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl); 439 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl); 440 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl); 441 var Request = declareInterfaceClass(RequestImpl); 442 var Resource = declareInterfaceClass(ResourceImpl); 443 444 /** 445 * @constructor 446 * @extends {PanelWithSidebar} 447 */ 448 function ElementsPanel() 449 { 450 PanelWithSidebar.call(this, "elements"); 451 } 452 453 ElementsPanel.prototype = { 454 __proto__: PanelWithSidebar.prototype 455 } 456 457 /** 458 * @constructor 459 * @extends {PanelWithSidebar} 460 */ 461 function SourcesPanel() 462 { 463 PanelWithSidebar.call(this, "sources"); 464 } 465 466 SourcesPanel.prototype = { 467 __proto__: PanelWithSidebar.prototype 468 } 469 470 /** 471 * @constructor 472 * @extends {ExtensionViewImpl} 473 */ 474 function ExtensionPanelImpl(id) 475 { 476 ExtensionViewImpl.call(this, id); 477 this.onSearch = new EventSink(events.PanelSearch + id); 478 } 479 480 ExtensionPanelImpl.prototype = { 481 /** 482 * @return {!Object} 483 */ 484 createStatusBarButton: function(iconPath, tooltipText, disabled) 485 { 486 var id = "button-" + extensionServer.nextObjectId(); 487 var request = { 488 command: commands.CreateStatusBarButton, 489 panel: this._id, 490 id: id, 491 icon: iconPath, 492 tooltip: tooltipText, 493 disabled: !!disabled 494 }; 495 extensionServer.sendRequest(request); 496 return new Button(id); 497 }, 498 499 show: function() 500 { 501 if (!userAction) 502 return; 503 504 var request = { 505 command: commands.ShowPanel, 506 id: this._id 507 }; 508 extensionServer.sendRequest(request); 509 }, 510 511 __proto__: ExtensionViewImpl.prototype 512 } 513 514 /** 515 * @constructor 516 * @extends {ExtensionViewImpl} 517 */ 518 function ExtensionSidebarPaneImpl(id) 519 { 520 ExtensionViewImpl.call(this, id); 521 } 522 523 ExtensionSidebarPaneImpl.prototype = { 524 setHeight: function(height) 525 { 526 extensionServer.sendRequest({ command: commands.SetSidebarHeight, id: this._id, height: height }); 527 }, 528 529 setExpression: function(expression, rootTitle, evaluateOptions) 530 { 531 var request = { 532 command: commands.SetSidebarContent, 533 id: this._id, 534 expression: expression, 535 rootTitle: rootTitle, 536 evaluateOnPage: true, 537 }; 538 if (typeof evaluateOptions === "object") 539 request.evaluateOptions = evaluateOptions; 540 extensionServer.sendRequest(request, extractCallbackArgument(arguments)); 541 }, 542 543 setObject: function(jsonObject, rootTitle, callback) 544 { 545 extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle }, callback); 546 }, 547 548 setPage: function(page) 549 { 550 extensionServer.sendRequest({ command: commands.SetSidebarPage, id: this._id, page: page }); 551 }, 552 553 __proto__: ExtensionViewImpl.prototype 554 } 555 556 /** 557 * @constructor 558 */ 559 function ButtonImpl(id) 560 { 561 this._id = id; 562 this.onClicked = new EventSink(events.ButtonClicked + id); 563 } 564 565 ButtonImpl.prototype = { 566 update: function(iconPath, tooltipText, disabled) 567 { 568 var request = { 569 command: commands.UpdateButton, 570 id: this._id, 571 icon: iconPath, 572 tooltip: tooltipText, 573 disabled: !!disabled 574 }; 575 extensionServer.sendRequest(request); 576 } 577 }; 578 579 /** 580 * @constructor 581 */ 582 function Audits() 583 { 584 } 585 586 Audits.prototype = { 587 /** 588 * @return {!AuditCategory} 589 */ 590 addCategory: function(displayName, resultCount) 591 { 592 var id = "extension-audit-category-" + extensionServer.nextObjectId(); 593 if (typeof resultCount !== "undefined") 594 console.warn("Passing resultCount to audits.addCategory() is deprecated. Use AuditResult.updateProgress() instead."); 595 extensionServer.sendRequest({ command: commands.AddAuditCategory, id: id, displayName: displayName, resultCount: resultCount }); 596 return new AuditCategory(id); 597 } 598 } 599 600 /** 601 * @constructor 602 */ 603 function AuditCategoryImpl(id) 604 { 605 /** 606 * @this {EventSinkImpl} 607 */ 608 function dispatchAuditEvent(request) 609 { 610 var auditResult = new AuditResult(request.arguments[0]); 611 try { 612 this._fire(auditResult); 613 } catch (e) { 614 console.error("Uncaught exception in extension audit event handler: " + e); 615 auditResult.done(); 616 } 617 } 618 this._id = id; 619 this.onAuditStarted = new EventSink(events.AuditStarted + id, dispatchAuditEvent); 620 } 621 622 /** 623 * @constructor 624 */ 625 function AuditResultImpl(id) 626 { 627 this._id = id; 628 629 this.createURL = this._nodeFactory.bind(this, "url"); 630 this.createSnippet = this._nodeFactory.bind(this, "snippet"); 631 this.createText = this._nodeFactory.bind(this, "text"); 632 this.createObject = this._nodeFactory.bind(this, "object"); 633 this.createNode = this._nodeFactory.bind(this, "node"); 634 } 635 636 AuditResultImpl.prototype = { 637 addResult: function(displayName, description, severity, details) 638 { 639 // shorthand for specifying details directly in addResult(). 640 if (details && !(details instanceof AuditResultNode)) 641 details = new AuditResultNode(details instanceof Array ? details : [details]); 642 643 var request = { 644 command: commands.AddAuditResult, 645 resultId: this._id, 646 displayName: displayName, 647 description: description, 648 severity: severity, 649 details: details 650 }; 651 extensionServer.sendRequest(request); 652 }, 653 654 /** 655 * @return {!Object} 656 */ 657 createResult: function() 658 { 659 return new AuditResultNode(Array.prototype.slice.call(arguments)); 660 }, 661 662 updateProgress: function(worked, totalWork) 663 { 664 extensionServer.sendRequest({ command: commands.UpdateAuditProgress, resultId: this._id, progress: worked / totalWork }); 665 }, 666 667 done: function() 668 { 669 extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id }); 670 }, 671 672 /** 673 * @type {!Object.<string, string>} 674 */ 675 get Severity() 676 { 677 return apiPrivate.audits.Severity; 678 }, 679 680 /** 681 * @return {!{type: string, arguments: !Array.<string|number>}} 682 */ 683 createResourceLink: function(url, lineNumber) 684 { 685 return { 686 type: "resourceLink", 687 arguments: [url, lineNumber && lineNumber - 1] 688 }; 689 }, 690 691 /** 692 * @return {!{type: string, arguments: !Array.<string|number>}} 693 */ 694 _nodeFactory: function(type) 695 { 696 return { 697 type: type, 698 arguments: Array.prototype.slice.call(arguments, 1) 699 }; 700 } 701 } 702 703 /** 704 * @constructor 705 */ 706 function AuditResultNode(contents) 707 { 708 this.contents = contents; 709 this.children = []; 710 this.expanded = false; 711 } 712 713 AuditResultNode.prototype = { 714 /** 715 * @return {!Object} 716 */ 717 addChild: function() 718 { 719 var node = new AuditResultNode(Array.prototype.slice.call(arguments)); 720 this.children.push(node); 721 return node; 722 } 723 }; 724 725 /** 726 * @constructor 727 */ 728 function InspectedWindow() 729 { 730 /** 731 * @this {EventSinkImpl} 732 */ 733 function dispatchResourceEvent(message) 734 { 735 this._fire(new Resource(message.arguments[0])); 736 } 737 738 /** 739 * @this {EventSinkImpl} 740 */ 741 function dispatchResourceContentEvent(message) 742 { 743 this._fire(new Resource(message.arguments[0]), message.arguments[1]); 744 } 745 746 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent); 747 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent); 748 } 749 750 InspectedWindow.prototype = { 751 reload: function(optionsOrUserAgent) 752 { 753 var options = null; 754 if (typeof optionsOrUserAgent === "object") 755 options = optionsOrUserAgent; 756 else if (typeof optionsOrUserAgent === "string") { 757 options = { userAgent: optionsOrUserAgent }; 758 console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " + 759 "Use inspectedWindow.reload({ userAgent: value}) instead."); 760 } 761 extensionServer.sendRequest({ command: commands.Reload, options: options }); 762 }, 763 764 /** 765 * @return {?Object} 766 */ 767 eval: function(expression, evaluateOptions) 768 { 769 var callback = extractCallbackArgument(arguments); 770 function callbackWrapper(result) 771 { 772 if (result.isError || result.isException) 773 callback(undefined, result); 774 else 775 callback(result.value); 776 } 777 var request = { 778 command: commands.EvaluateOnInspectedPage, 779 expression: expression 780 }; 781 if (typeof evaluateOptions === "object") 782 request.evaluateOptions = evaluateOptions; 783 extensionServer.sendRequest(request, callback && callbackWrapper); 784 return null; 785 }, 786 787 getResources: function(callback) 788 { 789 function wrapResource(resourceData) 790 { 791 return new Resource(resourceData); 792 } 793 function callbackWrapper(resources) 794 { 795 callback(resources.map(wrapResource)); 796 } 797 extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper); 798 } 799 } 800 801 /** 802 * @constructor 803 */ 804 function ResourceImpl(resourceData) 805 { 806 this._url = resourceData.url 807 this._type = resourceData.type; 808 } 809 810 ResourceImpl.prototype = { 811 get url() 812 { 813 return this._url; 814 }, 815 816 get type() 817 { 818 return this._type; 819 }, 820 821 getContent: function(callback) 822 { 823 function callbackWrapper(response) 824 { 825 callback(response.content, response.encoding); 826 } 827 828 extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper); 829 }, 830 831 setContent: function(content, commit, callback) 832 { 833 extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback); 834 } 835 } 836 837 var keyboardEventRequestQueue = []; 838 var forwardTimer = null; 839 840 function forwardKeyboardEvent(event) 841 { 842 const Esc = "U+001B"; 843 // We only care about global hotkeys, not about random text 844 if (!event.ctrlKey && !event.altKey && !event.metaKey && !/^F\d+$/.test(event.keyIdentifier) && event.keyIdentifier !== Esc) 845 return; 846 var requestPayload = { 847 eventType: event.type, 848 ctrlKey: event.ctrlKey, 849 altKey: event.altKey, 850 metaKey: event.metaKey, 851 keyIdentifier: event.keyIdentifier, 852 location: event.location, 853 keyCode: event.keyCode 854 }; 855 keyboardEventRequestQueue.push(requestPayload); 856 if (!forwardTimer) 857 forwardTimer = setTimeout(forwardEventQueue, 0); 858 } 859 860 function forwardEventQueue() 861 { 862 forwardTimer = null; 863 var request = { 864 command: commands.ForwardKeyboardEvent, 865 entries: keyboardEventRequestQueue 866 }; 867 extensionServer.sendRequest(request); 868 keyboardEventRequestQueue = []; 869 } 870 871 document.addEventListener("keydown", forwardKeyboardEvent, false); 872 document.addEventListener("keypress", forwardKeyboardEvent, false); 873 874 /** 875 * @constructor 876 */ 877 function ExtensionServerClient() 878 { 879 this._callbacks = {}; 880 this._handlers = {}; 881 this._lastRequestId = 0; 882 this._lastObjectId = 0; 883 884 this.registerHandler("callback", this._onCallback.bind(this)); 885 886 var channel = new MessageChannel(); 887 this._port = channel.port1; 888 this._port.addEventListener("message", this._onMessage.bind(this), false); 889 this._port.start(); 890 891 window.parent.postMessage("registerExtension", [ channel.port2 ], "*"); 892 } 893 894 ExtensionServerClient.prototype = { 895 /** 896 * @param {!Object} message 897 * @param {function()=} callback 898 */ 899 sendRequest: function(message, callback) 900 { 901 if (typeof callback === "function") 902 message.requestId = this._registerCallback(callback); 903 this._port.postMessage(message); 904 }, 905 906 /** 907 * @return {boolean} 908 */ 909 hasHandler: function(command) 910 { 911 return !!this._handlers[command]; 912 }, 913 914 registerHandler: function(command, handler) 915 { 916 this._handlers[command] = handler; 917 }, 918 919 unregisterHandler: function(command) 920 { 921 delete this._handlers[command]; 922 }, 923 924 /** 925 * @return {string} 926 */ 927 nextObjectId: function() 928 { 929 return injectedScriptId + "_" + ++this._lastObjectId; 930 }, 931 932 _registerCallback: function(callback) 933 { 934 var id = ++this._lastRequestId; 935 this._callbacks[id] = callback; 936 return id; 937 }, 938 939 _onCallback: function(request) 940 { 941 if (request.requestId in this._callbacks) { 942 var callback = this._callbacks[request.requestId]; 943 delete this._callbacks[request.requestId]; 944 callback(request.result); 945 } 946 }, 947 948 _onMessage: function(event) 949 { 950 var request = event.data; 951 var handler = this._handlers[request.command]; 952 if (handler) 953 handler.call(this, request); 954 } 955 } 956 957 function populateInterfaceClass(interfaze, implementation) 958 { 959 for (var member in implementation) { 960 if (member.charAt(0) === "_") 961 continue; 962 var descriptor = null; 963 // Traverse prototype chain until we find the owner. 964 for (var owner = implementation; owner && !descriptor; owner = owner.__proto__) 965 descriptor = Object.getOwnPropertyDescriptor(owner, member); 966 if (!descriptor) 967 continue; 968 if (typeof descriptor.value === "function") 969 interfaze[member] = descriptor.value.bind(implementation); 970 else if (typeof descriptor.get === "function") 971 interfaze.__defineGetter__(member, descriptor.get.bind(implementation)); 972 else 973 Object.defineProperty(interfaze, member, descriptor); 974 } 975 } 976 977 // extensionServer is a closure variable defined by the glue below -- make sure we fail if it's not there. 978 if (!extensionServer) 979 extensionServer = new ExtensionServerClient(); 980 981 return new InspectorExtensionAPI(); 982 } 983 984 /** 985 * @suppress {checkVars, checkTypes} 986 */ 987 function platformExtensionAPI(coreAPI) 988 { 989 function getTabId() 990 { 991 return tabId; 992 } 993 chrome = window.chrome || {}; 994 // Override chrome.devtools as a workaround for a error-throwing getter being exposed 995 // in extension pages loaded into a non-extension process (only happens for remote client 996 // extensions) 997 var devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, "devtools"); 998 if (!devtools_descriptor || devtools_descriptor.get) 999 Object.defineProperty(chrome, "devtools", { value: {}, enumerable: true }); 1000 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow. 1001 chrome.devtools.inspectedWindow = {}; 1002 chrome.devtools.inspectedWindow.__defineGetter__("tabId", getTabId); 1003 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow; 1004 chrome.devtools.network = coreAPI.network; 1005 chrome.devtools.panels = coreAPI.panels; 1006 1007 // default to expose experimental APIs for now. 1008 if (extensionInfo.exposeExperimentalAPIs !== false) { 1009 chrome.experimental = chrome.experimental || {}; 1010 chrome.experimental.devtools = chrome.experimental.devtools || {}; 1011 1012 var properties = Object.getOwnPropertyNames(coreAPI); 1013 for (var i = 0; i < properties.length; ++i) { 1014 var descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]); 1015 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor); 1016 } 1017 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow; 1018 } 1019 if (extensionInfo.exposeWebInspectorNamespace) 1020 window.webInspector = coreAPI; 1021 } 1022 1023 /** 1024 * @param {!ExtensionDescriptor} extensionInfo 1025 * @return {string} 1026 */ 1027 function buildPlatformExtensionAPI(extensionInfo) 1028 { 1029 return "var extensionInfo = " + JSON.stringify(extensionInfo) + ";" + 1030 "var tabId = " + WebInspector._inspectedTabId + ";" + 1031 platformExtensionAPI.toString(); 1032 } 1033 1034 /** 1035 * @param {!ExtensionDescriptor} extensionInfo 1036 * @return {string} 1037 */ 1038 function buildExtensionAPIInjectedScript(extensionInfo) 1039 { 1040 return "(function(injectedScriptId){ " + 1041 "var extensionServer;" + 1042 defineCommonExtensionSymbols.toString() + ";" + 1043 injectedExtensionAPI.toString() + ";" + 1044 buildPlatformExtensionAPI(extensionInfo) + ";" + 1045 "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" + 1046 "return {};" + 1047 "})"; 1048 } 1049