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 TimelineEventRecorded: "timeline-event-recorded", 70 ViewShown: "view-shown-", 71 ViewHidden: "view-hidden-" 72 }; 73 74 apiPrivate.Commands = { 75 AddAuditCategory: "addAuditCategory", 76 AddAuditResult: "addAuditResult", 77 AddConsoleMessage: "addConsoleMessage", 78 AddRequestHeaders: "addRequestHeaders", 79 ApplyStyleSheet: "applyStyleSheet", 80 CreatePanel: "createPanel", 81 CreateSidebarPane: "createSidebarPane", 82 CreateStatusBarButton: "createStatusBarButton", 83 EvaluateOnInspectedPage: "evaluateOnInspectedPage", 84 ForwardKeyboardEvent: "_forwardKeyboardEvent", 85 GetConsoleMessages: "getConsoleMessages", 86 GetHAR: "getHAR", 87 GetPageResources: "getPageResources", 88 GetRequestContent: "getRequestContent", 89 GetResourceContent: "getResourceContent", 90 InspectedURLChanged: "inspectedURLChanged", 91 OpenResource: "openResource", 92 Reload: "Reload", 93 Subscribe: "subscribe", 94 SetOpenResourceHandler: "setOpenResourceHandler", 95 SetResourceContent: "setResourceContent", 96 SetSidebarContent: "setSidebarContent", 97 SetSidebarHeight: "setSidebarHeight", 98 SetSidebarPage: "setSidebarPage", 99 ShowPanel: "showPanel", 100 StopAuditCategoryRun: "stopAuditCategoryRun", 101 Unsubscribe: "unsubscribe", 102 UpdateAuditProgress: "updateAuditProgress", 103 UpdateButton: "updateButton" 104 }; 105 } 106 107 function injectedExtensionAPI(injectedScriptId) 108 { 109 110 var apiPrivate = {}; 111 112 defineCommonExtensionSymbols(apiPrivate); 113 114 var commands = apiPrivate.Commands; 115 var events = apiPrivate.Events; 116 var userAction = false; 117 118 // Here and below, all constructors are private to API implementation. 119 // For a public type Foo, if internal fields are present, these are on 120 // a private FooImpl type, an instance of FooImpl is used in a closure 121 // by Foo consutrctor to re-bind publicly exported members to an instance 122 // of Foo. 123 124 /** 125 * @constructor 126 */ 127 function EventSinkImpl(type, customDispatch) 128 { 129 this._type = type; 130 this._listeners = []; 131 this._customDispatch = customDispatch; 132 } 133 134 EventSinkImpl.prototype = { 135 addListener: function(callback) 136 { 137 if (typeof callback !== "function") 138 throw "addListener: callback is not a function"; 139 if (this._listeners.length === 0) 140 extensionServer.sendRequest({ command: commands.Subscribe, type: this._type }); 141 this._listeners.push(callback); 142 extensionServer.registerHandler("notify-" + this._type, this._dispatch.bind(this)); 143 }, 144 145 removeListener: function(callback) 146 { 147 var listeners = this._listeners; 148 149 for (var i = 0; i < listeners.length; ++i) { 150 if (listeners[i] === callback) { 151 listeners.splice(i, 1); 152 break; 153 } 154 } 155 if (this._listeners.length === 0) 156 extensionServer.sendRequest({ command: commands.Unsubscribe, type: this._type }); 157 }, 158 159 /** 160 * @param {...} vararg 161 */ 162 _fire: function(vararg) 163 { 164 var listeners = this._listeners.slice(); 165 for (var i = 0; i < listeners.length; ++i) 166 listeners[i].apply(null, arguments); 167 }, 168 169 _dispatch: function(request) 170 { 171 if (this._customDispatch) 172 this._customDispatch.call(this, request); 173 else 174 this._fire.apply(this, request.arguments); 175 } 176 } 177 178 /** 179 * @constructor 180 */ 181 function InspectorExtensionAPI() 182 { 183 this.audits = new Audits(); 184 this.inspectedWindow = new InspectedWindow(); 185 this.panels = new Panels(); 186 this.network = new Network(); 187 defineDeprecatedProperty(this, "webInspector", "resources", "network"); 188 this.timeline = new Timeline(); 189 this.console = new ConsoleAPI(); 190 } 191 192 /** 193 * @constructor 194 */ 195 function ConsoleAPI() 196 { 197 this.onMessageAdded = new EventSink(events.ConsoleMessageAdded); 198 } 199 200 ConsoleAPI.prototype = { 201 getMessages: function(callback) 202 { 203 extensionServer.sendRequest({ command: commands.GetConsoleMessages }, callback); 204 }, 205 206 addMessage: function(severity, text, url, line) 207 { 208 extensionServer.sendRequest({ command: commands.AddConsoleMessage, severity: severity, text: text, url: url, line: line }); 209 }, 210 211 get Severity() 212 { 213 return apiPrivate.console.Severity; 214 } 215 } 216 217 /** 218 * @constructor 219 */ 220 function Network() 221 { 222 /** 223 * @this {EventSinkImpl} 224 */ 225 function dispatchRequestEvent(message) 226 { 227 var request = message.arguments[1]; 228 request.__proto__ = new Request(message.arguments[0]); 229 this._fire(request); 230 } 231 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent); 232 defineDeprecatedProperty(this, "network", "onFinished", "onRequestFinished"); 233 this.onNavigated = new EventSink(events.InspectedURLChanged); 234 } 235 236 Network.prototype = { 237 getHAR: function(callback) 238 { 239 function callbackWrapper(result) 240 { 241 var entries = (result && result.entries) || []; 242 for (var i = 0; i < entries.length; ++i) { 243 entries[i].__proto__ = new Request(entries[i]._requestId); 244 delete entries[i]._requestId; 245 } 246 callback(result); 247 } 248 return extensionServer.sendRequest({ command: commands.GetHAR }, callback && callbackWrapper); 249 }, 250 251 addRequestHeaders: function(headers) 252 { 253 return extensionServer.sendRequest({ command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname }); 254 } 255 } 256 257 /** 258 * @constructor 259 */ 260 function RequestImpl(id) 261 { 262 this._id = id; 263 } 264 265 RequestImpl.prototype = { 266 getContent: function(callback) 267 { 268 function callbackWrapper(response) 269 { 270 callback(response.content, response.encoding); 271 } 272 extensionServer.sendRequest({ command: commands.GetRequestContent, id: this._id }, callback && callbackWrapper); 273 } 274 } 275 276 /** 277 * @constructor 278 */ 279 function Panels() 280 { 281 var panels = { 282 elements: new ElementsPanel(), 283 sources: new SourcesPanel(), 284 }; 285 286 function panelGetter(name) 287 { 288 return panels[name]; 289 } 290 for (var panel in panels) 291 this.__defineGetter__(panel, panelGetter.bind(null, panel)); 292 this.applyStyleSheet = function(styleSheet) { extensionServer.sendRequest({ command: commands.ApplyStyleSheet, styleSheet: styleSheet }); }; 293 } 294 295 Panels.prototype = { 296 create: function(title, icon, page, callback) 297 { 298 var id = "extension-panel-" + extensionServer.nextObjectId(); 299 var request = { 300 command: commands.CreatePanel, 301 id: id, 302 title: title, 303 icon: icon, 304 page: page 305 }; 306 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id))); 307 }, 308 309 setOpenResourceHandler: function(callback) 310 { 311 var hadHandler = extensionServer.hasHandler(events.OpenResource); 312 313 function callbackWrapper(message) 314 { 315 // Allow the panel to show itself when handling the event. 316 userAction = true; 317 try { 318 callback.call(null, new Resource(message.resource), message.lineNumber); 319 } finally { 320 userAction = false; 321 } 322 } 323 324 if (!callback) 325 extensionServer.unregisterHandler(events.OpenResource); 326 else 327 extensionServer.registerHandler(events.OpenResource, callbackWrapper); 328 329 // Only send command if we either removed an existing handler or added handler and had none before. 330 if (hadHandler === !callback) 331 extensionServer.sendRequest({ command: commands.SetOpenResourceHandler, "handlerPresent": !!callback }); 332 }, 333 334 openResource: function(url, lineNumber, callback) 335 { 336 extensionServer.sendRequest({ command: commands.OpenResource, "url": url, "lineNumber": lineNumber }, callback); 337 }, 338 339 get SearchAction() 340 { 341 return apiPrivate.panels.SearchAction; 342 } 343 } 344 345 /** 346 * @constructor 347 */ 348 function ExtensionViewImpl(id) 349 { 350 this._id = id; 351 352 /** 353 * @this {EventSinkImpl} 354 */ 355 function dispatchShowEvent(message) 356 { 357 var frameIndex = message.arguments[0]; 358 if (typeof frameIndex === "number") 359 this._fire(window.parent.frames[frameIndex]); 360 else 361 this._fire(); 362 } 363 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent); 364 this.onHidden = new EventSink(events.ViewHidden + id); 365 } 366 367 /** 368 * @constructor 369 */ 370 function PanelWithSidebarImpl(hostPanelName) 371 { 372 this._hostPanelName = hostPanelName; 373 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName); 374 } 375 376 PanelWithSidebarImpl.prototype = { 377 createSidebarPane: function(title, callback) 378 { 379 var id = "extension-sidebar-" + extensionServer.nextObjectId(); 380 var request = { 381 command: commands.CreateSidebarPane, 382 panel: this._hostPanelName, 383 id: id, 384 title: title 385 }; 386 function callbackWrapper() 387 { 388 callback(new ExtensionSidebarPane(id)); 389 } 390 extensionServer.sendRequest(request, callback && callbackWrapper); 391 }, 392 393 __proto__: ExtensionViewImpl.prototype 394 } 395 396 /** 397 * @constructor 398 * @extends {PanelWithSidebar} 399 */ 400 function ElementsPanel() 401 { 402 PanelWithSidebar.call(this, "elements"); 403 } 404 405 /** 406 * @constructor 407 * @extends {PanelWithSidebar} 408 */ 409 function SourcesPanel() 410 { 411 PanelWithSidebar.call(this, "sources"); 412 } 413 414 /** 415 * @constructor 416 * @extends {ExtensionViewImpl} 417 */ 418 function ExtensionPanelImpl(id) 419 { 420 ExtensionViewImpl.call(this, id); 421 this.onSearch = new EventSink(events.PanelSearch + id); 422 } 423 424 ExtensionPanelImpl.prototype = { 425 createStatusBarButton: function(iconPath, tooltipText, disabled) 426 { 427 var id = "button-" + extensionServer.nextObjectId(); 428 var request = { 429 command: commands.CreateStatusBarButton, 430 panel: this._id, 431 id: id, 432 icon: iconPath, 433 tooltip: tooltipText, 434 disabled: !!disabled 435 }; 436 extensionServer.sendRequest(request); 437 return new Button(id); 438 }, 439 440 show: function() 441 { 442 if (!userAction) 443 return; 444 445 var request = { 446 command: commands.ShowPanel, 447 id: this._id 448 }; 449 extensionServer.sendRequest(request); 450 }, 451 452 __proto__: ExtensionViewImpl.prototype 453 } 454 455 /** 456 * @constructor 457 * @extends {ExtensionViewImpl} 458 */ 459 function ExtensionSidebarPaneImpl(id) 460 { 461 ExtensionViewImpl.call(this, id); 462 } 463 464 ExtensionSidebarPaneImpl.prototype = { 465 setHeight: function(height) 466 { 467 extensionServer.sendRequest({ command: commands.SetSidebarHeight, id: this._id, height: height }); 468 }, 469 470 setExpression: function(expression, rootTitle, evaluateOptions) 471 { 472 var request = { 473 command: commands.SetSidebarContent, 474 id: this._id, 475 expression: expression, 476 rootTitle: rootTitle, 477 evaluateOnPage: true, 478 }; 479 if (typeof evaluateOptions === "object") 480 request.evaluateOptions = evaluateOptions; 481 extensionServer.sendRequest(request, extractCallbackArgument(arguments)); 482 }, 483 484 setObject: function(jsonObject, rootTitle, callback) 485 { 486 extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle }, callback); 487 }, 488 489 setPage: function(page) 490 { 491 extensionServer.sendRequest({ command: commands.SetSidebarPage, id: this._id, page: page }); 492 } 493 } 494 495 /** 496 * @constructor 497 */ 498 function ButtonImpl(id) 499 { 500 this._id = id; 501 this.onClicked = new EventSink(events.ButtonClicked + id); 502 } 503 504 ButtonImpl.prototype = { 505 update: function(iconPath, tooltipText, disabled) 506 { 507 var request = { 508 command: commands.UpdateButton, 509 id: this._id, 510 icon: iconPath, 511 tooltip: tooltipText, 512 disabled: !!disabled 513 }; 514 extensionServer.sendRequest(request); 515 } 516 }; 517 518 /** 519 * @constructor 520 */ 521 function Audits() 522 { 523 } 524 525 Audits.prototype = { 526 addCategory: function(displayName, resultCount) 527 { 528 var id = "extension-audit-category-" + extensionServer.nextObjectId(); 529 if (typeof resultCount !== "undefined") 530 console.warn("Passing resultCount to audits.addCategory() is deprecated. Use AuditResult.updateProgress() instead."); 531 extensionServer.sendRequest({ command: commands.AddAuditCategory, id: id, displayName: displayName, resultCount: resultCount }); 532 return new AuditCategory(id); 533 } 534 } 535 536 /** 537 * @constructor 538 */ 539 function AuditCategoryImpl(id) 540 { 541 /** 542 * @this {EventSinkImpl} 543 */ 544 function dispatchAuditEvent(request) 545 { 546 var auditResult = new AuditResult(request.arguments[0]); 547 try { 548 this._fire(auditResult); 549 } catch (e) { 550 console.error("Uncaught exception in extension audit event handler: " + e); 551 auditResult.done(); 552 } 553 } 554 this._id = id; 555 this.onAuditStarted = new EventSink(events.AuditStarted + id, dispatchAuditEvent); 556 } 557 558 /** 559 * @constructor 560 */ 561 function AuditResultImpl(id) 562 { 563 this._id = id; 564 565 this.createURL = this._nodeFactory.bind(null, "url"); 566 this.createSnippet = this._nodeFactory.bind(null, "snippet"); 567 this.createText = this._nodeFactory.bind(null, "text"); 568 this.createObject = this._nodeFactory.bind(null, "object"); 569 this.createNode = this._nodeFactory.bind(null, "node"); 570 } 571 572 AuditResultImpl.prototype = { 573 addResult: function(displayName, description, severity, details) 574 { 575 // shorthand for specifying details directly in addResult(). 576 if (details && !(details instanceof AuditResultNode)) 577 details = new AuditResultNode(details instanceof Array ? details : [details]); 578 579 var request = { 580 command: commands.AddAuditResult, 581 resultId: this._id, 582 displayName: displayName, 583 description: description, 584 severity: severity, 585 details: details 586 }; 587 extensionServer.sendRequest(request); 588 }, 589 590 createResult: function() 591 { 592 return new AuditResultNode(Array.prototype.slice.call(arguments)); 593 }, 594 595 updateProgress: function(worked, totalWork) 596 { 597 extensionServer.sendRequest({ command: commands.UpdateAuditProgress, resultId: this._id, progress: worked / totalWork }); 598 }, 599 600 done: function() 601 { 602 extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id }); 603 }, 604 605 get Severity() 606 { 607 return apiPrivate.audits.Severity; 608 }, 609 610 createResourceLink: function(url, lineNumber) 611 { 612 return { 613 type: "resourceLink", 614 arguments: [url, lineNumber && lineNumber - 1] 615 }; 616 }, 617 618 _nodeFactory: function(type) 619 { 620 return { 621 type: type, 622 arguments: Array.prototype.slice.call(arguments, 1) 623 }; 624 } 625 } 626 627 /** 628 * @constructor 629 */ 630 function AuditResultNode(contents) 631 { 632 this.contents = contents; 633 this.children = []; 634 this.expanded = false; 635 } 636 637 AuditResultNode.prototype = { 638 addChild: function() 639 { 640 var node = new AuditResultNode(Array.prototype.slice.call(arguments)); 641 this.children.push(node); 642 return node; 643 } 644 }; 645 646 /** 647 * @constructor 648 */ 649 function InspectedWindow() 650 { 651 /** 652 * @this {EventSinkImpl} 653 */ 654 function dispatchResourceEvent(message) 655 { 656 this._fire(new Resource(message.arguments[0])); 657 } 658 659 /** 660 * @this {EventSinkImpl} 661 */ 662 function dispatchResourceContentEvent(message) 663 { 664 this._fire(new Resource(message.arguments[0]), message.arguments[1]); 665 } 666 667 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent); 668 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent); 669 } 670 671 InspectedWindow.prototype = { 672 reload: function(optionsOrUserAgent) 673 { 674 var options = null; 675 if (typeof optionsOrUserAgent === "object") 676 options = optionsOrUserAgent; 677 else if (typeof optionsOrUserAgent === "string") { 678 options = { userAgent: optionsOrUserAgent }; 679 console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " + 680 "Use inspectedWindow.reload({ userAgent: value}) instead."); 681 } 682 return extensionServer.sendRequest({ command: commands.Reload, options: options }); 683 }, 684 685 eval: function(expression, evaluateOptions) 686 { 687 var callback = extractCallbackArgument(arguments); 688 function callbackWrapper(result) 689 { 690 if (result.isError || result.isException) 691 callback(undefined, result); 692 else 693 callback(result.value); 694 } 695 var request = { 696 command: commands.EvaluateOnInspectedPage, 697 expression: expression 698 }; 699 if (typeof evaluateOptions === "object") 700 request.evaluateOptions = evaluateOptions; 701 return extensionServer.sendRequest(request, callback && callbackWrapper); 702 }, 703 704 getResources: function(callback) 705 { 706 function wrapResource(resourceData) 707 { 708 return new Resource(resourceData); 709 } 710 function callbackWrapper(resources) 711 { 712 callback(resources.map(wrapResource)); 713 } 714 return extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper); 715 } 716 } 717 718 /** 719 * @constructor 720 */ 721 function ResourceImpl(resourceData) 722 { 723 this._url = resourceData.url 724 this._type = resourceData.type; 725 } 726 727 ResourceImpl.prototype = { 728 get url() 729 { 730 return this._url; 731 }, 732 733 get type() 734 { 735 return this._type; 736 }, 737 738 getContent: function(callback) 739 { 740 function callbackWrapper(response) 741 { 742 callback(response.content, response.encoding); 743 } 744 745 return extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper); 746 }, 747 748 setContent: function(content, commit, callback) 749 { 750 return extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback); 751 } 752 } 753 754 /** 755 * @constructor 756 */ 757 function TimelineImpl() 758 { 759 this.onEventRecorded = new EventSink(events.TimelineEventRecorded); 760 } 761 762 function forwardKeyboardEvent(event) 763 { 764 const Esc = "U+001B"; 765 // We only care about global hotkeys, not about random text 766 if (!event.ctrlKey && !event.altKey && !event.metaKey && !/^F\d+$/.test(event.keyIdentifier) && event.keyIdentifier !== Esc) 767 return; 768 var request = { 769 command: commands.ForwardKeyboardEvent, 770 eventType: event.type, 771 ctrlKey: event.ctrlKey, 772 altKey: event.altKey, 773 metaKey: event.metaKey, 774 keyIdentifier: event.keyIdentifier, 775 location: event.location 776 }; 777 extensionServer.sendRequest(request); 778 } 779 780 document.addEventListener("keydown", forwardKeyboardEvent, false); 781 document.addEventListener("keypress", forwardKeyboardEvent, false); 782 783 /** 784 * @constructor 785 */ 786 function ExtensionServerClient() 787 { 788 this._callbacks = {}; 789 this._handlers = {}; 790 this._lastRequestId = 0; 791 this._lastObjectId = 0; 792 793 this.registerHandler("callback", this._onCallback.bind(this)); 794 795 var channel = new MessageChannel(); 796 this._port = channel.port1; 797 this._port.addEventListener("message", this._onMessage.bind(this), false); 798 this._port.start(); 799 800 window.parent.postMessage("registerExtension", [ channel.port2 ], "*"); 801 } 802 803 ExtensionServerClient.prototype = { 804 /** 805 * @param {function()=} callback 806 */ 807 sendRequest: function(message, callback) 808 { 809 if (typeof callback === "function") 810 message.requestId = this._registerCallback(callback); 811 return this._port.postMessage(message); 812 }, 813 814 hasHandler: function(command) 815 { 816 return !!this._handlers[command]; 817 }, 818 819 registerHandler: function(command, handler) 820 { 821 this._handlers[command] = handler; 822 }, 823 824 unregisterHandler: function(command) 825 { 826 delete this._handlers[command]; 827 }, 828 829 nextObjectId: function() 830 { 831 return injectedScriptId + "_" + ++this._lastObjectId; 832 }, 833 834 _registerCallback: function(callback) 835 { 836 var id = ++this._lastRequestId; 837 this._callbacks[id] = callback; 838 return id; 839 }, 840 841 _onCallback: function(request) 842 { 843 if (request.requestId in this._callbacks) { 844 var callback = this._callbacks[request.requestId]; 845 delete this._callbacks[request.requestId]; 846 callback(request.result); 847 } 848 }, 849 850 _onMessage: function(event) 851 { 852 var request = event.data; 853 var handler = this._handlers[request.command]; 854 if (handler) 855 handler.call(this, request); 856 } 857 } 858 859 function populateInterfaceClass(interface, implementation) 860 { 861 for (var member in implementation) { 862 if (member.charAt(0) === "_") 863 continue; 864 var descriptor = null; 865 // Traverse prototype chain until we find the owner. 866 for (var owner = implementation; owner && !descriptor; owner = owner.__proto__) 867 descriptor = Object.getOwnPropertyDescriptor(owner, member); 868 if (!descriptor) 869 continue; 870 if (typeof descriptor.value === "function") 871 interface[member] = descriptor.value.bind(implementation); 872 else if (typeof descriptor.get === "function") 873 interface.__defineGetter__(member, descriptor.get.bind(implementation)); 874 else 875 Object.defineProperty(interface, member, descriptor); 876 } 877 } 878 879 function declareInterfaceClass(implConstructor) 880 { 881 return function() 882 { 883 var impl = { __proto__: implConstructor.prototype }; 884 implConstructor.apply(impl, arguments); 885 populateInterfaceClass(this, impl); 886 } 887 } 888 889 function defineDeprecatedProperty(object, className, oldName, newName) 890 { 891 var warningGiven = false; 892 function getter() 893 { 894 if (!warningGiven) { 895 console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead"); 896 warningGiven = true; 897 } 898 return object[newName]; 899 } 900 object.__defineGetter__(oldName, getter); 901 } 902 903 function extractCallbackArgument(args) 904 { 905 var lastArgument = args[args.length - 1]; 906 return typeof lastArgument === "function" ? lastArgument : undefined; 907 } 908 909 var AuditCategory = declareInterfaceClass(AuditCategoryImpl); 910 var AuditResult = declareInterfaceClass(AuditResultImpl); 911 var Button = declareInterfaceClass(ButtonImpl); 912 var EventSink = declareInterfaceClass(EventSinkImpl); 913 var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl); 914 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl); 915 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl); 916 var Request = declareInterfaceClass(RequestImpl); 917 var Resource = declareInterfaceClass(ResourceImpl); 918 var Timeline = declareInterfaceClass(TimelineImpl); 919 920 // extensionServer is a closure variable defined by the glue below -- make sure we fail if it's not there. 921 if (!extensionServer) 922 extensionServer = new ExtensionServerClient(); 923 924 return new InspectorExtensionAPI(); 925 } 926 927 /** 928 * @param {!ExtensionDescriptor} extensionInfo 929 * @return {string} 930 */ 931 function buildExtensionAPIInjectedScript(extensionInfo) 932 { 933 return "(function(injectedScriptId){ " + 934 "var extensionServer;" + 935 defineCommonExtensionSymbols.toString() + ";" + 936 injectedExtensionAPI.toString() + ";" + 937 buildPlatformExtensionAPI(extensionInfo) + ";" + 938 "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" + 939 "return {};" + 940 "})"; 941 } 942