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