• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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