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