• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30var Preferences = {
31    ignoreWhitespace: true,
32    showUserAgentStyles: true,
33    maxInlineTextChildLength: 80,
34    minConsoleHeight: 75,
35    minSidebarWidth: 100,
36    minElementsSidebarWidth: 200,
37    minScriptsSidebarWidth: 200,
38    showInheritedComputedStyleProperties: false,
39    styleRulesExpandedState: {},
40    showMissingLocalizedStrings: false
41}
42
43var WebInspector = {
44    resources: [],
45    resourceURLMap: {},
46    missingLocalizedStrings: {},
47
48    get previousFocusElement()
49    {
50        return this._previousFocusElement;
51    },
52
53    get currentFocusElement()
54    {
55        return this._currentFocusElement;
56    },
57
58    set currentFocusElement(x)
59    {
60        if (this._currentFocusElement !== x)
61            this._previousFocusElement = this._currentFocusElement;
62        this._currentFocusElement = x;
63
64        if (this._currentFocusElement) {
65            this._currentFocusElement.focus();
66
67            // Make a caret selection inside the new element if there isn't a range selection and
68            // there isn't already a caret selection inside.
69            var selection = window.getSelection();
70            if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
71                var selectionRange = document.createRange();
72                selectionRange.setStart(this._currentFocusElement, 0);
73                selectionRange.setEnd(this._currentFocusElement, 0);
74
75                selection.removeAllRanges();
76                selection.addRange(selectionRange);
77            }
78        } else if (this._previousFocusElement)
79            this._previousFocusElement.blur();
80    },
81
82    get currentPanel()
83    {
84        return this._currentPanel;
85    },
86
87    set currentPanel(x)
88    {
89        if (this._currentPanel === x)
90            return;
91
92        if (this._currentPanel)
93            this._currentPanel.hide();
94
95        this._currentPanel = x;
96
97        this.updateSearchLabel();
98
99        if (x) {
100            x.show();
101
102            if (this.currentQuery) {
103                if (x.performSearch) {
104                    function performPanelSearch()
105                    {
106                        this.updateSearchMatchesCount();
107
108                        x.currentQuery = this.currentQuery;
109                        x.performSearch(this.currentQuery);
110                    }
111
112                    // Perform the search on a timeout so the panel switches fast.
113                    setTimeout(performPanelSearch.bind(this), 0);
114                } else {
115                    // Update to show Not found for panels that can't be searched.
116                    this.updateSearchMatchesCount();
117                }
118            }
119        }
120    },
121
122    get attached()
123    {
124        return this._attached;
125    },
126
127    set attached(x)
128    {
129        if (this._attached === x)
130            return;
131
132        this._attached = x;
133
134        this.updateSearchLabel();
135
136        var dockToggleButton = document.getElementById("dock-status-bar-item");
137        var body = document.body;
138
139        if (x) {
140            InspectorController.attach();
141            body.removeStyleClass("detached");
142            body.addStyleClass("attached");
143            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
144        } else {
145            InspectorController.detach();
146            body.removeStyleClass("attached");
147            body.addStyleClass("detached");
148            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
149        }
150    },
151
152    get errors()
153    {
154        return this._errors || 0;
155    },
156
157    set errors(x)
158    {
159        x = Math.max(x, 0);
160
161        if (this._errors === x)
162            return;
163        this._errors = x;
164        this._updateErrorAndWarningCounts();
165    },
166
167    get warnings()
168    {
169        return this._warnings || 0;
170    },
171
172    set warnings(x)
173    {
174        x = Math.max(x, 0);
175
176        if (this._warnings === x)
177            return;
178        this._warnings = x;
179        this._updateErrorAndWarningCounts();
180    },
181
182    _updateErrorAndWarningCounts: function()
183    {
184        var errorWarningElement = document.getElementById("error-warning-count");
185        if (!errorWarningElement)
186            return;
187
188        if (!this.errors && !this.warnings) {
189            errorWarningElement.addStyleClass("hidden");
190            return;
191        }
192
193        errorWarningElement.removeStyleClass("hidden");
194
195        errorWarningElement.removeChildren();
196
197        if (this.errors) {
198            var errorElement = document.createElement("span");
199            errorElement.id = "error-count";
200            errorElement.textContent = this.errors;
201            errorWarningElement.appendChild(errorElement);
202        }
203
204        if (this.warnings) {
205            var warningsElement = document.createElement("span");
206            warningsElement.id = "warning-count";
207            warningsElement.textContent = this.warnings;
208            errorWarningElement.appendChild(warningsElement);
209        }
210
211        if (this.errors) {
212            if (this.warnings) {
213                if (this.errors == 1) {
214                    if (this.warnings == 1)
215                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
216                    else
217                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
218                } else if (this.warnings == 1)
219                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
220                else
221                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
222            } else if (this.errors == 1)
223                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
224            else
225                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
226        } else if (this.warnings == 1)
227            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
228        else if (this.warnings)
229            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
230        else
231            errorWarningElement.title = null;
232    },
233
234    get hoveredDOMNode()
235    {
236        return this._hoveredDOMNode;
237    },
238
239    set hoveredDOMNode(x)
240    {
241        if (objectsAreSame(this._hoveredDOMNode, x))
242            return;
243
244        this._hoveredDOMNode = x;
245
246        if (this._hoveredDOMNode)
247            this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
248        else
249            this._updateHoverHighlight();
250    },
251
252    _updateHoverHighlightSoon: function(delay)
253    {
254        if ("_updateHoverHighlightTimeout" in this)
255            clearTimeout(this._updateHoverHighlightTimeout);
256        this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
257    },
258
259    _updateHoverHighlight: function()
260    {
261        if ("_updateHoverHighlightTimeout" in this) {
262            clearTimeout(this._updateHoverHighlightTimeout);
263            delete this._updateHoverHighlightTimeout;
264        }
265
266        if (this._hoveredDOMNode) {
267            InspectorController.highlightDOMNode(this._hoveredDOMNode);
268            this.showingDOMNodeHighlight = true;
269        } else {
270            InspectorController.hideDOMNodeHighlight();
271            this.showingDOMNodeHighlight = false;
272        }
273    }
274}
275
276WebInspector.loaded = function()
277{
278    var platform = InspectorController.platform();
279    document.body.addStyleClass("platform-" + platform);
280
281    this.console = new WebInspector.Console();
282    this.panels = {
283        elements: new WebInspector.ElementsPanel(),
284        resources: new WebInspector.ResourcesPanel(),
285        scripts: new WebInspector.ScriptsPanel(),
286        profiles: new WebInspector.ProfilesPanel(),
287        databases: new WebInspector.DatabasesPanel()
288    };
289
290    var toolbarElement = document.getElementById("toolbar");
291    var previousToolbarItem = toolbarElement.children[0];
292
293    for (var panelName in this.panels) {
294        var panel = this.panels[panelName];
295        var panelToolbarItem = panel.toolbarItem;
296        panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
297        if (previousToolbarItem)
298            toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
299        else
300            toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
301        previousToolbarItem = panelToolbarItem;
302    }
303
304    this.currentPanel = this.panels.elements;
305
306    this.resourceCategories = {
307        documents: new WebInspector.ResourceCategory(WebInspector.UIString("Documents"), "documents"),
308        stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("Stylesheets"), "stylesheets"),
309        images: new WebInspector.ResourceCategory(WebInspector.UIString("Images"), "images"),
310        scripts: new WebInspector.ResourceCategory(WebInspector.UIString("Scripts"), "scripts"),
311        xhr: new WebInspector.ResourceCategory(WebInspector.UIString("XHR"), "xhr"),
312        fonts: new WebInspector.ResourceCategory(WebInspector.UIString("Fonts"), "fonts"),
313        other: new WebInspector.ResourceCategory(WebInspector.UIString("Other"), "other")
314    };
315
316    this.Tips = {
317        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
318    };
319
320    this.Warnings = {
321        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
322    };
323
324    this.addMainEventListeners(document);
325
326    window.addEventListener("unload", this.windowUnload.bind(this), true);
327    window.addEventListener("resize", this.windowResize.bind(this), true);
328
329    document.addEventListener("focus", this.focusChanged.bind(this), true);
330    document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
331    document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
332    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
333    document.addEventListener("copy", this.documentCopy.bind(this), true);
334
335    var mainPanelsElement = document.getElementById("main-panels");
336    mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
337    mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
338    mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);
339
340    // Focus the mainPanelsElement in a timeout so it happens after the initial focus,
341    // so it doesn't get reset to the first toolbar button. This initial focus happens
342    // on Mac when the window is made key and the WebHTMLView becomes the first responder.
343    setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);
344
345    var dockToggleButton = document.getElementById("dock-status-bar-item");
346    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
347
348    if (this.attached)
349        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
350    else
351        dockToggleButton.title = WebInspector.UIString("Dock to main window.");
352
353    var errorWarningCount = document.getElementById("error-warning-count");
354    errorWarningCount.addEventListener("click", this.console.show.bind(this.console), false);
355    this._updateErrorAndWarningCounts();
356
357    var searchField = document.getElementById("search");
358    searchField.addEventListener("keydown", this.searchKeyDown.bind(this), false);
359    searchField.addEventListener("keyup", this.searchKeyUp.bind(this), false);
360    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
361
362    document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true);
363    document.getElementById("close-button").addEventListener("click", this.close, true);
364
365    InspectorController.loaded();
366}
367
368var windowLoaded = function()
369{
370    var localizedStringsURL = InspectorController.localizedStringsURL();
371    if (localizedStringsURL) {
372        var localizedStringsScriptElement = document.createElement("script");
373        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
374        localizedStringsScriptElement.type = "text/javascript";
375        localizedStringsScriptElement.src = localizedStringsURL;
376        document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement);
377    } else
378        WebInspector.loaded();
379
380    window.removeEventListener("load", windowLoaded, false);
381    delete windowLoaded;
382};
383
384window.addEventListener("load", windowLoaded, false);
385
386WebInspector.windowUnload = function(event)
387{
388    InspectorController.windowUnloading();
389}
390
391WebInspector.windowResize = function(event)
392{
393    if (this.currentPanel && this.currentPanel.resize)
394        this.currentPanel.resize();
395}
396
397WebInspector.windowFocused = function(event)
398{
399    if (event.target.nodeType === Node.DOCUMENT_NODE)
400        document.body.removeStyleClass("inactive");
401}
402
403WebInspector.windowBlured = function(event)
404{
405    if (event.target.nodeType === Node.DOCUMENT_NODE)
406        document.body.addStyleClass("inactive");
407}
408
409WebInspector.focusChanged = function(event)
410{
411    this.currentFocusElement = event.target;
412}
413
414WebInspector.setAttachedWindow = function(attached)
415{
416    this.attached = attached;
417}
418
419WebInspector.close = function(event)
420{
421    InspectorController.closeWindow();
422}
423
424WebInspector.documentClick = function(event)
425{
426    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
427    if (!anchor)
428        return;
429
430    // Prevent the link from navigating, since we don't do any navigation by following links normally.
431    event.preventDefault();
432
433    function followLink()
434    {
435        // FIXME: support webkit-html-external-link links here.
436        if (anchor.href in WebInspector.resourceURLMap) {
437            if (anchor.hasStyleClass("webkit-html-external-link")) {
438                anchor.removeStyleClass("webkit-html-external-link");
439                anchor.addStyleClass("webkit-html-resource-link");
440            }
441
442            WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
443        } else {
444            var profileStringRegEx = new RegExp("webkit-profile://.+/([0-9]+)");
445            var profileString = profileStringRegEx.exec(anchor.href);
446            if (profileString)
447                WebInspector.showProfileById(profileString[1])
448        }
449    }
450
451    if (WebInspector.followLinkTimeout)
452        clearTimeout(WebInspector.followLinkTimeout);
453
454    if (anchor.preventFollowOnDoubleClick) {
455        // Start a timeout if this is the first click, if the timeout is canceled
456        // before it fires, then a double clicked happened or another link was clicked.
457        if (event.detail === 1)
458            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
459        return;
460    }
461
462    followLink();
463}
464
465WebInspector.documentKeyDown = function(event)
466{
467    if (!this.currentFocusElement)
468        return;
469    if (this.currentFocusElement.handleKeyEvent)
470        this.currentFocusElement.handleKeyEvent(event);
471    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
472        WebInspector[this.currentFocusElement.id + "KeyDown"](event);
473
474    if (!event.handled) {
475        var isMac = InspectorController.platform().indexOf("mac-") === 0;
476
477        switch (event.keyIdentifier) {
478            case "U+001B": // Escape key
479                this.console.visible = !this.console.visible;
480                event.preventDefault();
481                break;
482
483            case "U+0046": // F key
484                if (isMac)
485                    var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
486                else
487                    var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
488
489                if (isFindKey) {
490                    var searchField = document.getElementById("search");
491                    searchField.focus();
492                    searchField.select();
493                    event.preventDefault();
494                }
495
496                break;
497
498            case "U+0047": // G key
499                if (isMac)
500                    var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
501                else
502                    var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;
503
504                if (isFindAgainKey) {
505                    if (event.shiftKey) {
506                        if (this.currentPanel.jumpToPreviousSearchResult)
507                            this.currentPanel.jumpToPreviousSearchResult();
508                    } else if (this.currentPanel.jumpToNextSearchResult)
509                        this.currentPanel.jumpToNextSearchResult();
510                    event.preventDefault();
511                }
512
513                break;
514        }
515    }
516}
517
518WebInspector.documentKeyUp = function(event)
519{
520    if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
521        return;
522    this.currentFocusElement.handleKeyUpEvent(event);
523}
524
525WebInspector.documentCanCopy = function(event)
526{
527    if (!this.currentFocusElement)
528        return;
529    // Calling preventDefault() will say "we support copying, so enable the Copy menu".
530    if (this.currentFocusElement.handleCopyEvent)
531        event.preventDefault();
532    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
533        event.preventDefault();
534}
535
536WebInspector.documentCopy = function(event)
537{
538    if (!this.currentFocusElement)
539        return;
540    if (this.currentFocusElement.handleCopyEvent)
541        this.currentFocusElement.handleCopyEvent(event);
542    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
543        WebInspector[this.currentFocusElement.id + "Copy"](event);
544}
545
546WebInspector.mainKeyDown = function(event)
547{
548    if (this.currentPanel && this.currentPanel.handleKeyEvent)
549        this.currentPanel.handleKeyEvent(event);
550}
551
552WebInspector.mainKeyUp = function(event)
553{
554    if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
555        this.currentPanel.handleKeyUpEvent(event);
556}
557
558WebInspector.mainCopy = function(event)
559{
560    if (this.currentPanel && this.currentPanel.handleCopyEvent)
561        this.currentPanel.handleCopyEvent(event);
562}
563
564WebInspector.animateStyle = function(animations, duration, callback, complete)
565{
566    if (complete === undefined)
567        complete = 0;
568    var slice = (1000 / 30); // 30 frames per second
569
570    var defaultUnit = "px";
571    var propertyUnit = {opacity: ""};
572
573    for (var i = 0; i < animations.length; ++i) {
574        var animation = animations[i];
575        var element = null;
576        var start = null;
577        var current = null;
578        var end = null;
579        for (key in animation) {
580            if (key === "element")
581                element = animation[key];
582            else if (key === "start")
583                start = animation[key];
584            else if (key === "current")
585                current = animation[key];
586            else if (key === "end")
587                end = animation[key];
588        }
589
590        if (!element || !end)
591            continue;
592
593        var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
594        if (!start) {
595            start = {};
596            for (key in end)
597                start[key] = parseInt(computedStyle.getPropertyValue(key));
598            animation.start = start;
599        } else if (complete == 0)
600            for (key in start)
601                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
602
603        if (!current) {
604            current = {};
605            for (key in start)
606                current[key] = start[key];
607            animation.current = current;
608        }
609
610        function cubicInOut(t, b, c, d)
611        {
612            if ((t/=d/2) < 1) return c/2*t*t*t + b;
613            return c/2*((t-=2)*t*t + 2) + b;
614        }
615
616        var style = element.style;
617        for (key in end) {
618            var startValue = start[key];
619            var currentValue = current[key];
620            var endValue = end[key];
621            if ((complete + slice) < duration) {
622                var delta = (endValue - startValue) / (duration / slice);
623                var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
624                style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
625                current[key] = newValue;
626            } else {
627                style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
628            }
629        }
630    }
631
632    if (complete < duration)
633        setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice);
634    else if (callback)
635        callback();
636}
637
638WebInspector.updateSearchLabel = function()
639{
640    if (!this.currentPanel)
641        return;
642
643    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
644    if (this.attached)
645        document.getElementById("search").setAttribute("placeholder", newLabel);
646    else {
647        document.getElementById("search").removeAttribute("placeholder");
648        document.getElementById("search-toolbar-label").textContent = newLabel;
649    }
650}
651
652WebInspector.toggleAttach = function()
653{
654    this.attached = !this.attached;
655}
656
657WebInspector.toolbarDragStart = function(event)
658{
659    if (!WebInspector.attached && InspectorController.platform() !== "mac-leopard")
660        return;
661
662    var target = event.target;
663    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
664        return;
665
666    var toolbar = document.getElementById("toolbar");
667    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
668        return;
669
670    toolbar.lastScreenX = event.screenX;
671    toolbar.lastScreenY = event.screenY;
672
673    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
674}
675
676WebInspector.toolbarDragEnd = function(event)
677{
678    var toolbar = document.getElementById("toolbar");
679
680    WebInspector.elementDragEnd(event);
681
682    delete toolbar.lastScreenX;
683    delete toolbar.lastScreenY;
684}
685
686WebInspector.toolbarDrag = function(event)
687{
688    var toolbar = document.getElementById("toolbar");
689
690    if (WebInspector.attached) {
691        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);
692
693        InspectorController.setAttachedWindowHeight(height);
694    } else {
695        var x = event.screenX - toolbar.lastScreenX;
696        var y = event.screenY - toolbar.lastScreenY;
697
698        // We cannot call window.moveBy here because it restricts the movement
699        // of the window at the edges.
700        InspectorController.moveByUnrestricted(x, y);
701    }
702
703    toolbar.lastScreenX = event.screenX;
704    toolbar.lastScreenY = event.screenY;
705
706    event.preventDefault();
707}
708
709WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
710{
711    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
712        this.elementDragEnd(event);
713
714    this._elementDraggingEventListener = dividerDrag;
715    this._elementEndDraggingEventListener = elementDragEnd;
716
717    document.addEventListener("mousemove", dividerDrag, true);
718    document.addEventListener("mouseup", elementDragEnd, true);
719
720    document.body.style.cursor = cursor;
721
722    event.preventDefault();
723}
724
725WebInspector.elementDragEnd = function(event)
726{
727    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
728    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
729
730    document.body.style.removeProperty("cursor");
731
732    delete this._elementDraggingEventListener;
733    delete this._elementEndDraggingEventListener;
734
735    event.preventDefault();
736}
737
738WebInspector.showConsole = function()
739{
740    this.console.show();
741}
742
743WebInspector.showElementsPanel = function()
744{
745    this.currentPanel = this.panels.elements;
746}
747
748WebInspector.showResourcesPanel = function()
749{
750    this.currentPanel = this.panels.resources;
751}
752
753WebInspector.showScriptsPanel = function()
754{
755    this.currentPanel = this.panels.scripts;
756}
757
758WebInspector.showProfilesPanel = function()
759{
760    this.currentPanel = this.panels.profiles;
761}
762
763WebInspector.showDatabasesPanel = function()
764{
765    this.currentPanel = this.panels.databases;
766}
767
768WebInspector.addResource = function(resource)
769{
770    this.resources.push(resource);
771    this.resourceURLMap[resource.url] = resource;
772
773    if (resource.mainResource) {
774        this.mainResource = resource;
775        this.panels.elements.reset();
776    }
777
778    if (this.panels.resources)
779        this.panels.resources.addResource(resource);
780}
781
782WebInspector.removeResource = function(resource)
783{
784    resource.category.removeResource(resource);
785    delete this.resourceURLMap[resource.url];
786
787    this.resources.remove(resource, true);
788
789    if (this.panels.resources)
790        this.panels.resources.removeResource(resource);
791}
792
793WebInspector.addDatabase = function(database)
794{
795    this.panels.databases.addDatabase(database);
796}
797
798WebInspector.debuggerWasEnabled = function()
799{
800    this.panels.scripts.debuggerWasEnabled();
801}
802
803WebInspector.debuggerWasDisabled = function()
804{
805    this.panels.scripts.debuggerWasDisabled();
806}
807
808WebInspector.profilerWasEnabled = function()
809{
810    this.panels.profiles.profilerWasEnabled();
811}
812
813WebInspector.profilerWasDisabled = function()
814{
815    this.panels.profiles.profilerWasDisabled();
816}
817
818WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
819{
820    this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
821}
822
823WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
824{
825    this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
826}
827
828WebInspector.pausedScript = function()
829{
830    this.panels.scripts.debuggerPaused();
831}
832
833WebInspector.populateInterface = function()
834{
835    for (var panelName in this.panels) {
836        var panel = this.panels[panelName];
837        if ("populateInterface" in panel)
838            panel.populateInterface();
839    }
840}
841
842WebInspector.reset = function()
843{
844    for (var panelName in this.panels) {
845        var panel = this.panels[panelName];
846        if ("reset" in panel)
847            panel.reset();
848    }
849
850    for (var category in this.resourceCategories)
851        this.resourceCategories[category].removeAllResources();
852
853    this.resources = [];
854    this.resourceURLMap = {};
855    this.hoveredDOMNode = null;
856
857    delete this.mainResource;
858
859    this.console.clearMessages();
860}
861
862WebInspector.inspectedWindowCleared = function(inspectedWindow)
863{
864    this.panels.elements.inspectedWindowCleared(inspectedWindow);
865}
866
867WebInspector.resourceURLChanged = function(resource, oldURL)
868{
869    delete this.resourceURLMap[oldURL];
870    this.resourceURLMap[resource.url] = resource;
871}
872
873WebInspector.addMessageToConsole = function(msg)
874{
875    this.console.addMessage(msg);
876}
877
878WebInspector.addProfile = function(profile)
879{
880    this.panels.profiles.addProfile(profile);
881}
882
883WebInspector.setRecordingProfile = function(isProfiling)
884{
885    this.panels.profiles.setRecordingProfile(isProfiling);
886}
887
888WebInspector.drawLoadingPieChart = function(canvas, percent) {
889    var g = canvas.getContext("2d");
890    var darkColor = "rgb(122, 168, 218)";
891    var lightColor = "rgb(228, 241, 251)";
892    var cx = 8;
893    var cy = 8;
894    var r = 7;
895
896    g.beginPath();
897    g.arc(cx, cy, r, 0, Math.PI * 2, false);
898    g.closePath();
899
900    g.lineWidth = 1;
901    g.strokeStyle = darkColor;
902    g.fillStyle = lightColor;
903    g.fill();
904    g.stroke();
905
906    var startangle = -Math.PI / 2;
907    var endangle = startangle + (percent * Math.PI * 2);
908
909    g.beginPath();
910    g.moveTo(cx, cy);
911    g.arc(cx, cy, r, startangle, endangle, false);
912    g.closePath();
913
914    g.fillStyle = darkColor;
915    g.fill();
916}
917
918WebInspector.updateFocusedNode = function(node)
919{
920    if (!node)
921        // FIXME: Should we deselect if null is passed in?
922        return;
923
924    this.currentPanel = this.panels.elements;
925    this.panels.elements.focusedDOMNode = node;
926}
927
928WebInspector.displayNameForURL = function(url)
929{
930    if (!url)
931        return "";
932    var resource = this.resourceURLMap[url];
933    if (resource)
934        return resource.displayName;
935    return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
936}
937
938WebInspector.resourceForURL = function(url)
939{
940    if (url in this.resourceURLMap)
941        return this.resourceURLMap[url];
942
943    // No direct match found. Search for resources that contain
944    // a substring of the URL.
945    for (var resourceURL in this.resourceURLMap) {
946        if (resourceURL.hasSubstring(url))
947            return this.resourceURLMap[resourceURL];
948    }
949
950    return null;
951}
952
953WebInspector.showResourceForURL = function(url, line, preferredPanel)
954{
955    var resource = this.resourceForURL(url);
956    if (!resource)
957        return false;
958
959    if (preferredPanel && preferredPanel in WebInspector.panels) {
960        var panel = this.panels[preferredPanel];
961        if (!("showResource" in panel))
962            panel = null;
963        else if ("canShowResource" in panel && !panel.canShowResource(resource))
964            panel = null;
965    }
966
967    this.currentPanel = panel || this.panels.resources;
968    if (!this.currentPanel)
969        return false;
970    this.currentPanel.showResource(resource, line);
971    return true;
972}
973
974WebInspector.linkifyStringAsFragment = function(string)
975{
976    var container = document.createDocumentFragment();
977    var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]");
978
979    while (string) {
980        var linkString = linkStringRegEx.exec(string);
981        if (!linkString)
982            break;
983
984        linkString = linkString[0];
985        var title = linkString;
986        var linkIndex = string.indexOf(linkString);
987        var nonLink = string.substring(0, linkIndex);
988        container.appendChild(document.createTextNode(nonLink));
989
990        var profileStringRegEx = new RegExp("webkit-profile://(.+)/[0-9]+");
991        var profileStringMatches = profileStringRegEx.exec(title);
992        var profileTitle;
993        if (profileStringMatches)
994            profileTitle = profileStringMatches[1];
995        if (profileTitle)
996            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileTitle);
997
998        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
999        container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
1000        string = string.substring(linkIndex + linkString.length, string.length);
1001    }
1002
1003    if (string)
1004        container.appendChild(document.createTextNode(string));
1005
1006    return container;
1007}
1008
1009WebInspector.showProfileById = function(uid) {
1010    WebInspector.showProfilesPanel();
1011    WebInspector.panels.profiles.showProfileById(uid);
1012}
1013
1014WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal)
1015{
1016    if (!linkText)
1017        linkText = url;
1018    classes = (classes ? classes + " " : "");
1019    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1020
1021    var a = document.createElement("a");
1022    a.href = url;
1023    a.className = classes;
1024    a.title = url;
1025    a.target = "_blank";
1026    a.textContent = linkText;
1027
1028    return a;
1029}
1030
1031WebInspector.linkifyURL = function(url, linkText, classes, isExternal)
1032{
1033    // Use the DOM version of this function so as to avoid needing to escape attributes.
1034    // FIXME:  Get rid of linkifyURL entirely.
1035    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal).outerHTML;
1036}
1037
1038WebInspector.addMainEventListeners = function(doc)
1039{
1040    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), true);
1041    doc.defaultView.addEventListener("blur", this.windowBlured.bind(this), true);
1042    doc.addEventListener("click", this.documentClick.bind(this), true);
1043}
1044
1045WebInspector.searchKeyDown = function(event)
1046{
1047    if (event.keyIdentifier !== "Enter")
1048        return;
1049
1050    // Call preventDefault since this was the Enter key. This prevents a "search" event
1051    // from firing for key down. We handle the Enter key on key up in searchKeyUp. This
1052    // stops performSearch from being called twice in a row.
1053    event.preventDefault();
1054}
1055
1056WebInspector.searchKeyUp = function(event)
1057{
1058    if (event.keyIdentifier !== "Enter")
1059        return;
1060
1061    // Select all of the text so the user can easily type an entirely new query.
1062    event.target.select();
1063
1064    // Only call performSearch if the Enter key was pressed. Otherwise the search
1065    // performance is poor because of searching on every key. The search field has
1066    // the incremental attribute set, so we still get incremental searches.
1067    this.performSearch(event);
1068}
1069
1070WebInspector.performSearch = function(event)
1071{
1072    var query = event.target.value;
1073    var forceSearch = event.keyIdentifier === "Enter";
1074
1075    if (!query || !query.length || (!forceSearch && query.length < 3)) {
1076        delete this.currentQuery;
1077
1078        for (var panelName in this.panels) {
1079            var panel = this.panels[panelName];
1080            if (panel.currentQuery && panel.searchCanceled)
1081                panel.searchCanceled();
1082            delete panel.currentQuery;
1083        }
1084
1085        this.updateSearchMatchesCount();
1086
1087        return;
1088    }
1089
1090    if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
1091        // When this is the same query and a forced search, jump to the next
1092        // search result for a good user experience.
1093        if (forceSearch && this.currentPanel.jumpToNextSearchResult)
1094            this.currentPanel.jumpToNextSearchResult();
1095        return;
1096    }
1097
1098    this.currentQuery = query;
1099
1100    this.updateSearchMatchesCount();
1101
1102    if (!this.currentPanel.performSearch)
1103        return;
1104
1105    this.currentPanel.currentQuery = query;
1106    this.currentPanel.performSearch(query);
1107}
1108
1109WebInspector.updateSearchMatchesCount = function(matches, panel)
1110{
1111    if (!panel)
1112        panel = this.currentPanel;
1113
1114    panel.currentSearchMatches = matches;
1115
1116    if (panel !== this.currentPanel)
1117        return;
1118
1119    if (!this.currentPanel.currentQuery) {
1120        document.getElementById("search-results-matches").addStyleClass("hidden");
1121        return;
1122    }
1123
1124    if (matches) {
1125        if (matches === 1)
1126            var matchesString = WebInspector.UIString("1 match");
1127        else
1128            var matchesString = WebInspector.UIString("%d matches", matches);
1129    } else
1130        var matchesString = WebInspector.UIString("Not Found");
1131
1132    var matchesToolbarElement = document.getElementById("search-results-matches");
1133    matchesToolbarElement.removeStyleClass("hidden");
1134    matchesToolbarElement.textContent = matchesString;
1135}
1136
1137WebInspector.UIString = function(string)
1138{
1139    if (window.localizedStrings && string in window.localizedStrings)
1140        string = window.localizedStrings[string];
1141    else {
1142        if (!(string in this.missingLocalizedStrings)) {
1143            console.error("Localized string \"" + string + "\" not found.");
1144            this.missingLocalizedStrings[string] = true;
1145        }
1146
1147        if (Preferences.showMissingLocalizedStrings)
1148            string += " (not localized)";
1149    }
1150
1151    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1152}
1153
1154WebInspector.isBeingEdited = function(element)
1155{
1156    return element.__editing;
1157}
1158
1159WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
1160{
1161    if (element.__editing)
1162        return;
1163    element.__editing = true;
1164
1165    var oldText = element.textContent;
1166    var oldHandleKeyEvent = element.handleKeyEvent;
1167
1168    element.addStyleClass("editing");
1169
1170    var oldTabIndex = element.tabIndex;
1171    if (element.tabIndex < 0)
1172        element.tabIndex = 0;
1173
1174    function blurEventListener() {
1175        editingCommitted.call(element);
1176    }
1177
1178    function cleanUpAfterEditing() {
1179        delete this.__editing;
1180
1181        this.removeStyleClass("editing");
1182        this.tabIndex = oldTabIndex;
1183        this.scrollTop = 0;
1184        this.scrollLeft = 0;
1185
1186        this.handleKeyEvent = oldHandleKeyEvent;
1187        element.removeEventListener("blur", blurEventListener, false);
1188
1189        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1190            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1191    }
1192
1193    function editingCancelled() {
1194        this.innerText = oldText;
1195
1196        cleanUpAfterEditing.call(this);
1197
1198        cancelledCallback(this, context);
1199    }
1200
1201    function editingCommitted() {
1202        cleanUpAfterEditing.call(this);
1203
1204        committedCallback(this, this.textContent, oldText, context);
1205    }
1206
1207    element.handleKeyEvent = function(event) {
1208        if (oldHandleKeyEvent)
1209            oldHandleKeyEvent(event);
1210        if (event.handled)
1211            return;
1212
1213        if (event.keyIdentifier === "Enter") {
1214            editingCommitted.call(element);
1215            event.preventDefault();
1216        } else if (event.keyCode === 27) { // Escape key
1217            editingCancelled.call(element);
1218            event.preventDefault();
1219            event.handled = true;
1220        }
1221    }
1222
1223    element.addEventListener("blur", blurEventListener, false);
1224
1225    WebInspector.currentFocusElement = element;
1226}
1227
1228WebInspector._toolbarItemClicked = function(event)
1229{
1230    var toolbarItem = event.currentTarget;
1231    this.currentPanel = toolbarItem.panel;
1232}
1233
1234// This table maps MIME types to the Resource.Types which are valid for them.
1235// The following line:
1236//    "text/html":                {0: 1},
1237// means that text/html is a valid MIME type for resources that have type
1238// WebInspector.Resource.Type.Document (which has a value of 0).
1239WebInspector.MIMETypes = {
1240    "text/html":                   {0: true},
1241    "text/xml":                    {0: true},
1242    "text/plain":                  {0: true},
1243    "application/xhtml+xml":       {0: true},
1244    "text/css":                    {1: true},
1245    "text/xsl":                    {1: true},
1246    "image/jpeg":                  {2: true},
1247    "image/png":                   {2: true},
1248    "image/gif":                   {2: true},
1249    "image/bmp":                   {2: true},
1250    "image/x-icon":                {2: true},
1251    "image/x-xbitmap":             {2: true},
1252    "font/ttf":                    {3: true},
1253    "font/opentype":               {3: true},
1254    "application/x-font-type1":    {3: true},
1255    "application/x-font-ttf":      {3: true},
1256    "application/x-truetype-font": {3: true},
1257    "text/javascript":             {4: true},
1258    "text/ecmascript":             {4: true},
1259    "application/javascript":      {4: true},
1260    "application/ecmascript":      {4: true},
1261    "application/x-javascript":    {4: true},
1262    "text/javascript1.1":          {4: true},
1263    "text/javascript1.2":          {4: true},
1264    "text/javascript1.3":          {4: true},
1265    "text/jscript":                {4: true},
1266    "text/livescript":             {4: true},
1267}
1268