1/*
2 * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
4 * Copyright (C) 2009 Joseph Pecoraro
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 *     its contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31function preloadImages()
32{
33    (new Image()).src = "Images/clearConsoleButtonGlyph.png";
34    (new Image()).src = "Images/consoleButtonGlyph.png";
35    (new Image()).src = "Images/dockButtonGlyph.png";
36    (new Image()).src = "Images/enableOutlineButtonGlyph.png";
37    (new Image()).src = "Images/enableSolidButtonGlyph.png";
38    (new Image()).src = "Images/excludeButtonGlyph.png";
39    (new Image()).src = "Images/focusButtonGlyph.png";
40    (new Image()).src = "Images/largerResourcesButtonGlyph.png";
41    (new Image()).src = "Images/nodeSearchButtonGlyph.png";
42    (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png";
43    (new Image()).src = "Images/percentButtonGlyph.png";
44    (new Image()).src = "Images/recordButtonGlyph.png";
45    (new Image()).src = "Images/recordToggledButtonGlyph.png";
46    (new Image()).src = "Images/reloadButtonGlyph.png";
47    (new Image()).src = "Images/undockButtonGlyph.png";
48}
49
50preloadImages();
51
52var WebInspector = {
53    resources: {},
54    resourceURLMap: {},
55    cookieDomains: {},
56    missingLocalizedStrings: {},
57    pendingDispatches: 0,
58
59    // RegExp groups:
60    // 1 - scheme
61    // 2 - hostname
62    // 3 - ?port
63    // 4 - ?path
64    // 5 - ?fragment
65    URLRegExp: /^(http[s]?|file):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i,
66
67    get platform()
68    {
69        if (!("_platform" in this))
70            this._platform = InspectorFrontendHost.platform();
71
72        return this._platform;
73    },
74
75    get platformFlavor()
76    {
77        if (!("_platformFlavor" in this))
78            this._platformFlavor = this._detectPlatformFlavor();
79
80        return this._platformFlavor;
81    },
82
83    _detectPlatformFlavor: function()
84    {
85        const userAgent = navigator.userAgent;
86
87        if (this.platform === "windows") {
88            var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
89            if (match && match[1] >= 6)
90                return WebInspector.PlatformFlavor.WindowsVista;
91            return null;
92        } else if (this.platform === "mac") {
93            var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
94            if (!match || match[1] != 10)
95                return WebInspector.PlatformFlavor.MacSnowLeopard;
96            switch (Number(match[2])) {
97                case 4:
98                    return WebInspector.PlatformFlavor.MacTiger;
99                case 5:
100                    return WebInspector.PlatformFlavor.MacLeopard;
101                case 6:
102                default:
103                    return WebInspector.PlatformFlavor.MacSnowLeopard;
104            }
105        }
106
107        return null;
108    },
109
110    get port()
111    {
112        if (!("_port" in this))
113            this._port = InspectorFrontendHost.port();
114
115        return this._port;
116    },
117
118    get previousFocusElement()
119    {
120        return this._previousFocusElement;
121    },
122
123    get currentFocusElement()
124    {
125        return this._currentFocusElement;
126    },
127
128    set currentFocusElement(x)
129    {
130        if (this._currentFocusElement !== x)
131            this._previousFocusElement = this._currentFocusElement;
132        this._currentFocusElement = x;
133
134        if (this._currentFocusElement) {
135            this._currentFocusElement.focus();
136
137            // Make a caret selection inside the new element if there isn't a range selection and
138            // there isn't already a caret selection inside.
139            var selection = window.getSelection();
140            if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
141                var selectionRange = this._currentFocusElement.ownerDocument.createRange();
142                selectionRange.setStart(this._currentFocusElement, 0);
143                selectionRange.setEnd(this._currentFocusElement, 0);
144
145                selection.removeAllRanges();
146                selection.addRange(selectionRange);
147            }
148        } else if (this._previousFocusElement)
149            this._previousFocusElement.blur();
150    },
151
152    get currentPanel()
153    {
154        return this._currentPanel;
155    },
156
157    set currentPanel(x)
158    {
159        if (this._currentPanel === x)
160            return;
161
162        if (this._currentPanel)
163            this._currentPanel.hide();
164
165        this._currentPanel = x;
166
167        this.updateSearchLabel();
168
169        if (x) {
170            x.show();
171
172            if (this.currentQuery) {
173                if (x.performSearch) {
174                    function performPanelSearch()
175                    {
176                        this.updateSearchMatchesCount();
177
178                        x.currentQuery = this.currentQuery;
179                        x.performSearch(this.currentQuery);
180                    }
181
182                    // Perform the search on a timeout so the panel switches fast.
183                    setTimeout(performPanelSearch.bind(this), 0);
184                } else {
185                    // Update to show Not found for panels that can't be searched.
186                    this.updateSearchMatchesCount();
187                }
188            }
189        }
190
191        for (var panelName in WebInspector.panels) {
192            if (WebInspector.panels[panelName] == x)
193                InspectorBackend.storeLastActivePanel(panelName);
194        }
195    },
196
197    _createPanels: function()
198    {
199        var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
200        if (hiddenPanels.indexOf("elements") === -1)
201            this.panels.elements = new WebInspector.ElementsPanel();
202        if (hiddenPanels.indexOf("resources") === -1)
203            this.panels.resources = new WebInspector.ResourcesPanel();
204        if (hiddenPanels.indexOf("scripts") === -1)
205            this.panels.scripts = new WebInspector.ScriptsPanel();
206        if (hiddenPanels.indexOf("timeline") === -1)
207            this.panels.timeline = new WebInspector.TimelinePanel();
208        if (hiddenPanels.indexOf("profiles") === -1) {
209            this.panels.profiles = new WebInspector.ProfilesPanel();
210            this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType());
211        }
212
213        if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
214            this.panels.storage = new WebInspector.StoragePanel();
215
216        // FIXME: Uncomment when ready.
217        // if (hiddenPanels.indexOf("audits") === -1)
218        //    this.panels.audits = new WebInspector.AuditsPanel();
219
220        if (hiddenPanels.indexOf("console") === -1)
221            this.panels.console = new WebInspector.ConsolePanel();
222    },
223
224    get attached()
225    {
226        return this._attached;
227    },
228
229    set attached(x)
230    {
231        if (this._attached === x)
232            return;
233
234        this._attached = x;
235
236        this.updateSearchLabel();
237
238        var dockToggleButton = document.getElementById("dock-status-bar-item");
239        var body = document.body;
240
241        if (x) {
242            InspectorFrontendHost.attach();
243            body.removeStyleClass("detached");
244            body.addStyleClass("attached");
245            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
246        } else {
247            InspectorFrontendHost.detach();
248            body.removeStyleClass("attached");
249            body.addStyleClass("detached");
250            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
251        }
252    },
253
254    get errors()
255    {
256        return this._errors || 0;
257    },
258
259    set errors(x)
260    {
261        x = Math.max(x, 0);
262
263        if (this._errors === x)
264            return;
265        this._errors = x;
266        this._updateErrorAndWarningCounts();
267    },
268
269    get warnings()
270    {
271        return this._warnings || 0;
272    },
273
274    set warnings(x)
275    {
276        x = Math.max(x, 0);
277
278        if (this._warnings === x)
279            return;
280        this._warnings = x;
281        this._updateErrorAndWarningCounts();
282    },
283
284    _updateErrorAndWarningCounts: function()
285    {
286        var errorWarningElement = document.getElementById("error-warning-count");
287        if (!errorWarningElement)
288            return;
289
290        if (!this.errors && !this.warnings) {
291            errorWarningElement.addStyleClass("hidden");
292            return;
293        }
294
295        errorWarningElement.removeStyleClass("hidden");
296
297        errorWarningElement.removeChildren();
298
299        if (this.errors) {
300            var errorElement = document.createElement("span");
301            errorElement.id = "error-count";
302            errorElement.textContent = this.errors;
303            errorWarningElement.appendChild(errorElement);
304        }
305
306        if (this.warnings) {
307            var warningsElement = document.createElement("span");
308            warningsElement.id = "warning-count";
309            warningsElement.textContent = this.warnings;
310            errorWarningElement.appendChild(warningsElement);
311        }
312
313        if (this.errors) {
314            if (this.warnings) {
315                if (this.errors == 1) {
316                    if (this.warnings == 1)
317                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
318                    else
319                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
320                } else if (this.warnings == 1)
321                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
322                else
323                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
324            } else if (this.errors == 1)
325                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
326            else
327                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
328        } else if (this.warnings == 1)
329            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
330        else if (this.warnings)
331            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
332        else
333            errorWarningElement.title = null;
334    },
335
336    get styleChanges()
337    {
338        return this._styleChanges;
339    },
340
341    set styleChanges(x)
342    {
343        x = Math.max(x, 0);
344
345        if (this._styleChanges === x)
346            return;
347        this._styleChanges = x;
348        this._updateChangesCount();
349    },
350
351    _updateChangesCount: function()
352    {
353        // TODO: Remove immediate return when enabling the Changes Panel
354        return;
355
356        var changesElement = document.getElementById("changes-count");
357        if (!changesElement)
358            return;
359
360        if (!this.styleChanges) {
361            changesElement.addStyleClass("hidden");
362            return;
363        }
364
365        changesElement.removeStyleClass("hidden");
366        changesElement.removeChildren();
367
368        if (this.styleChanges) {
369            var styleChangesElement = document.createElement("span");
370            styleChangesElement.id = "style-changes-count";
371            styleChangesElement.textContent = this.styleChanges;
372            changesElement.appendChild(styleChangesElement);
373        }
374
375        if (this.styleChanges) {
376            if (this.styleChanges === 1)
377                changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
378            else
379                changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
380        }
381    },
382
383    get hoveredDOMNode()
384    {
385        return this._hoveredDOMNode;
386    },
387
388    set hoveredDOMNode(x)
389    {
390        if (this._hoveredDOMNode === x)
391            return;
392
393        this._hoveredDOMNode = x;
394
395        if (this._hoveredDOMNode)
396            this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
397        else
398            this._updateHoverHighlight();
399    },
400
401    _updateHoverHighlightSoon: function(delay)
402    {
403        if ("_updateHoverHighlightTimeout" in this)
404            clearTimeout(this._updateHoverHighlightTimeout);
405        this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
406    },
407
408    _updateHoverHighlight: function()
409    {
410        if ("_updateHoverHighlightTimeout" in this) {
411            clearTimeout(this._updateHoverHighlightTimeout);
412            delete this._updateHoverHighlightTimeout;
413        }
414
415        if (this._hoveredDOMNode) {
416            InspectorBackend.highlightDOMNode(this._hoveredDOMNode.id);
417            this.showingDOMNodeHighlight = true;
418        } else {
419            InspectorBackend.hideDOMNodeHighlight();
420            this.showingDOMNodeHighlight = false;
421        }
422    }
423}
424
425WebInspector.PlatformFlavor = {
426    WindowsVista: "windows-vista",
427    MacTiger: "mac-tiger",
428    MacLeopard: "mac-leopard",
429    MacSnowLeopard: "mac-snowleopard"
430}
431
432WebInspector.loaded = function()
433{
434    InspectorBackend.setInjectedScriptSource("(" + injectedScriptConstructor + ");");
435
436    var platform = WebInspector.platform;
437    document.body.addStyleClass("platform-" + platform);
438    var flavor = WebInspector.platformFlavor;
439    if (flavor)
440        document.body.addStyleClass("platform-" + flavor);
441    var port = WebInspector.port;
442    document.body.addStyleClass("port-" + port);
443
444    this.settings = new WebInspector.Settings();
445
446    this.drawer = new WebInspector.Drawer();
447    this.console = new WebInspector.ConsoleView(this.drawer);
448    // TODO: Uncomment when enabling the Changes Panel
449    // this.changes = new WebInspector.ChangesView(this.drawer);
450    // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
451    this.drawer.visibleView = this.console;
452    this.domAgent = new WebInspector.DOMAgent();
453
454    this.resourceCategories = {
455        documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"),
456        stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"),
457        images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"),
458        scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"),
459        xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"),
460        fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"),
461        other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
462    };
463
464    this.panels = {};
465    this._createPanels();
466
467    var toolbarElement = document.getElementById("toolbar");
468    var previousToolbarItem = toolbarElement.children[0];
469
470    this.panelOrder = [];
471    for (var panelName in this.panels)
472        previousToolbarItem = WebInspector.addPanelToolbarIcon(toolbarElement, this.panels[panelName], previousToolbarItem);
473
474    this.Tips = {
475        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
476    };
477
478    this.Warnings = {
479        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
480    };
481
482    this.addMainEventListeners(document);
483
484    window.addEventListener("unload", this.windowUnload.bind(this), true);
485    window.addEventListener("resize", this.windowResize.bind(this), true);
486
487    document.addEventListener("focus", this.focusChanged.bind(this), true);
488    document.addEventListener("keydown", this.documentKeyDown.bind(this), false);
489    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
490    document.addEventListener("copy", this.documentCopy.bind(this), true);
491    document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);
492
493    var dockToggleButton = document.getElementById("dock-status-bar-item");
494    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
495
496    if (this.attached)
497        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
498    else
499        dockToggleButton.title = WebInspector.UIString("Dock to main window.");
500
501    var errorWarningCount = document.getElementById("error-warning-count");
502    errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
503    this._updateErrorAndWarningCounts();
504
505    this.styleChanges = 0;
506    // TODO: Uncomment when enabling the Changes Panel
507    // var changesElement = document.getElementById("changes-count");
508    // changesElement.addEventListener("click", this.showChanges.bind(this), false);
509    // this._updateErrorAndWarningCounts();
510
511    var searchField = document.getElementById("search");
512    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
513    searchField.addEventListener("mousedown", this._searchFieldManualFocus.bind(this), false); // when the search field is manually selected
514    searchField.addEventListener("keydown", this._searchKeyDown.bind(this), true);
515
516    toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true);
517    document.getElementById("close-button-left").addEventListener("click", this.close, true);
518    document.getElementById("close-button-right").addEventListener("click", this.close, true);
519
520    InspectorFrontendHost.loaded();
521}
522
523WebInspector.addPanelToolbarIcon = function(toolbarElement, panel, previousToolbarItem)
524{
525    var panelToolbarItem = panel.toolbarItem;
526    this.panelOrder.push(panel);
527    panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
528    if (previousToolbarItem)
529        toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
530    else
531        toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
532    return panelToolbarItem;
533}
534
535var windowLoaded = function()
536{
537    var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
538    if (localizedStringsURL) {
539        var localizedStringsScriptElement = document.createElement("script");
540        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
541        localizedStringsScriptElement.type = "text/javascript";
542        localizedStringsScriptElement.src = localizedStringsURL;
543        document.head.appendChild(localizedStringsScriptElement);
544    } else
545        WebInspector.loaded();
546
547    window.removeEventListener("load", windowLoaded, false);
548    delete windowLoaded;
549};
550
551window.addEventListener("load", windowLoaded, false);
552
553WebInspector.dispatch = function() {
554    var methodName = arguments[0];
555    var parameters = Array.prototype.slice.call(arguments, 1);
556
557    // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
558    // This is important to LayoutTests.
559    function delayDispatch()
560    {
561        WebInspector[methodName].apply(WebInspector, parameters);
562        WebInspector.pendingDispatches--;
563    }
564    WebInspector.pendingDispatches++;
565    setTimeout(delayDispatch, 0);
566}
567
568WebInspector.windowUnload = function(event)
569{
570    InspectorFrontendHost.windowUnloading();
571}
572
573WebInspector.windowResize = function(event)
574{
575    if (this.currentPanel)
576        this.currentPanel.resize();
577    this.drawer.resize();
578}
579
580WebInspector.windowFocused = function(event)
581{
582    // Fires after blur, so when focusing on either the main inspector
583    // or an <iframe> within the inspector we should always remove the
584    // "inactive" class.
585    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
586        document.body.removeStyleClass("inactive");
587}
588
589WebInspector.windowBlurred = function(event)
590{
591    // Leaving the main inspector or an <iframe> within the inspector.
592    // We can add "inactive" now, and if we are moving the focus to another
593    // part of the inspector then windowFocused will correct this.
594    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
595        document.body.addStyleClass("inactive");
596}
597
598WebInspector.focusChanged = function(event)
599{
600    this.currentFocusElement = event.target;
601}
602
603WebInspector.setAttachedWindow = function(attached)
604{
605    this.attached = attached;
606}
607
608WebInspector.close = function(event)
609{
610    InspectorFrontendHost.closeWindow();
611}
612
613WebInspector.documentClick = function(event)
614{
615    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
616    if (!anchor)
617        return;
618
619    // Prevent the link from navigating, since we don't do any navigation by following links normally.
620    event.preventDefault();
621
622    function followLink()
623    {
624        // FIXME: support webkit-html-external-link links here.
625        if (WebInspector.canShowSourceLineForURL(anchor.href, anchor.preferredPanel)) {
626            if (anchor.hasStyleClass("webkit-html-external-link")) {
627                anchor.removeStyleClass("webkit-html-external-link");
628                anchor.addStyleClass("webkit-html-resource-link");
629            }
630
631            WebInspector.showSourceLineForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
632        } else {
633            var profileString = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
634            if (profileString)
635                WebInspector.showProfileForURL(anchor.href);
636        }
637    }
638
639    if (WebInspector.followLinkTimeout)
640        clearTimeout(WebInspector.followLinkTimeout);
641
642    if (anchor.preventFollowOnDoubleClick) {
643        // Start a timeout if this is the first click, if the timeout is canceled
644        // before it fires, then a double clicked happened or another link was clicked.
645        if (event.detail === 1)
646            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
647        return;
648    }
649
650    followLink();
651}
652
653WebInspector.documentKeyDown = function(event)
654{
655    if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) {
656        this.currentFocusElement.handleKeyEvent(event);
657        if (event.handled) {
658            event.preventDefault();
659            return;
660        }
661    }
662
663    if (this.currentPanel && this.currentPanel.handleShortcut) {
664        this.currentPanel.handleShortcut(event);
665        if (event.handled) {
666            event.preventDefault();
667            return;
668        }
669    }
670
671    var isMac = WebInspector.isMac();
672
673    switch (event.keyIdentifier) {
674        case "U+001B": // Escape key
675            event.preventDefault();
676            if (this.drawer.fullPanel)
677                return;
678
679            this.drawer.visible = !this.drawer.visible;
680            break;
681
682        case "U+0046": // F key
683            if (isMac)
684                var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
685            else
686                var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
687
688            if (isFindKey) {
689                var searchField = document.getElementById("search");
690                searchField.focus();
691                searchField.select();
692                event.preventDefault();
693            }
694
695            break;
696
697        case "U+0047": // G key
698            if (isMac)
699                var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
700            else
701                var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;
702
703            if (isFindAgainKey) {
704                if (event.shiftKey) {
705                    if (this.currentPanel.jumpToPreviousSearchResult)
706                        this.currentPanel.jumpToPreviousSearchResult();
707                } else if (this.currentPanel.jumpToNextSearchResult)
708                    this.currentPanel.jumpToNextSearchResult();
709                event.preventDefault();
710            }
711
712            break;
713
714        // Windows and Mac have two different definitions of [, so accept both.
715        case "U+005B":
716        case "U+00DB": // [ key
717            if (isMac)
718                var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
719            else
720                var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
721
722            if (isRotateLeft) {
723                var index = this.panelOrder.indexOf(this.currentPanel);
724                index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
725                this.panelOrder[index].toolbarItem.click();
726                event.preventDefault();
727            }
728
729            break;
730
731        // Windows and Mac have two different definitions of ], so accept both.
732        case "U+005D":
733        case "U+00DD":  // ] key
734            if (isMac)
735                var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
736            else
737                var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
738
739            if (isRotateRight) {
740                var index = this.panelOrder.indexOf(this.currentPanel);
741                index = (index + 1) % this.panelOrder.length;
742                this.panelOrder[index].toolbarItem.click();
743                event.preventDefault();
744            }
745
746            break;
747
748        case "U+0041": // A key
749            if (isMac)
750                var shouldShowAuditsPanel = event.metaKey && !event.shiftKey && !event.ctrlKey && event.altKey;
751            else
752                var shouldShowAuditsPanel = event.ctrlKey && !event.shiftKey && !event.metaKey && event.altKey;
753
754            if (shouldShowAuditsPanel) {
755                if (!this.panels.audits) {
756                    this.panels.audits = new WebInspector.AuditsPanel();
757                    var toolbarElement = document.getElementById("toolbar");
758                    WebInspector.addPanelToolbarIcon(toolbarElement, this.panels.audits, this.panels.console.toolbarItem);
759                }
760                this.currentPanel = this.panels.audits;
761            }
762
763            break;
764    }
765}
766
767WebInspector.documentCanCopy = function(event)
768{
769    if (this.currentPanel && this.currentPanel.handleCopyEvent)
770        event.preventDefault();
771}
772
773WebInspector.documentCopy = function(event)
774{
775    if (this.currentPanel && this.currentPanel.handleCopyEvent)
776        this.currentPanel.handleCopyEvent(event);
777}
778
779WebInspector.contextMenuEventFired = function(event)
780{
781    if (event.handled || event.target.hasStyleClass("popup-glasspane"))
782        event.preventDefault();
783}
784
785WebInspector.animateStyle = function(animations, duration, callback)
786{
787    var interval;
788    var complete = 0;
789
790    const intervalDuration = (1000 / 30); // 30 frames per second.
791    const animationsLength = animations.length;
792    const propertyUnit = {opacity: ""};
793    const defaultUnit = "px";
794
795    function cubicInOut(t, b, c, d)
796    {
797        if ((t/=d/2) < 1) return c/2*t*t*t + b;
798        return c/2*((t-=2)*t*t + 2) + b;
799    }
800
801    // Pre-process animations.
802    for (var i = 0; i < animationsLength; ++i) {
803        var animation = animations[i];
804        var element = null, start = null, end = null, key = null;
805        for (key in animation) {
806            if (key === "element")
807                element = animation[key];
808            else if (key === "start")
809                start = animation[key];
810            else if (key === "end")
811                end = animation[key];
812        }
813
814        if (!element || !end)
815            continue;
816
817        if (!start) {
818            var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
819            start = {};
820            for (key in end)
821                start[key] = parseInt(computedStyle.getPropertyValue(key));
822            animation.start = start;
823        } else
824            for (key in start)
825                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
826    }
827
828    function animateLoop()
829    {
830        // Advance forward.
831        complete += intervalDuration;
832        var next = complete + intervalDuration;
833
834        // Make style changes.
835        for (var i = 0; i < animationsLength; ++i) {
836            var animation = animations[i];
837            var element = animation.element;
838            var start = animation.start;
839            var end = animation.end;
840            if (!element || !end)
841                continue;
842
843            var style = element.style;
844            for (key in end) {
845                var endValue = end[key];
846                if (next < duration) {
847                    var startValue = start[key];
848                    var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
849                    style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
850                } else
851                    style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
852            }
853        }
854
855        // End condition.
856        if (complete >= duration) {
857            clearInterval(interval);
858            if (callback)
859                callback();
860        }
861    }
862
863    interval = setInterval(animateLoop, intervalDuration);
864    return interval;
865}
866
867WebInspector.updateSearchLabel = function()
868{
869    if (!this.currentPanel)
870        return;
871
872    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
873    if (this.attached)
874        document.getElementById("search").setAttribute("placeholder", newLabel);
875    else {
876        document.getElementById("search").removeAttribute("placeholder");
877        document.getElementById("search-toolbar-label").textContent = newLabel;
878    }
879}
880
881WebInspector.toggleAttach = function()
882{
883    this.attached = !this.attached;
884    this.drawer.resize();
885}
886
887WebInspector.toolbarDragStart = function(event)
888{
889    if ((!WebInspector.attached && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacLeopard && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacSnowLeopard) || WebInspector.port == "qt")
890        return;
891
892    var target = event.target;
893    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
894        return;
895
896    var toolbar = document.getElementById("toolbar");
897    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
898        return;
899
900    toolbar.lastScreenX = event.screenX;
901    toolbar.lastScreenY = event.screenY;
902
903    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
904}
905
906WebInspector.toolbarDragEnd = function(event)
907{
908    var toolbar = document.getElementById("toolbar");
909
910    WebInspector.elementDragEnd(event);
911
912    delete toolbar.lastScreenX;
913    delete toolbar.lastScreenY;
914}
915
916WebInspector.toolbarDrag = function(event)
917{
918    var toolbar = document.getElementById("toolbar");
919
920    if (WebInspector.attached) {
921        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);
922
923        InspectorFrontendHost.setAttachedWindowHeight(height);
924    } else {
925        var x = event.screenX - toolbar.lastScreenX;
926        var y = event.screenY - toolbar.lastScreenY;
927
928        // We cannot call window.moveBy here because it restricts the movement
929        // of the window at the edges.
930        InspectorFrontendHost.moveWindowBy(x, y);
931    }
932
933    toolbar.lastScreenX = event.screenX;
934    toolbar.lastScreenY = event.screenY;
935
936    event.preventDefault();
937}
938
939WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
940{
941    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
942        this.elementDragEnd(event);
943
944    this._elementDraggingEventListener = dividerDrag;
945    this._elementEndDraggingEventListener = elementDragEnd;
946
947    document.addEventListener("mousemove", dividerDrag, true);
948    document.addEventListener("mouseup", elementDragEnd, true);
949
950    document.body.style.cursor = cursor;
951
952    event.preventDefault();
953}
954
955WebInspector.elementDragEnd = function(event)
956{
957    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
958    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
959
960    document.body.style.removeProperty("cursor");
961
962    delete this._elementDraggingEventListener;
963    delete this._elementEndDraggingEventListener;
964
965    event.preventDefault();
966}
967
968WebInspector.showConsole = function()
969{
970    this.drawer.showView(this.console);
971}
972
973WebInspector.showChanges = function()
974{
975    this.drawer.showView(this.changes);
976}
977
978WebInspector.showElementsPanel = function()
979{
980    this.currentPanel = this.panels.elements;
981}
982
983WebInspector.showResourcesPanel = function()
984{
985    this.currentPanel = this.panels.resources;
986}
987
988WebInspector.showScriptsPanel = function()
989{
990    this.currentPanel = this.panels.scripts;
991}
992
993WebInspector.showTimelinePanel = function()
994{
995    this.currentPanel = this.panels.timeline;
996}
997
998WebInspector.showProfilesPanel = function()
999{
1000    this.currentPanel = this.panels.profiles;
1001}
1002
1003WebInspector.showStoragePanel = function()
1004{
1005    this.currentPanel = this.panels.storage;
1006}
1007
1008WebInspector.showConsolePanel = function()
1009{
1010    this.currentPanel = this.panels.console;
1011}
1012
1013WebInspector.clearConsoleMessages = function()
1014{
1015    WebInspector.console.clearMessages();
1016}
1017
1018WebInspector.selectDatabase = function(o)
1019{
1020    WebInspector.showStoragePanel();
1021    WebInspector.panels.storage.selectDatabase(o);
1022}
1023
1024WebInspector.selectDOMStorage = function(o)
1025{
1026    WebInspector.showStoragePanel();
1027    WebInspector.panels.storage.selectDOMStorage(o);
1028}
1029
1030WebInspector.updateResource = function(identifier, payload)
1031{
1032    var resource = this.resources[identifier];
1033    if (!resource) {
1034        resource = new WebInspector.Resource(identifier, payload.url);
1035        this.resources[identifier] = resource;
1036        this.resourceURLMap[resource.url] = resource;
1037        if (this.panels.resources)
1038            this.panels.resources.addResource(resource);
1039    }
1040
1041    if (payload.didRequestChange) {
1042        resource.domain = payload.host;
1043        resource.path = payload.path;
1044        resource.lastPathComponent = payload.lastPathComponent;
1045        resource.requestHeaders = payload.requestHeaders;
1046        resource.mainResource = payload.mainResource;
1047        resource.requestMethod = payload.requestMethod;
1048        resource.requestFormData = payload.requestFormData;
1049        resource.cached = payload.cached;
1050        resource.documentURL = payload.documentURL;
1051
1052        if (resource.mainResource)
1053            this.mainResource = resource;
1054
1055        var match = payload.documentURL.match(WebInspector.URLRegExp);
1056        if (match) {
1057            var protocol = match[1].toLowerCase();
1058            if (protocol.indexOf("http") === 0 || protocol === "file")
1059                this._addCookieDomain(protocol === "file" ? "" : match[2]);
1060        }
1061    }
1062
1063    if (payload.didResponseChange) {
1064        resource.mimeType = payload.mimeType;
1065        resource.suggestedFilename = payload.suggestedFilename;
1066        resource.expectedContentLength = payload.expectedContentLength;
1067        resource.statusCode = payload.statusCode;
1068        resource.suggestedFilename = payload.suggestedFilename;
1069        resource.responseHeaders = payload.responseHeaders;
1070    }
1071
1072    if (payload.didTypeChange) {
1073        resource.type = payload.type;
1074    }
1075
1076    if (payload.didLengthChange) {
1077        resource.contentLength = payload.contentLength;
1078    }
1079
1080    if (payload.didCompletionChange) {
1081        resource.failed = payload.failed;
1082        resource.finished = payload.finished;
1083    }
1084
1085    if (payload.didTimingChange) {
1086        if (payload.startTime)
1087            resource.startTime = payload.startTime;
1088        if (payload.responseReceivedTime)
1089            resource.responseReceivedTime = payload.responseReceivedTime;
1090        if (payload.endTime)
1091            resource.endTime = payload.endTime;
1092
1093        if (payload.loadEventTime) {
1094            // This loadEventTime is for the main resource, and we want to show it
1095            // for all resources on this page. This means we want to set it as a member
1096            // of the resources panel instead of the individual resource.
1097            if (this.panels.resources)
1098                this.panels.resources.mainResourceLoadTime = payload.loadEventTime;
1099            if (this.panels.audits)
1100                this.panels.audits.mainResourceLoadTime = payload.loadEventTime;
1101        }
1102
1103        if (payload.domContentEventTime) {
1104            // This domContentEventTime is for the main resource, so it should go in
1105            // the resources panel for the same reasons as above.
1106            if (this.panels.resources)
1107                this.panels.resources.mainResourceDOMContentTime = payload.domContentEventTime;
1108            if (this.panels.audits)
1109                this.panels.audits.mainResourceDOMContentTime = payload.domContentEventTime;
1110        }
1111    }
1112}
1113
1114WebInspector.removeResource = function(identifier)
1115{
1116    var resource = this.resources[identifier];
1117    if (!resource)
1118        return;
1119
1120    resource.category.removeResource(resource);
1121    delete this.resourceURLMap[resource.url];
1122    delete this.resources[identifier];
1123
1124    if (this.panels.resources)
1125        this.panels.resources.removeResource(resource);
1126}
1127
1128WebInspector.addDatabase = function(payload)
1129{
1130    if (!this.panels.storage)
1131        return;
1132    var database = new WebInspector.Database(
1133        payload.id,
1134        payload.domain,
1135        payload.name,
1136        payload.version);
1137    this.panels.storage.addDatabase(database);
1138}
1139
1140WebInspector._addCookieDomain = function(domain)
1141{
1142    // Eliminate duplicate domains from the list.
1143    if (domain in this.cookieDomains)
1144        return;
1145    this.cookieDomains[domain] = true;
1146
1147    if (!this.panels.storage)
1148        return;
1149    this.panels.storage.addCookieDomain(domain);
1150}
1151
1152WebInspector.addDOMStorage = function(payload)
1153{
1154    if (!this.panels.storage)
1155        return;
1156    var domStorage = new WebInspector.DOMStorage(
1157        payload.id,
1158        payload.host,
1159        payload.isLocalStorage);
1160    this.panels.storage.addDOMStorage(domStorage);
1161}
1162
1163WebInspector.updateDOMStorage = function(storageId)
1164{
1165    if (!this.panels.storage)
1166        return;
1167    this.panels.storage.updateDOMStorage(storageId);
1168}
1169
1170WebInspector.resourceTrackingWasEnabled = function()
1171{
1172    this.panels.resources.resourceTrackingWasEnabled();
1173}
1174
1175WebInspector.resourceTrackingWasDisabled = function()
1176{
1177    this.panels.resources.resourceTrackingWasDisabled();
1178}
1179
1180WebInspector.attachDebuggerWhenShown = function()
1181{
1182    this.panels.scripts.attachDebuggerWhenShown();
1183}
1184
1185WebInspector.debuggerWasEnabled = function()
1186{
1187    this.panels.scripts.debuggerWasEnabled();
1188}
1189
1190WebInspector.debuggerWasDisabled = function()
1191{
1192    this.panels.scripts.debuggerWasDisabled();
1193}
1194
1195WebInspector.profilerWasEnabled = function()
1196{
1197    this.panels.profiles.profilerWasEnabled();
1198}
1199
1200WebInspector.profilerWasDisabled = function()
1201{
1202    this.panels.profiles.profilerWasDisabled();
1203}
1204
1205WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
1206{
1207    this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
1208}
1209
1210WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
1211{
1212    this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
1213}
1214
1215WebInspector.pausedScript = function(callFrames)
1216{
1217    this.panels.scripts.debuggerPaused(callFrames);
1218}
1219
1220WebInspector.resumedScript = function()
1221{
1222    this.panels.scripts.debuggerResumed();
1223}
1224
1225WebInspector.populateInterface = function()
1226{
1227    for (var panelName in this.panels) {
1228        var panel = this.panels[panelName];
1229        if ("populateInterface" in panel)
1230            panel.populateInterface();
1231    }
1232}
1233
1234WebInspector.reset = function()
1235{
1236    for (var panelName in this.panels) {
1237        var panel = this.panels[panelName];
1238        if ("reset" in panel)
1239            panel.reset();
1240    }
1241
1242    for (var category in this.resourceCategories)
1243        this.resourceCategories[category].removeAllResources();
1244
1245    this.resources = {};
1246    this.resourceURLMap = {};
1247    this.cookieDomains = {};
1248    this.hoveredDOMNode = null;
1249
1250    delete this.mainResource;
1251
1252    this.console.clearMessages();
1253}
1254
1255WebInspector.resourceURLChanged = function(resource, oldURL)
1256{
1257    delete this.resourceURLMap[oldURL];
1258    this.resourceURLMap[resource.url] = resource;
1259}
1260
1261WebInspector.didCommitLoad = function()
1262{
1263    // Cleanup elements panel early on inspected page refresh.
1264    WebInspector.setDocument(null);
1265}
1266
1267WebInspector.updateConsoleMessageExpiredCount = function(count)
1268{
1269    var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count);
1270    WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning));
1271}
1272
1273WebInspector.addConsoleMessage = function(payload, opt_args)
1274{
1275    var consoleMessage = new WebInspector.ConsoleMessage(
1276        payload.source,
1277        payload.type,
1278        payload.level,
1279        payload.line,
1280        payload.url,
1281        payload.groupLevel,
1282        payload.repeatCount);
1283    consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1));
1284    this.console.addMessage(consoleMessage);
1285}
1286
1287WebInspector.updateConsoleMessageRepeatCount = function(count)
1288{
1289    this.console.updateMessageRepeatCount(count);
1290}
1291
1292WebInspector.log = function(message)
1293{
1294    // remember 'this' for setInterval() callback
1295    var self = this;
1296
1297    // return indication if we can actually log a message
1298    function isLogAvailable()
1299    {
1300        return WebInspector.ConsoleMessage && WebInspector.ObjectProxy && self.console;
1301    }
1302
1303    // flush the queue of pending messages
1304    function flushQueue()
1305    {
1306        var queued = WebInspector.log.queued;
1307        if (!queued)
1308            return;
1309
1310        for (var i = 0; i < queued.length; ++i)
1311            logMessage(queued[i]);
1312
1313        delete WebInspector.log.queued;
1314    }
1315
1316    // flush the queue if it console is available
1317    // - this function is run on an interval
1318    function flushQueueIfAvailable()
1319    {
1320        if (!isLogAvailable())
1321            return;
1322
1323        clearInterval(WebInspector.log.interval);
1324        delete WebInspector.log.interval;
1325
1326        flushQueue();
1327    }
1328
1329    // actually log the message
1330    function logMessage(message)
1331    {
1332        var repeatCount = 1;
1333        if (message == WebInspector.log.lastMessage)
1334            repeatCount = WebInspector.log.repeatCount + 1;
1335
1336        WebInspector.log.lastMessage = message;
1337        WebInspector.log.repeatCount = repeatCount;
1338
1339        // ConsoleMessage expects a proxy object
1340        message = new WebInspector.ObjectProxy(null, null, [], 0, message, false);
1341
1342        // post the message
1343        var msg = new WebInspector.ConsoleMessage(
1344            WebInspector.ConsoleMessage.MessageSource.Other,
1345            WebInspector.ConsoleMessage.MessageType.Log,
1346            WebInspector.ConsoleMessage.MessageLevel.Debug,
1347            -1,
1348            null,
1349            null,
1350            repeatCount,
1351            message);
1352
1353        self.console.addMessage(msg);
1354    }
1355
1356    // if we can't log the message, queue it
1357    if (!isLogAvailable()) {
1358        if (!WebInspector.log.queued)
1359            WebInspector.log.queued = [];
1360
1361        WebInspector.log.queued.push(message);
1362
1363        if (!WebInspector.log.interval)
1364            WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
1365
1366        return;
1367    }
1368
1369    // flush the pending queue if any
1370    flushQueue();
1371
1372    // log the message
1373    logMessage(message);
1374}
1375
1376WebInspector.addProfileHeader = function(profile)
1377{
1378    this.panels.profiles.addProfileHeader(profile);
1379}
1380
1381WebInspector.setRecordingProfile = function(isProfiling)
1382{
1383    this.panels.profiles.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
1384    this.panels.profiles.updateProfileTypeButtons();
1385}
1386
1387WebInspector.drawLoadingPieChart = function(canvas, percent) {
1388    var g = canvas.getContext("2d");
1389    var darkColor = "rgb(122, 168, 218)";
1390    var lightColor = "rgb(228, 241, 251)";
1391    var cx = 8;
1392    var cy = 8;
1393    var r = 7;
1394
1395    g.beginPath();
1396    g.arc(cx, cy, r, 0, Math.PI * 2, false);
1397    g.closePath();
1398
1399    g.lineWidth = 1;
1400    g.strokeStyle = darkColor;
1401    g.fillStyle = lightColor;
1402    g.fill();
1403    g.stroke();
1404
1405    var startangle = -Math.PI / 2;
1406    var endangle = startangle + (percent * Math.PI * 2);
1407
1408    g.beginPath();
1409    g.moveTo(cx, cy);
1410    g.arc(cx, cy, r, startangle, endangle, false);
1411    g.closePath();
1412
1413    g.fillStyle = darkColor;
1414    g.fill();
1415}
1416
1417WebInspector.updateFocusedNode = function(nodeId)
1418{
1419    var node = WebInspector.domAgent.nodeForId(nodeId);
1420    if (!node)
1421        // FIXME: Should we deselect if null is passed in?
1422        return;
1423
1424    this.currentPanel = this.panels.elements;
1425    this.panels.elements.focusedDOMNode = node;
1426}
1427
1428WebInspector.displayNameForURL = function(url)
1429{
1430    if (!url)
1431        return "";
1432    var resource = this.resourceURLMap[url];
1433    if (resource)
1434        return resource.displayName;
1435    return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
1436}
1437
1438WebInspector.resourceForURL = function(url)
1439{
1440    if (url in this.resourceURLMap)
1441        return this.resourceURLMap[url];
1442
1443    // No direct match found. Search for resources that contain
1444    // a substring of the URL.
1445    for (var resourceURL in this.resourceURLMap) {
1446        if (resourceURL.hasSubstring(url))
1447            return this.resourceURLMap[resourceURL];
1448    }
1449
1450    return null;
1451}
1452
1453WebInspector._choosePanelToShowSourceLineForURL = function(url, preferredPanel)
1454{
1455    preferredPanel = preferredPanel || "resources";
1456    var panel = this.panels[preferredPanel];
1457    if (panel && panel.canShowSourceLineForURL(url))
1458        return panel;
1459    panel = this.panels.resources;
1460    return panel.canShowSourceLineForURL(url) ? panel : null;
1461}
1462
1463WebInspector.canShowSourceLineForURL = function(url, preferredPanel)
1464{
1465    return !!this._choosePanelToShowSourceLineForURL(url, preferredPanel);
1466}
1467
1468WebInspector.showSourceLineForURL = function(url, line, preferredPanel)
1469{
1470    this.currentPanel = this._choosePanelToShowSourceLineForURL(url, preferredPanel);
1471    if (!this.currentPanel)
1472        return false;
1473    this.currentPanel.showSourceLineForURL(url, line);
1474    return true;
1475}
1476
1477WebInspector.linkifyStringAsFragment = function(string)
1478{
1479    var container = document.createDocumentFragment();
1480    var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1481
1482    while (string) {
1483        var linkString = linkStringRegEx.exec(string);
1484        if (!linkString)
1485            break;
1486
1487        linkString = linkString[0];
1488        var title = linkString;
1489        var linkIndex = string.indexOf(linkString);
1490        var nonLink = string.substring(0, linkIndex);
1491        container.appendChild(document.createTextNode(nonLink));
1492
1493        var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
1494        if (profileStringMatches)
1495            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
1496
1497        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
1498        container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
1499        string = string.substring(linkIndex + linkString.length, string.length);
1500    }
1501
1502    if (string)
1503        container.appendChild(document.createTextNode(string));
1504
1505    return container;
1506}
1507
1508WebInspector.showProfileForURL = function(url)
1509{
1510    WebInspector.showProfilesPanel();
1511    WebInspector.panels.profiles.showProfileForURL(url);
1512}
1513
1514WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
1515{
1516    if (!linkText)
1517        linkText = url;
1518    classes = (classes ? classes + " " : "");
1519    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1520
1521    var a = document.createElement("a");
1522    a.href = url;
1523    a.className = classes;
1524    a.title = tooltipText || url;
1525    a.target = "_blank";
1526    a.textContent = linkText;
1527
1528    return a;
1529}
1530
1531WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
1532{
1533    // Use the DOM version of this function so as to avoid needing to escape attributes.
1534    // FIXME:  Get rid of linkifyURL entirely.
1535    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
1536}
1537
1538WebInspector.completeURL = function(baseURL, href)
1539{
1540    var match = baseURL.match(WebInspector.URLRegExp);
1541    if (match) {
1542        var path = href;
1543        if (path.charAt(0) !== "/") {
1544            var basePath = match[4] || "/";
1545            path = basePath.substring(0, basePath.lastIndexOf("/")) + "/" + path;
1546        }
1547        return match[1] + "://" + match[2] + (match[3] ? (":" + match[3]) : "") + path;
1548    }
1549    return null;
1550}
1551
1552WebInspector.addMainEventListeners = function(doc)
1553{
1554    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
1555    doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
1556    doc.addEventListener("click", this.documentClick.bind(this), true);
1557}
1558
1559WebInspector._searchFieldManualFocus = function(event)
1560{
1561    this.currentFocusElement = event.target;
1562    this._previousFocusElement = event.target;
1563}
1564
1565WebInspector._searchKeyDown = function(event)
1566{
1567    // Escape Key will clear the field and clear the search results
1568    if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Esc) {
1569        // If focus belongs here and text is empty - nothing to do, return unhandled.
1570        if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement)
1571            return;
1572        event.preventDefault();
1573        event.stopPropagation();
1574        // When search was selected manually and is currently blank, we'd like Esc stay unhandled
1575        // and hit console drawer handler.
1576        event.target.value = "";
1577
1578        this.performSearch(event);
1579        this.currentFocusElement = this.previousFocusElement;
1580        if (this.currentFocusElement === event.target)
1581            this.currentFocusElement.select();
1582        return false;
1583    }
1584
1585    if (!isEnterKey(event))
1586        return false;
1587
1588    // Select all of the text so the user can easily type an entirely new query.
1589    event.target.select();
1590
1591    // Only call performSearch if the Enter key was pressed. Otherwise the search
1592    // performance is poor because of searching on every key. The search field has
1593    // the incremental attribute set, so we still get incremental searches.
1594    this.performSearch(event);
1595
1596    // Call preventDefault since this was the Enter key. This prevents a "search" event
1597    // from firing for key down. This stops performSearch from being called twice in a row.
1598    event.preventDefault();
1599}
1600
1601WebInspector.performSearch = function(event)
1602{
1603    var query = event.target.value;
1604    var forceSearch = event.keyIdentifier === "Enter";
1605    var isShortSearch = (query.length < 3);
1606
1607    // Clear a leftover short search flag due to a non-conflicting forced search.
1608    if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query)
1609        delete this.shortSearchWasForcedByKeyEvent;
1610
1611    // Indicate this was a forced search on a short query.
1612    if (isShortSearch && forceSearch)
1613        this.shortSearchWasForcedByKeyEvent = true;
1614
1615    if (!query || !query.length || (!forceSearch && isShortSearch)) {
1616        // Prevent clobbering a short search forced by the user.
1617        if (this.shortSearchWasForcedByKeyEvent) {
1618            delete this.shortSearchWasForcedByKeyEvent;
1619            return;
1620        }
1621
1622        delete this.currentQuery;
1623
1624        for (var panelName in this.panels) {
1625            var panel = this.panels[panelName];
1626            if (panel.currentQuery && panel.searchCanceled)
1627                panel.searchCanceled();
1628            delete panel.currentQuery;
1629        }
1630
1631        this.updateSearchMatchesCount();
1632
1633        return;
1634    }
1635
1636    if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
1637        // When this is the same query and a forced search, jump to the next
1638        // search result for a good user experience.
1639        if (forceSearch && this.currentPanel.jumpToNextSearchResult)
1640            this.currentPanel.jumpToNextSearchResult();
1641        return;
1642    }
1643
1644    this.currentQuery = query;
1645
1646    this.updateSearchMatchesCount();
1647
1648    if (!this.currentPanel.performSearch)
1649        return;
1650
1651    this.currentPanel.currentQuery = query;
1652    this.currentPanel.performSearch(query);
1653}
1654
1655WebInspector.addNodesToSearchResult = function(nodeIds)
1656{
1657    WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
1658}
1659
1660WebInspector.updateSearchMatchesCount = function(matches, panel)
1661{
1662    if (!panel)
1663        panel = this.currentPanel;
1664
1665    panel.currentSearchMatches = matches;
1666
1667    if (panel !== this.currentPanel)
1668        return;
1669
1670    if (!this.currentPanel.currentQuery) {
1671        document.getElementById("search-results-matches").addStyleClass("hidden");
1672        return;
1673    }
1674
1675    if (matches) {
1676        if (matches === 1)
1677            var matchesString = WebInspector.UIString("1 match");
1678        else
1679            var matchesString = WebInspector.UIString("%d matches", matches);
1680    } else
1681        var matchesString = WebInspector.UIString("Not Found");
1682
1683    var matchesToolbarElement = document.getElementById("search-results-matches");
1684    matchesToolbarElement.removeStyleClass("hidden");
1685    matchesToolbarElement.textContent = matchesString;
1686}
1687
1688WebInspector.UIString = function(string)
1689{
1690    if (window.localizedStrings && string in window.localizedStrings)
1691        string = window.localizedStrings[string];
1692    else {
1693        if (!(string in this.missingLocalizedStrings)) {
1694            if (!WebInspector.InspectorBackendStub)
1695                console.error("Localized string \"" + string + "\" not found.");
1696            this.missingLocalizedStrings[string] = true;
1697        }
1698
1699        if (Preferences.showMissingLocalizedStrings)
1700            string += " (not localized)";
1701    }
1702
1703    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1704}
1705
1706WebInspector.isMac = function()
1707{
1708    if (!("_isMac" in this))
1709        this._isMac = WebInspector.platform === "mac";
1710
1711    return this._isMac;
1712}
1713
1714WebInspector.isBeingEdited = function(element)
1715{
1716    return element.__editing;
1717}
1718
1719WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context, multiline)
1720{
1721    if (element.__editing)
1722        return;
1723    element.__editing = true;
1724
1725    var oldText = getContent(element);
1726    var moveDirection = "";
1727
1728    element.addStyleClass("editing");
1729
1730    var oldTabIndex = element.tabIndex;
1731    if (element.tabIndex < 0)
1732        element.tabIndex = 0;
1733
1734    function blurEventListener() {
1735        editingCommitted.call(element);
1736    }
1737
1738    function getContent(element) {
1739        if (element.tagName === "INPUT" && element.type === "text")
1740            return element.value;
1741        else
1742            return element.textContent;
1743    }
1744
1745    function cleanUpAfterEditing() {
1746        delete this.__editing;
1747
1748        this.removeStyleClass("editing");
1749        this.tabIndex = oldTabIndex;
1750        this.scrollTop = 0;
1751        this.scrollLeft = 0;
1752
1753        element.removeEventListener("blur", blurEventListener, false);
1754        element.removeEventListener("keydown", keyDownEventListener, true);
1755
1756        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1757            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1758    }
1759
1760    function editingCancelled() {
1761        if (this.tagName === "INPUT" && this.type === "text")
1762            this.value = oldText;
1763        else
1764            this.textContent = oldText;
1765
1766        cleanUpAfterEditing.call(this);
1767
1768        if (cancelledCallback)
1769            cancelledCallback(this, context);
1770    }
1771
1772    function editingCommitted() {
1773        cleanUpAfterEditing.call(this);
1774
1775        if (committedCallback)
1776            committedCallback(this, getContent(this), oldText, context, moveDirection);
1777    }
1778
1779    function keyDownEventListener(event) {
1780        var isMetaOrCtrl = WebInspector.isMac() ?
1781            event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
1782            event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
1783        if (isEnterKey(event) && (!multiline || isMetaOrCtrl)) {
1784            editingCommitted.call(element);
1785            event.preventDefault();
1786            event.stopPropagation();
1787        } else if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Esc) {
1788            editingCancelled.call(element);
1789            event.preventDefault();
1790            event.stopPropagation();
1791        } else if (event.keyIdentifier === "U+0009") // Tab key
1792            moveDirection = (event.shiftKey ? "backward" : "forward");
1793    }
1794
1795    element.addEventListener("blur", blurEventListener, false);
1796    element.addEventListener("keydown", keyDownEventListener, true);
1797
1798    WebInspector.currentFocusElement = element;
1799}
1800
1801WebInspector._toolbarItemClicked = function(event)
1802{
1803    var toolbarItem = event.currentTarget;
1804    this.currentPanel = toolbarItem.panel;
1805}
1806
1807// This table maps MIME types to the Resource.Types which are valid for them.
1808// The following line:
1809//    "text/html":                {0: 1},
1810// means that text/html is a valid MIME type for resources that have type
1811// WebInspector.Resource.Type.Document (which has a value of 0).
1812WebInspector.MIMETypes = {
1813    "text/html":                   {0: true},
1814    "text/xml":                    {0: true},
1815    "text/plain":                  {0: true},
1816    "application/xhtml+xml":       {0: true},
1817    "text/css":                    {1: true},
1818    "text/xsl":                    {1: true},
1819    "image/jpeg":                  {2: true},
1820    "image/png":                   {2: true},
1821    "image/gif":                   {2: true},
1822    "image/bmp":                   {2: true},
1823    "image/vnd.microsoft.icon":    {2: true},
1824    "image/x-icon":                {2: true},
1825    "image/x-xbitmap":             {2: true},
1826    "font/ttf":                    {3: true},
1827    "font/opentype":               {3: true},
1828    "application/x-font-type1":    {3: true},
1829    "application/x-font-ttf":      {3: true},
1830    "application/x-truetype-font": {3: true},
1831    "text/javascript":             {4: true},
1832    "text/ecmascript":             {4: true},
1833    "application/javascript":      {4: true},
1834    "application/ecmascript":      {4: true},
1835    "application/x-javascript":    {4: true},
1836    "text/javascript1.1":          {4: true},
1837    "text/javascript1.2":          {4: true},
1838    "text/javascript1.3":          {4: true},
1839    "text/jscript":                {4: true},
1840    "text/livescript":             {4: true},
1841}
1842