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