Home | History | Annotate | Download | only in extensions
      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