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