/* utilities.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

Object.properties = function(obj)
{
    var properties = [];
    for (var prop in obj)
        properties.push(prop);
    return properties;
}

Object.sortedProperties = function(obj, sortFunc)
{
    return Object.properties(obj).sort(sortFunc);
}

Function.prototype.bind = function(thisObject)
{
    var func = this;
    var args = Array.prototype.slice.call(arguments, 1);
    function bound()
    {
        return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
    }
    bound.toString = function() {
        return "bound: " + func;
    };
    return bound;
}

Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
{
    var startNode;
    var startOffset = 0;
    var endNode;
    var endOffset = 0;

    if (!stayWithinNode)
        stayWithinNode = this;

    if (!direction || direction === "backward" || direction === "both") {
        var node = this;
        while (node) {
            if (node === stayWithinNode) {
                if (!startNode)
                    startNode = stayWithinNode;
                break;
            }

            if (node.nodeType === Node.TEXT_NODE) {
                var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
                for (var i = start; i >= 0; --i) {
                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
                        startNode = node;
                        startOffset = i + 1;
                        break;
                    }
                }
            }

            if (startNode)
                break;

            node = node.traversePreviousNode(stayWithinNode);
        }

        if (!startNode) {
            startNode = stayWithinNode;
            startOffset = 0;
        }
    } else {
        startNode = this;
        startOffset = offset;
    }

    if (!direction || direction === "forward" || direction === "both") {
        node = this;
        while (node) {
            if (node === stayWithinNode) {
                if (!endNode)
                    endNode = stayWithinNode;
                break;
            }

            if (node.nodeType === Node.TEXT_NODE) {
                var start = (node === this ? offset : 0);
                for (var i = start; i < node.nodeValue.length; ++i) {
                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
                        endNode = node;
                        endOffset = i;
                        break;
                    }
                }
            }

            if (endNode)
                break;

            node = node.traverseNextNode(stayWithinNode);
        }

        if (!endNode) {
            endNode = stayWithinNode;
            endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
        }
    } else {
        endNode = this;
        endOffset = offset;
    }

    var result = this.ownerDocument.createRange();
    result.setStart(startNode, startOffset);
    result.setEnd(endNode, endOffset);

    return result;
}

Node.prototype.traverseNextTextNode = function(stayWithin)
{
    var node = this.traverseNextNode(stayWithin);
    if (!node)
        return;

    while (node && node.nodeType !== Node.TEXT_NODE)
        node = node.traverseNextNode(stayWithin);

    return node;
}

Node.prototype.rangeBoundaryForOffset = function(offset)
{
    var node = this.traverseNextTextNode(this);
    while (node && offset > node.nodeValue.length) {
        offset -= node.nodeValue.length;
        node = node.traverseNextTextNode(this);
    }
    if (!node)
        return { container: this, offset: 0 };
    return { container: node, offset: offset };
}

Element.prototype.removeStyleClass = function(className) 
{
    // Test for the simple case first.
    if (this.className === className) {
        this.className = "";
        return;
    }

    var index = this.className.indexOf(className);
    if (index === -1)
        return;

    this.className = this.className.split(" ").filter(function(s) {
        return s && s !== className;
    }).join(" ");
}

Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
{
    var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
    if (regex.test(this.className))
        this.className = this.className.replace(regex, " ");
}

Element.prototype.addStyleClass = function(className) 
{
    if (className && !this.hasStyleClass(className))
        this.className += (this.className.length ? " " + className : className);
}

Element.prototype.hasStyleClass = function(className) 
{
    if (!className)
        return false;
    // Test for the simple case
    if (this.className === className)
        return true;

    var index = this.className.indexOf(className);
    if (index === -1)
        return false;
    var toTest = " " + this.className + " ";
    return toTest.indexOf(" " + className + " ", index) !== -1;
}

Element.prototype.positionAt = function(x, y)
{
    this.style.left = x + "px";
    this.style.top = y + "px";
}

Element.prototype.pruneEmptyTextNodes = function()
{
    var sibling = this.firstChild;
    while (sibling) {
        var nextSibling = sibling.nextSibling;
        if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "")
            this.removeChild(sibling);
        sibling = nextSibling;
    }
}

Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
{
    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
        for (var i = 0; i < nameArray.length; ++i)
            if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
                return node;
    return null;
}

Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
{
    return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
}

Node.prototype.enclosingNodeOrSelfWithClass = function(className)
{
    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
        if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
            return node;
    return null;
}

Node.prototype.enclosingNodeWithClass = function(className)
{
    if (!this.parentNode)
        return null;
    return this.parentNode.enclosingNodeOrSelfWithClass(className);
}

Element.prototype.query = function(query) 
{
    return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

Element.prototype.removeChildren = function()
{
    if (this.firstChild)
        this.textContent = "";
}

Element.prototype.isInsertionCaretInside = function()
{
    var selection = window.getSelection();
    if (!selection.rangeCount || !selection.isCollapsed)
        return false;
    var selectionRange = selection.getRangeAt(0);
    return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
}

Element.prototype.createChild = function(elementName, className)
{
    var element = document.createElement(elementName);
    if (className)
        element.className = className;
    this.appendChild(element);
    return element;
}

Element.prototype.__defineGetter__("totalOffsetLeft", function()
{
    var total = 0;
    for (var element = this; element; element = element.offsetParent)
        total += element.offsetLeft + (this !== element ? element.clientLeft : 0);
    return total;
});

Element.prototype.__defineGetter__("totalOffsetTop", function()
{
    var total = 0;
    for (var element = this; element; element = element.offsetParent)
        total += element.offsetTop + (this !== element ? element.clientTop : 0);
    return total;
});

Element.prototype.offsetRelativeToWindow = function(targetWindow)
{
    var elementOffset = {x: 0, y: 0};
    var curElement = this;
    var curWindow = this.ownerDocument.defaultView;
    while (curWindow && curElement) {
        elementOffset.x += curElement.totalOffsetLeft;
        elementOffset.y += curElement.totalOffsetTop;
        if (curWindow === targetWindow)
            break;

        curElement = curWindow.frameElement;
        curWindow = curWindow.parent;
    }

    return elementOffset;
}

KeyboardEvent.prototype.__defineGetter__("data", function()
{
    // Emulate "data" attribute from DOM 3 TextInput event.
    // See http://www.w3.org/TR/DOM-Level-3-Events/#events-Events-TextEvent-data
    switch (this.type) {
        case "keypress":
            if (!this.ctrlKey && !this.metaKey)
                return String.fromCharCode(this.charCode);
            else
                return "";
        case "keydown":
        case "keyup":
            if (!this.ctrlKey && !this.metaKey && !this.altKey)
                return String.fromCharCode(this.which);
            else
                return "";
    }
});

Text.prototype.select = function(start, end)
{
    start = start || 0;
    end = end || this.textContent.length;

    if (start < 0)
        start = end + start;

    var selection = window.getSelection();
    selection.removeAllRanges();
    var range = document.createRange();
    range.setStart(this, start);
    range.setEnd(this, end);
    selection.addRange(range);
    return this;
}

Element.prototype.__defineGetter__("selectionLeftOffset", function() {
    // Calculate selection offset relative to the current element.

    var selection = window.getSelection();
    if (!selection.containsNode(this, true))
        return null;

    var leftOffset = selection.anchorOffset;
    var node = selection.anchorNode;

    while (node !== this) {
        while (node.previousSibling) {
            node = node.previousSibling;
            leftOffset += node.textContent.length;
        }
        node = node.parentNode;
    }

    return leftOffset;
});

Node.prototype.isWhitespace = isNodeWhitespace;
Node.prototype.displayName = nodeDisplayName;
Node.prototype.isAncestor = function(node)
{
    return isAncestorNode(this, node);
};
Node.prototype.isDescendant = isDescendantNode;
Node.prototype.traverseNextNode = traverseNextNode;
Node.prototype.traversePreviousNode = traversePreviousNode;
Node.prototype.onlyTextChild = onlyTextChild;

String.prototype.hasSubstring = function(string, caseInsensitive)
{
    if (!caseInsensitive)
        return this.indexOf(string) !== -1;
    return this.match(new RegExp(string.escapeForRegExp(), "i"));
}

String.prototype.escapeCharacters = function(chars)
{
    var foundChar = false;
    for (var i = 0; i < chars.length; ++i) {
        if (this.indexOf(chars.charAt(i)) !== -1) {
            foundChar = true;
            break;
        }
    }

    if (!foundChar)
        return this;

    var result = "";
    for (var i = 0; i < this.length; ++i) {
        if (chars.indexOf(this.charAt(i)) !== -1)
            result += "\\";
        result += this.charAt(i);
    }

    return result;
}

String.prototype.escapeForRegExp = function()
{
    return this.escapeCharacters("^[]{}()\\.$*+?|");
}

String.prototype.escapeHTML = function()
{
    return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

String.prototype.collapseWhitespace = function()
{
    return this.replace(/[\s\xA0]+/g, " ");
}

String.prototype.trimURL = function(baseURLDomain)
{
    var result = this.replace(/^(https|http|file):\/\//i, "");
    if (baseURLDomain)
        result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
    return result;
}

function isNodeWhitespace()
{
    if (!this || this.nodeType !== Node.TEXT_NODE)
        return false;
    if (!this.nodeValue.length)
        return true;
    return this.nodeValue.match(/^[\s\xA0]+$/);
}

function nodeDisplayName()
{
    if (!this)
        return "";

    switch (this.nodeType) {
        case Node.DOCUMENT_NODE:
            return "Document";

        case Node.ELEMENT_NODE:
            var name = "<" + this.nodeName.toLowerCase();

            if (this.hasAttributes()) {
                var value = this.getAttribute("id");
                if (value)
                    name += " id=\"" + value + "\"";
                value = this.getAttribute("class");
                if (value)
                    name += " class=\"" + value + "\"";
                if (this.nodeName.toLowerCase() === "a") {
                    value = this.getAttribute("name");
                    if (value)
                        name += " name=\"" + value + "\"";
                    value = this.getAttribute("href");
                    if (value)
                        name += " href=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "img") {
                    value = this.getAttribute("src");
                    if (value)
                        name += " src=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "iframe") {
                    value = this.getAttribute("src");
                    if (value)
                        name += " src=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "input") {
                    value = this.getAttribute("name");
                    if (value)
                        name += " name=\"" + value + "\"";
                    value = this.getAttribute("type");
                    if (value)
                        name += " type=\"" + value + "\"";
                } else if (this.nodeName.toLowerCase() === "form") {
                    value = this.getAttribute("action");
                    if (value)
                        name += " action=\"" + value + "\"";
                }
            }

            return name + ">";

        case Node.TEXT_NODE:
            if (isNodeWhitespace.call(this))
                return "(whitespace)";
            return "\"" + this.nodeValue + "\"";

        case Node.COMMENT_NODE:
            return "<!--" + this.nodeValue + "-->";
            
        case Node.DOCUMENT_TYPE_NODE:
            var docType = "<!DOCTYPE " + this.nodeName;
            if (this.publicId) {
                docType += " PUBLIC \"" + this.publicId + "\"";
                if (this.systemId)
                    docType += " \"" + this.systemId + "\"";
            } else if (this.systemId)
                docType += " SYSTEM \"" + this.systemId + "\"";
            if (this.internalSubset)
                docType += " [" + this.internalSubset + "]";
            return docType + ">";
    }

    return this.nodeName.toLowerCase().collapseWhitespace();
}

function isAncestorNode(ancestor, node)
{
    if (!node || !ancestor)
        return false;

    var currentNode = node.parentNode;
    while (currentNode) {
        if (ancestor === currentNode)
            return true;
        currentNode = currentNode.parentNode;
    }
    return false;
}

function isDescendantNode(descendant)
{
    return isAncestorNode(descendant, this);
}

function traverseNextNode(stayWithin)
{
    if (!this)
        return;

    var node = this.firstChild;
    if (node)
        return node;

    if (stayWithin && this === stayWithin)
        return null;

    node = this.nextSibling;
    if (node)
        return node;

    node = this;
    while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
        node = node.parentNode;
    if (!node)
        return null;

    return node.nextSibling;
}

function traversePreviousNode(stayWithin)
{
    if (!this)
        return;
    if (stayWithin && this === stayWithin)
        return null;
    var node = this.previousSibling;
    while (node && node.lastChild)
        node = node.lastChild;
    if (node)
        return node;
    return this.parentNode;
}

function onlyTextChild()
{
    if (!this)
        return null;

    var firstChild = this.firstChild;
    if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE)
        return null;

    var sibling = firstChild.nextSibling;
    return sibling ? null : firstChild;
}

function appropriateSelectorForNode(node, justSelector)
{
    if (!node)
        return "";

    var lowerCaseName = node.localName || node.nodeName.toLowerCase();

    var id = node.getAttribute("id");
    if (id) {
        var selector = "#" + id;
        return (justSelector ? selector : lowerCaseName + selector);
    }

    var className = node.getAttribute("class");
    if (className) {
        var selector = "." + className.replace(/\s+/, ".");
        return (justSelector ? selector : lowerCaseName + selector);
    }

    if (lowerCaseName === "input" && node.getAttribute("type"))
        return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]";

    return lowerCaseName;
}

function getDocumentForNode(node)
{
    return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument;
}

function parentNode(node)
{
    return node.parentNode;
}

Number.millisToString = function(ms, formatterFunction, higherResolution)
{
    return Number.secondsToString(ms / 1000, formatterFunction, higherResolution);
}

Number.secondsToString = function(seconds, formatterFunction, higherResolution)
{
    if (!formatterFunction)
        formatterFunction = String.sprintf;

    if (seconds === 0)
        return "0";

    var ms = seconds * 1000;
    if (higherResolution && ms < 1000)
        return formatterFunction("%.3fms", ms);
    else if (ms < 1000)
        return formatterFunction("%.0fms", ms);

    if (seconds < 60)
        return formatterFunction("%.2fs", seconds);

    var minutes = seconds / 60;
    if (minutes < 60)
        return formatterFunction("%.1fmin", minutes);

    var hours = minutes / 60;
    if (hours < 24)
        return formatterFunction("%.1fhrs", hours);

    var days = hours / 24;
    return formatterFunction("%.1f days", days);
}

Number.bytesToString = function(bytes, formatterFunction, higherResolution)
{
    if (!formatterFunction)
        formatterFunction = String.sprintf;
    if (typeof higherResolution === "undefined")
        higherResolution = true;

    if (bytes < 1024)
        return formatterFunction("%.0fB", bytes);

    var kilobytes = bytes / 1024;
    if (higherResolution && kilobytes < 1024)
        return formatterFunction("%.2fKB", kilobytes);
    else if (kilobytes < 1024)
        return formatterFunction("%.0fKB", kilobytes);

    var megabytes = kilobytes / 1024;
    if (higherResolution)
        return formatterFunction("%.2fMB", megabytes);
    else
        return formatterFunction("%.0fMB", megabytes);
}

Number.constrain = function(num, min, max)
{
    if (num < min)
        num = min;
    else if (num > max)
        num = max;
    return num;
}

HTMLTextAreaElement.prototype.moveCursorToEnd = function()
{
    var length = this.value.length;
    this.setSelectionRange(length, length);
}

Array.prototype.remove = function(value, onlyFirst)
{
    if (onlyFirst) {
        var index = this.indexOf(value);
        if (index !== -1)
            this.splice(index, 1);
        return;
    }

    var length = this.length;
    for (var i = 0; i < length; ++i) {
        if (this[i] === value)
            this.splice(i, 1);
    }
}

Array.prototype.keySet = function()
{
    var keys = {};
    for (var i = 0; i < this.length; ++i)
        keys[this[i]] = true;
    return keys;
}

Array.convert = function(list)
{
    // Cast array-like object to an array.
    return Array.prototype.slice.call(list);
}

function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
{
    var first = 0;
    var last = aList.length - 1;
    var floor = Math.floor;
    var mid, c;

    while (first <= last) {
        mid = floor((first + last) / 2);
        c = aFunction(anObject, aList[mid]);

        if (c > 0)
            first = mid + 1;
        else if (c < 0)
            last = mid - 1;
        else {
            // Return the first occurance of an item in the list.
            while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0)
                mid--;
            first = mid;
            break;
        }
    }

    return first;
}

String.sprintf = function(format)
{
    return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
}

String.tokenizeFormatString = function(format)
{
    var tokens = [];
    var substitutionIndex = 0;

    function addStringToken(str)
    {
        tokens.push({ type: "string", value: str });
    }

    function addSpecifierToken(specifier, precision, substitutionIndex)
    {
        tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
    }

    var index = 0;
    for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
        addStringToken(format.substring(index, precentIndex));
        index = precentIndex + 1;

        if (format[index] === "%") {
            addStringToken("%");
            ++index;
            continue;
        }

        if (!isNaN(format[index])) {
            // The first character is a number, it might be a substitution index.
            var number = parseInt(format.substring(index));
            while (!isNaN(format[index]))
                ++index;
            // If the number is greater than zero and ends with a "$",
            // then this is a substitution index.
            if (number > 0 && format[index] === "$") {
                substitutionIndex = (number - 1);
                ++index;
            }
        }

        var precision = -1;
        if (format[index] === ".") {
            // This is a precision specifier. If no digit follows the ".",
            // then the precision should be zero.
            ++index;
            precision = parseInt(format.substring(index));
            if (isNaN(precision))
                precision = 0;
            while (!isNaN(format[index]))
                ++index;
        }

        addSpecifierToken(format[index], precision, substitutionIndex);

        ++substitutionIndex;
        ++index;
    }

    addStringToken(format.substring(index));

    return tokens;
}

String.standardFormatters = {
    d: function(substitution)
    {
        if (typeof substitution == "object" && WebInspector.RemoteObject.type(substitution) === "number")
            substitution = substitution.description;
        substitution = parseInt(substitution);
        return !isNaN(substitution) ? substitution : 0;
    },

    f: function(substitution, token)
    {
        if (typeof substitution == "object" && WebInspector.RemoteObject.type(substitution) === "number")
            substitution = substitution.description;
        substitution = parseFloat(substitution);
        if (substitution && token.precision > -1)
            substitution = substitution.toFixed(token.precision);
        return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
    },

    s: function(substitution)
    {
        if (typeof substitution == "object" && WebInspector.RemoteObject.type(substitution) !== "null")
            substitution = substitution.description;
        return substitution;
    },
};

String.vsprintf = function(format, substitutions)
{
    return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
}

String.format = function(format, substitutions, formatters, initialValue, append)
{
    if (!format || !substitutions || !substitutions.length)
        return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };

    function prettyFunctionName()
    {
        return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
    }

    function warn(msg)
    {
        console.warn(prettyFunctionName() + ": " + msg);
    }

    function error(msg)
    {
        console.error(prettyFunctionName() + ": " + msg);
    }

    var result = initialValue;
    var tokens = String.tokenizeFormatString(format);
    var usedSubstitutionIndexes = {};

    for (var i = 0; i < tokens.length; ++i) {
        var token = tokens[i];

        if (token.type === "string") {
            result = append(result, token.value);
            continue;
        }

        if (token.type !== "specifier") {
            error("Unknown token type \"" + token.type + "\" found.");
            continue;
        }

        if (token.substitutionIndex >= substitutions.length) {
            // If there are not enough substitutions for the current substitutionIndex
            // just output the format specifier literally and move on.
            error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
            result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
            continue;
        }

        usedSubstitutionIndexes[token.substitutionIndex] = true;

        if (!(token.specifier in formatters)) {
            // Encountered an unsupported format character, treat as a string.
            warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
            result = append(result, substitutions[token.substitutionIndex]);
            continue;
        }

        result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
    }

    var unusedSubstitutions = [];
    for (var i = 0; i < substitutions.length; ++i) {
        if (i in usedSubstitutionIndexes)
            continue;
        unusedSubstitutions.push(substitutions[i]);
    }

    return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
}

function isEnterKey(event) {
    // Check if in IME.
    return event.keyCode !== 229 && event.keyIdentifier === "Enter";
}


function highlightSearchResult(element, offset, length)
{
    var lineText = element.textContent;
    var endOffset = offset + length;
    var highlightNode = document.createElement("span");
    highlightNode.className = "webkit-search-result";
    highlightNode.textContent = lineText.substring(offset, endOffset);

    var boundary = element.rangeBoundaryForOffset(offset);
    var textNode = boundary.container;
    var text = textNode.textContent;

    if (boundary.offset + length < text.length) {
        // Selection belong to a single split mode.
        textNode.textContent = text.substring(boundary.offset + length);
        textNode.parentElement.insertBefore(highlightNode, textNode);
        var prefixNode = document.createTextNode(text.substring(0, boundary.offset));
        textNode.parentElement.insertBefore(prefixNode, highlightNode);
        return highlightNode;
    }

    var parentElement = textNode.parentElement;
    var anchorElement = textNode.nextSibling;

    length -= text.length - boundary.offset;
    textNode.textContent = text.substring(0, boundary.offset);
    textNode = textNode.traverseNextTextNode(element);

    while (textNode) {
        var text = textNode.textContent;
        if (length < text.length) {
            textNode.textContent = text.substring(length);
            break;
        }

        length -= text.length;
        textNode.textContent = "";
        textNode = textNode.traverseNextTextNode(element);
    }

    parentElement.insertBefore(highlightNode, anchorElement);
    return highlightNode;
}

function createSearchRegex(query)
{
    var regex = "";
    for (var i = 0; i < query.length; ++i) {
        var char = query.charAt(i);
        if (char === "]")
            char = "\\]";
        regex += "[" + char + "]";
    }
    return new RegExp(regex, "i");
}

function offerFileForDownload(contents)
{
    var builder = new BlobBuilder();
    builder.append(contents);
    var blob = builder.getBlob("application/octet-stream");
    var url = window.createObjectURL(blob);
    window.open(url);
}

/* treeoutline.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

function TreeOutline(listNode)
{
    this.children = [];
    this.selectedTreeElement = null;
    this._childrenListNode = listNode;
    this._childrenListNode.removeChildren();
    this._knownTreeElements = [];
    this._treeElementsExpandedState = [];
    this.expandTreeElementsWhenArrowing = false;
    this.root = true;
    this.hasChildren = false;
    this.expanded = true;
    this.selected = false;
    this.treeOutline = this;

    this._childrenListNode.tabIndex = 0;
    this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
}

TreeOutline._knownTreeElementNextIdentifier = 1;

TreeOutline._appendChild = function(child)
{
    if (!child)
        throw("child can't be undefined or null");

    var lastChild = this.children[this.children.length - 1];
    if (lastChild) {
        lastChild.nextSibling = child;
        child.previousSibling = lastChild;
    } else {
        child.previousSibling = null;
        child.nextSibling = null;
    }

    this.children.push(child);
    this.hasChildren = true;
    child.parent = this;
    child.treeOutline = this.treeOutline;
    child.treeOutline._rememberTreeElement(child);

    var current = child.children[0];
    while (current) {
        current.treeOutline = this.treeOutline;
        current.treeOutline._rememberTreeElement(current);
        current = current.traverseNextTreeElement(false, child, true);
    }

    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];

    if (!this._childrenListNode) {
        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");
        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");
    }

    child._attach();
}

TreeOutline._insertChild = function(child, index)
{
    if (!child)
        throw("child can't be undefined or null");

    var previousChild = (index > 0 ? this.children[index - 1] : null);
    if (previousChild) {
        previousChild.nextSibling = child;
        child.previousSibling = previousChild;
    } else {
        child.previousSibling = null;
    }

    var nextChild = this.children[index];
    if (nextChild) {
        nextChild.previousSibling = child;
        child.nextSibling = nextChild;
    } else {
        child.nextSibling = null;
    }

    this.children.splice(index, 0, child);
    this.hasChildren = true;
    child.parent = this;
    child.treeOutline = this.treeOutline;
    child.treeOutline._rememberTreeElement(child);

    var current = child.children[0];
    while (current) {
        current.treeOutline = this.treeOutline;
        current.treeOutline._rememberTreeElement(current);
        current = current.traverseNextTreeElement(false, child, true);
    }

    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];

    if (!this._childrenListNode) {
        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");
        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");
    }

    child._attach();
}

TreeOutline._removeChildAtIndex = function(childIndex)
{
    if (childIndex < 0 || childIndex >= this.children.length)
        throw("childIndex out of range");

    var child = this.children[childIndex];
    this.children.splice(childIndex, 1);

    var parent = child.parent;
    if (child.deselect()) {
        if (child.previousSibling)
            child.previousSibling.select();
        else if (child.nextSibling)
            child.nextSibling.select();
        else
            parent.select();
    }

    if (child.previousSibling)
        child.previousSibling.nextSibling = child.nextSibling;
    if (child.nextSibling)
        child.nextSibling.previousSibling = child.previousSibling;

    if (child.treeOutline) {
        child.treeOutline._forgetTreeElement(child);
        child.treeOutline._forgetChildrenRecursive(child);
    }

    child._detach();
    child.treeOutline = null;
    child.parent = null;
    child.nextSibling = null;
    child.previousSibling = null;
}

TreeOutline._removeChild = function(child)
{
    if (!child)
        throw("child can't be undefined or null");

    var childIndex = this.children.indexOf(child);
    if (childIndex === -1)
        throw("child not found in this node's children");

    TreeOutline._removeChildAtIndex.call(this, childIndex);
}

TreeOutline._removeChildren = function()
{
    for (var i = 0; i < this.children.length; ++i) {
        var child = this.children[i];
        child.deselect();

        if (child.treeOutline) {
            child.treeOutline._forgetTreeElement(child);
            child.treeOutline._forgetChildrenRecursive(child);
        }

        child._detach();
        child.treeOutline = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;
    }

    this.children = [];
}

TreeOutline._removeChildrenRecursive = function()
{
    var childrenToRemove = this.children;

    var child = this.children[0];
    while (child) {
        if (child.children.length)
            childrenToRemove = childrenToRemove.concat(child.children);
        child = child.traverseNextTreeElement(false, this, true);
    }

    for (var i = 0; i < childrenToRemove.length; ++i) {
        var child = childrenToRemove[i];
        child.deselect();
        if (child.treeOutline)
            child.treeOutline._forgetTreeElement(child);
        child._detach();
        child.children = [];
        child.treeOutline = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;
    }

    this.children = [];
}

TreeOutline.prototype._rememberTreeElement = function(element)
{
    if (!this._knownTreeElements[element.identifier])
        this._knownTreeElements[element.identifier] = [];

    // check if the element is already known
    var elements = this._knownTreeElements[element.identifier];
    if (elements.indexOf(element) !== -1)
        return;

    // add the element
    elements.push(element);
}

TreeOutline.prototype._forgetTreeElement = function(element)
{
    if (this._knownTreeElements[element.identifier])
        this._knownTreeElements[element.identifier].remove(element, true);
}

TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
{
    var child = parentElement.children[0];
    while (child) {
        this._forgetTreeElement(child);
        child = child.traverseNextTreeElement(false, this, true);
    }
}

TreeOutline.prototype.getCachedTreeElement = function(representedObject)
{
    if (!representedObject)
        return null;

    if ("__treeElementIdentifier" in representedObject) {
        // If this representedObject has a tree element identifier, and it is a known TreeElement
        // in our tree we can just return that tree element.
        var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
        if (elements) {
            for (var i = 0; i < elements.length; ++i)
                if (elements[i].representedObject === representedObject)
                    return elements[i];
        }
    }
    return null;
}

TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
{
    if (!representedObject)
        return null;

    var cachedElement = this.getCachedTreeElement(representedObject);
    if (cachedElement)
        return cachedElement;

    // The representedObject isn't known, so we start at the top of the tree and work down to find the first
    // tree element that represents representedObject or one of its ancestors.
    var item;
    var found = false;
    for (var i = 0; i < this.children.length; ++i) {
        item = this.children[i];
        if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) {
            found = true;
            break;
        }
    }

    if (!found)
        return null;

    // Make sure the item that we found is connected to the root of the tree.
    // Build up a list of representedObject's ancestors that aren't already in our tree.
    var ancestors = [];
    var currentObject = representedObject;
    while (currentObject) {
        ancestors.unshift(currentObject);
        if (currentObject === item.representedObject)
            break;
        currentObject = getParent(currentObject);
    }

    // For each of those ancestors we populate them to fill in the tree.
    for (var i = 0; i < ancestors.length; ++i) {
        // Make sure we don't call findTreeElement with the same representedObject
        // again, to prevent infinite recursion.
        if (ancestors[i] === representedObject)
            continue;
        // FIXME: we could do something faster than findTreeElement since we will know the next
        // ancestor exists in the tree.
        item = this.findTreeElement(ancestors[i], isAncestor, getParent);
        if (item && item.onpopulate)
            item.onpopulate(item);
    }

    return this.getCachedTreeElement(representedObject);
}

TreeOutline.prototype.treeElementFromPoint = function(x, y)
{
    var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
    var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
    if (listNode)
        return listNode.parentTreeElement || listNode.treeElement;
    return null;
}

TreeOutline.prototype._treeKeyDown = function(event)
{
    if (event.target !== this._childrenListNode)
        return;

    if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
        return;

    var handled = false;
    var nextSelectedElement;
    if (event.keyIdentifier === "Up" && !event.altKey) {
        nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
        while (nextSelectedElement && !nextSelectedElement.selectable)
            nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
        handled = nextSelectedElement ? true : false;
    } else if (event.keyIdentifier === "Down" && !event.altKey) {
        nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
        while (nextSelectedElement && !nextSelectedElement.selectable)
            nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
        handled = nextSelectedElement ? true : false;
    } else if (event.keyIdentifier === "Left") {
        if (this.selectedTreeElement.expanded) {
            if (event.altKey)
                this.selectedTreeElement.collapseRecursively();
            else
                this.selectedTreeElement.collapse();
            handled = true;
        } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
            handled = true;
            if (this.selectedTreeElement.parent.selectable) {
                nextSelectedElement = this.selectedTreeElement.parent;
                handled = nextSelectedElement ? true : false;
            } else if (this.selectedTreeElement.parent)
                this.selectedTreeElement.parent.collapse();
        }
    } else if (event.keyIdentifier === "Right") {
        if (!this.selectedTreeElement.revealed()) {
            this.selectedTreeElement.reveal();
            handled = true;
        } else if (this.selectedTreeElement.hasChildren) {
            handled = true;
            if (this.selectedTreeElement.expanded) {
                nextSelectedElement = this.selectedTreeElement.children[0];
                handled = nextSelectedElement ? true : false;
            } else {
                if (event.altKey)
                    this.selectedTreeElement.expandRecursively();
                else
                    this.selectedTreeElement.expand();
            }
        }
    } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code || event.keyCode === WebInspector.KeyboardShortcut.Keys.Delete.code) {
        if (this.selectedTreeElement.ondelete)
            handled = this.selectedTreeElement.ondelete();
    } else if (isEnterKey(event)) {
        if (this.selectedTreeElement.onenter)
            handled = this.selectedTreeElement.onenter();
    }

    if (nextSelectedElement) {
        nextSelectedElement.reveal();
        nextSelectedElement.select(false, true);
    }

    if (handled) {
        event.preventDefault();
        event.stopPropagation();
    }
}

TreeOutline.prototype.expand = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.collapse = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.revealed = function()
{
    return true;
}

TreeOutline.prototype.reveal = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.select = function()
{
    // this is the root, do nothing
}

TreeOutline.prototype.appendChild = TreeOutline._appendChild;
TreeOutline.prototype.insertChild = TreeOutline._insertChild;
TreeOutline.prototype.removeChild = TreeOutline._removeChild;
TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;

function TreeElement(title, representedObject, hasChildren)
{
    this._title = title;
    this.representedObject = (representedObject || {});

    if (this.representedObject.__treeElementIdentifier)
        this.identifier = this.representedObject.__treeElementIdentifier;
    else {
        this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
        this.representedObject.__treeElementIdentifier = this.identifier;
    }

    this._hidden = false;
    this.expanded = false;
    this.selected = false;
    this.hasChildren = hasChildren;
    this.children = [];
    this.treeOutline = null;
    this.parent = null;
    this.previousSibling = null;
    this.nextSibling = null;
    this._listItemNode = null;
}

TreeElement.prototype = {
    selectable: true,
    arrowToggleWidth: 10,

    get listItemElement() {
        return this._listItemNode;
    },

    get childrenListElement() {
        return this._childrenListNode;
    },

    get title() {
        return this._title;
    },

    set title(x) {
        this._title = x;
        this._setListItemNodeContent();
    },

    get tooltip() {
        return this._tooltip;
    },

    set tooltip(x) {
        this._tooltip = x;
        if (this._listItemNode)
            this._listItemNode.title = x ? x : "";
    },

    get hasChildren() {
        return this._hasChildren;
    },

    set hasChildren(x) {
        if (this._hasChildren === x)
            return;

        this._hasChildren = x;

        if (!this._listItemNode)
            return;

        if (x)
            this._listItemNode.addStyleClass("parent");
        else {
            this._listItemNode.removeStyleClass("parent");
            this.collapse();
        }
    },

    get hidden() {
        return this._hidden;
    },

    set hidden(x) {
        if (this._hidden === x)
            return;

        this._hidden = x;

        if (x) {
            if (this._listItemNode)
                this._listItemNode.addStyleClass("hidden");
            if (this._childrenListNode)
                this._childrenListNode.addStyleClass("hidden");
        } else {
            if (this._listItemNode)
                this._listItemNode.removeStyleClass("hidden");
            if (this._childrenListNode)
                this._childrenListNode.removeStyleClass("hidden");
        }
    },

    get shouldRefreshChildren() {
        return this._shouldRefreshChildren;
    },

    set shouldRefreshChildren(x) {
        this._shouldRefreshChildren = x;
        if (x && this.expanded)
            this.expand();
    },

    _setListItemNodeContent: function()
    {
        if (!this._listItemNode)
            return;
        if (!this._title || typeof this._title === "string")
            this._listItemNode.innerHTML = this._title;
        else {
            this._listItemNode.removeChildren();
            if (this._title.parentNode)
                this._title.parentNode.removeChild(this._title);
            this._listItemNode.appendChild(this._title);
        }
    }
}

TreeElement.prototype.appendChild = TreeOutline._appendChild;
TreeElement.prototype.insertChild = TreeOutline._insertChild;
TreeElement.prototype.removeChild = TreeOutline._removeChild;
TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;

TreeElement.prototype._attach = function()
{
    if (!this._listItemNode || this.parent._shouldRefreshChildren) {
        if (this._listItemNode && this._listItemNode.parentNode)
            this._listItemNode.parentNode.removeChild(this._listItemNode);

        this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
        this._listItemNode.treeElement = this;
        this._setListItemNodeContent();
        this._listItemNode.title = this._tooltip ? this._tooltip : "";

        if (this.hidden)
            this._listItemNode.addStyleClass("hidden");
        if (this.hasChildren)
            this._listItemNode.addStyleClass("parent");
        if (this.expanded)
            this._listItemNode.addStyleClass("expanded");
        if (this.selected)
            this._listItemNode.addStyleClass("selected");

        this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
        this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
        this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);

        if (this.onattach)
            this.onattach(this);
    }

    var nextSibling = null;
    if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
        nextSibling = this.nextSibling._listItemNode;
    this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
    if (this._childrenListNode)
        this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    if (this.selected)
        this.select();
    if (this.expanded)
        this.expand();
}

TreeElement.prototype._detach = function()
{
    if (this._listItemNode && this._listItemNode.parentNode)
        this._listItemNode.parentNode.removeChild(this._listItemNode);
    if (this._childrenListNode && this._childrenListNode.parentNode)
        this._childrenListNode.parentNode.removeChild(this._childrenListNode);
}

TreeElement.treeElementMouseDown = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement || !element.treeElement.selectable)
        return;

    if (element.treeElement.isEventWithinDisclosureTriangle(event))
        return;

    element.treeElement.selectOnMouseDown(event);
}

TreeElement.treeElementToggled = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement)
        return;

    if (!element.treeElement.isEventWithinDisclosureTriangle(event))
        return;

    if (element.treeElement.expanded) {
        if (event.altKey)
            element.treeElement.collapseRecursively();
        else
            element.treeElement.collapse();
    } else {
        if (event.altKey)
            element.treeElement.expandRecursively();
        else
            element.treeElement.expand();
    }
    event.stopPropagation();
}

TreeElement.treeElementDoubleClicked = function(event)
{
    var element = event.currentTarget;
    if (!element || !element.treeElement)
        return;

    if (element.treeElement.ondblclick)
        element.treeElement.ondblclick.call(element.treeElement, event);
    else if (element.treeElement.hasChildren && !element.treeElement.expanded)
        element.treeElement.expand();
}

TreeElement.prototype.collapse = function()
{
    if (this._listItemNode)
        this._listItemNode.removeStyleClass("expanded");
    if (this._childrenListNode)
        this._childrenListNode.removeStyleClass("expanded");

    this.expanded = false;
    if (this.treeOutline)
        this.treeOutline._treeElementsExpandedState[this.identifier] = true;

    if (this.oncollapse)
        this.oncollapse(this);
}

TreeElement.prototype.collapseRecursively = function()
{
    var item = this;
    while (item) {
        if (item.expanded)
            item.collapse();
        item = item.traverseNextTreeElement(false, this, true);
    }
}

TreeElement.prototype.expand = function()
{
    if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
        return;

    if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
        if (this._childrenListNode && this._childrenListNode.parentNode)
            this._childrenListNode.parentNode.removeChild(this._childrenListNode);

        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
        this._childrenListNode.parentTreeElement = this;
        this._childrenListNode.addStyleClass("children");

        if (this.hidden)
            this._childrenListNode.addStyleClass("hidden");

        if (this.onpopulate)
            this.onpopulate(this);

        for (var i = 0; i < this.children.length; ++i)
            this.children[i]._attach();

        delete this._shouldRefreshChildren;
    }

    if (this._listItemNode) {
        this._listItemNode.addStyleClass("expanded");
        if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
            this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    }

    if (this._childrenListNode)
        this._childrenListNode.addStyleClass("expanded");

    this.expanded = true;
    if (this.treeOutline)
        this.treeOutline._treeElementsExpandedState[this.identifier] = true;

    if (this.onexpand)
        this.onexpand(this);
}

TreeElement.prototype.expandRecursively = function(maxDepth)
{
    var item = this;
    var info = {};
    var depth = 0;

    // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
    // in some case can be infinite, since JavaScript objects can hold circular references.
    // So default to a recursion cap of 3 levels, since that gives fairly good results.
    if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
        maxDepth = 3;

    while (item) {
        if (depth < maxDepth)
            item.expand();
        item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
        depth += info.depthChange;
    }
}

TreeElement.prototype.hasAncestor = function(ancestor) {
    if (!ancestor)
        return false;

    var currentNode = this.parent;
    while (currentNode) {
        if (ancestor === currentNode)
            return true;
        currentNode = currentNode.parent;
    }

    return false;
}

TreeElement.prototype.reveal = function()
{
    var currentAncestor = this.parent;
    while (currentAncestor && !currentAncestor.root) {
        if (!currentAncestor.expanded)
            currentAncestor.expand();
        currentAncestor = currentAncestor.parent;
    }

    if (this.onreveal)
        this.onreveal(this);
}

TreeElement.prototype.revealed = function()
{
    var currentAncestor = this.parent;
    while (currentAncestor && !currentAncestor.root) {
        if (!currentAncestor.expanded)
            return false;
        currentAncestor = currentAncestor.parent;
    }

    return true;
}

TreeElement.prototype.selectOnMouseDown = function(event)
{
    this.select(false, true);
}

TreeElement.prototype.select = function(supressOnSelect, selectedByUser)
{
    if (!this.treeOutline || !this.selectable || this.selected)
        return;

    if (this.treeOutline.selectedTreeElement)
        this.treeOutline.selectedTreeElement.deselect();

    this.selected = true;
    this.treeOutline._childrenListNode.focus();
    this.treeOutline.selectedTreeElement = this;
    if (this._listItemNode)
        this._listItemNode.addStyleClass("selected");

    if (this.onselect && !supressOnSelect)
        this.onselect(this, selectedByUser);
}

TreeElement.prototype.deselect = function(supressOnDeselect)
{
    if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
        return false;

    this.selected = false;
    this.treeOutline.selectedTreeElement = null;
    if (this._listItemNode)
        this._listItemNode.removeStyleClass("selected");

    if (this.ondeselect && !supressOnDeselect)
        this.ondeselect(this);
    return true;
}

TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
{
    if (!dontPopulate && this.hasChildren && this.onpopulate)
        this.onpopulate(this);

    if (info)
        info.depthChange = 0;

    var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
    if (element && (!skipHidden || (skipHidden && this.expanded))) {
        if (info)
            info.depthChange = 1;
        return element;
    }

    if (this === stayWithin)
        return null;

    element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
    if (element)
        return element;

    element = this;
    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
        if (info)
            info.depthChange -= 1;
        element = element.parent;
    }

    if (!element)
        return null;

    return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
}

TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
{
    var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
    if (!dontPopulate && element && element.hasChildren && element.onpopulate)
        element.onpopulate(element);

    while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
        if (!dontPopulate && element.hasChildren && element.onpopulate)
            element.onpopulate(element);
        element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
    }

    if (element)
        return element;

    if (!this.parent || this.parent.root)
        return null;

    return this.parent;
}

TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
{
    var left = this._listItemNode.totalOffsetLeft;
    return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
}

/* inspector.js */

/*
 * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Keep this ; so that concatenated version of the script worked.
;(function preloadImages()
{
    (new Image()).src = "Images/clearConsoleButtonGlyph.png";
    (new Image()).src = "Images/consoleButtonGlyph.png";
    (new Image()).src = "Images/dockButtonGlyph.png";
    (new Image()).src = "Images/enableOutlineButtonGlyph.png";
    (new Image()).src = "Images/enableSolidButtonGlyph.png";
    (new Image()).src = "Images/excludeButtonGlyph.png";
    (new Image()).src = "Images/focusButtonGlyph.png";
    (new Image()).src = "Images/largerResourcesButtonGlyph.png";
    (new Image()).src = "Images/nodeSearchButtonGlyph.png";
    (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png";
    (new Image()).src = "Images/percentButtonGlyph.png";
    (new Image()).src = "Images/recordButtonGlyph.png";
    (new Image()).src = "Images/recordToggledButtonGlyph.png";
    (new Image()).src = "Images/reloadButtonGlyph.png";
    (new Image()).src = "Images/undockButtonGlyph.png";
})();

var WebInspector = {
    resources: {},
    resourceURLMap: {},
    cookieDomains: {},
    applicationCacheDomains: {},
    missingLocalizedStrings: {},
    pendingDispatches: 0,

    // RegExp groups:
    // 1 - scheme
    // 2 - hostname
    // 3 - ?port
    // 4 - ?path
    // 5 - ?fragment
    URLRegExp: /^(http[s]?|file):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i,
    GenericURLRegExp: /^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i,

    get platform()
    {
        if (!("_platform" in this))
            this._platform = InspectorFrontendHost.platform();

        return this._platform;
    },

    get platformFlavor()
    {
        if (!("_platformFlavor" in this))
            this._platformFlavor = this._detectPlatformFlavor();

        return this._platformFlavor;
    },

    _detectPlatformFlavor: function()
    {
        const userAgent = navigator.userAgent;

        if (this.platform === "windows") {
            var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
            if (match && match[1] >= 6)
                return WebInspector.PlatformFlavor.WindowsVista;
            return null;
        } else if (this.platform === "mac") {
            var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
            if (!match || match[1] != 10)
                return WebInspector.PlatformFlavor.MacSnowLeopard;
            switch (Number(match[2])) {
                case 4:
                    return WebInspector.PlatformFlavor.MacTiger;
                case 5:
                    return WebInspector.PlatformFlavor.MacLeopard;
                case 6:
                default:
                    return WebInspector.PlatformFlavor.MacSnowLeopard;
            }
        }

        return null;
    },

    get port()
    {
        if (!("_port" in this))
            this._port = InspectorFrontendHost.port();

        return this._port;
    },

    get previousFocusElement()
    {
        return this._previousFocusElement;
    },

    get currentFocusElement()
    {
        return this._currentFocusElement;
    },

    set currentFocusElement(x)
    {
        if (this._currentFocusElement !== x)
            this._previousFocusElement = this._currentFocusElement;
        this._currentFocusElement = x;

        if (this._currentFocusElement) {
            this._currentFocusElement.focus();

            // Make a caret selection inside the new element if there isn't a range selection and
            // there isn't already a caret selection inside.
            var selection = window.getSelection();
            if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
                var selectionRange = this._currentFocusElement.ownerDocument.createRange();
                selectionRange.setStart(this._currentFocusElement, 0);
                selectionRange.setEnd(this._currentFocusElement, 0);

                selection.removeAllRanges();
                selection.addRange(selectionRange);
            }
        } else if (this._previousFocusElement)
            this._previousFocusElement.blur();
    },

    get currentPanel()
    {
        return this._currentPanel;
    },

    set currentPanel(x)
    {
        if (this._currentPanel === x)
            return;

        if (this._currentPanel)
            this._currentPanel.hide();

        this._currentPanel = x;

        this.updateSearchLabel();

        if (x) {
            x.show();

            if (this.currentQuery) {
                if (x.performSearch) {
                    function performPanelSearch()
                    {
                        this.updateSearchMatchesCount();

                        x.currentQuery = this.currentQuery;
                        x.performSearch(this.currentQuery);
                    }

                    // Perform the search on a timeout so the panel switches fast.
                    setTimeout(performPanelSearch.bind(this), 0);
                } else {
                    // Update to show Not found for panels that can't be searched.
                    this.updateSearchMatchesCount();
                }
            }
        }

        for (var panelName in WebInspector.panels) {
            if (WebInspector.panels[panelName] === x) {
                InspectorBackend.storeLastActivePanel(panelName);
                this._panelHistory.setPanel(panelName);
            }
        }
    },

    createJSBreakpointsSidebarPane: function()
    {
        var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("Breakpoints"));
        function breakpointAdded(event)
        {
            pane.addBreakpoint(new WebInspector.JSBreakpointItem(event.data));
        }
        WebInspector.breakpointManager.addEventListener("breakpoint-added", breakpointAdded);
        return pane;
    },

    createDOMBreakpointsSidebarPane: function()
    {
        var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("DOM Breakpoints"));
        function breakpointAdded(event)
        {
            pane.addBreakpoint(new WebInspector.DOMBreakpointItem(event.data));
        }
        WebInspector.domBreakpointManager.addEventListener("dom-breakpoint-added", breakpointAdded);
        return pane;
    },

    createXHRBreakpointsSidebarPane: function()
    {
        var pane = new WebInspector.XHRBreakpointsSidebarPane();
        function breakpointAdded(event)
        {
            pane.addBreakpoint(new WebInspector.BreakpointItem(event.data));
        }
        WebInspector.breakpointManager.addEventListener("xhr-breakpoint-added", breakpointAdded);
        return pane;
    },

    _createPanels: function()
    {
        var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
        if (hiddenPanels.indexOf("elements") === -1)
            this.panels.elements = new WebInspector.ElementsPanel();
        if (Preferences.networkPanelEnabled && hiddenPanels.indexOf("network") === -1)
            this.panels.network = new WebInspector.NetworkPanel();
        if (hiddenPanels.indexOf("resources") === -1)
            this.panels.resources = new WebInspector.ResourcesPanel();
        if (hiddenPanels.indexOf("scripts") === -1)
            this.panels.scripts = new WebInspector.ScriptsPanel();
        if (hiddenPanels.indexOf("timeline") === -1)
            this.panels.timeline = new WebInspector.TimelinePanel();
        if (hiddenPanels.indexOf("profiles") === -1) {
            this.panels.profiles = new WebInspector.ProfilesPanel();
            this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType());
            if (Preferences.heapProfilerPresent)
                this.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
        }
        if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
            this.panels.storage = new WebInspector.StoragePanel();
        if (hiddenPanels.indexOf("audits") === -1)
            this.panels.audits = new WebInspector.AuditsPanel();
        if (hiddenPanels.indexOf("console") === -1)
            this.panels.console = new WebInspector.ConsolePanel();
    },

    get attached()
    {
        return this._attached;
    },

    set attached(x)
    {
        if (this._attached === x)
            return;

        this._attached = x;

        this.updateSearchLabel();

        var dockToggleButton = document.getElementById("dock-status-bar-item");
        var body = document.body;

        if (x) {
            body.removeStyleClass("detached");
            body.addStyleClass("attached");
            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
        } else {
            body.removeStyleClass("attached");
            body.addStyleClass("detached");
            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
        }
        if (this.drawer)
            this.drawer.resize();
    },

    get errors()
    {
        return this._errors || 0;
    },

    set errors(x)
    {
        x = Math.max(x, 0);

        if (this._errors === x)
            return;
        this._errors = x;
        this._updateErrorAndWarningCounts();
    },

    get warnings()
    {
        return this._warnings || 0;
    },

    set warnings(x)
    {
        x = Math.max(x, 0);

        if (this._warnings === x)
            return;
        this._warnings = x;
        this._updateErrorAndWarningCounts();
    },

    _updateErrorAndWarningCounts: function()
    {
        var errorWarningElement = document.getElementById("error-warning-count");
        if (!errorWarningElement)
            return;

        if (!this.errors && !this.warnings) {
            errorWarningElement.addStyleClass("hidden");
            return;
        }

        errorWarningElement.removeStyleClass("hidden");

        errorWarningElement.removeChildren();

        if (this.errors) {
            var errorElement = document.createElement("span");
            errorElement.id = "error-count";
            errorElement.textContent = this.errors;
            errorWarningElement.appendChild(errorElement);
        }

        if (this.warnings) {
            var warningsElement = document.createElement("span");
            warningsElement.id = "warning-count";
            warningsElement.textContent = this.warnings;
            errorWarningElement.appendChild(warningsElement);
        }

        if (this.errors) {
            if (this.warnings) {
                if (this.errors == 1) {
                    if (this.warnings == 1)
                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
                    else
                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
                } else if (this.warnings == 1)
                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
                else
                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
            } else if (this.errors == 1)
                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
            else
                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
        } else if (this.warnings == 1)
            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
        else if (this.warnings)
            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
        else
            errorWarningElement.title = null;
    },

    get styleChanges()
    {
        return this._styleChanges;
    },

    set styleChanges(x)
    {
        x = Math.max(x, 0);

        if (this._styleChanges === x)
            return;
        this._styleChanges = x;
        this._updateChangesCount();
    },

    _updateChangesCount: function()
    {
        // TODO: Remove immediate return when enabling the Changes Panel
        return;

        var changesElement = document.getElementById("changes-count");
        if (!changesElement)
            return;

        if (!this.styleChanges) {
            changesElement.addStyleClass("hidden");
            return;
        }

        changesElement.removeStyleClass("hidden");
        changesElement.removeChildren();

        if (this.styleChanges) {
            var styleChangesElement = document.createElement("span");
            styleChangesElement.id = "style-changes-count";
            styleChangesElement.textContent = this.styleChanges;
            changesElement.appendChild(styleChangesElement);
        }

        if (this.styleChanges) {
            if (this.styleChanges === 1)
                changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
            else
                changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
        }
    },

    highlightDOMNode: function(nodeId)
    {
        if ("_hideDOMNodeHighlightTimeout" in this) {
            clearTimeout(this._hideDOMNodeHighlightTimeout);
            delete this._hideDOMNodeHighlightTimeout;
        }

        if (this._highlightedDOMNodeId === nodeId)
            return;

        this._highlightedDOMNodeId = nodeId;
        if (nodeId)
            InspectorBackend.highlightDOMNode(nodeId);
        else
            InspectorBackend.hideDOMNodeHighlight();
    },

    highlightDOMNodeForTwoSeconds: function(nodeId)
    {
        this.highlightDOMNode(nodeId);
        this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000);
    },

    wireElementWithDOMNode: function(element, nodeId)
    {
        element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false);
        element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId), false);
        element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false);
    },

    _updateFocusedNode: function(nodeId)
    {
        this.currentPanel = this.panels.elements;
        this.panels.elements.updateFocusedNode(nodeId);
    }
}

WebInspector.PlatformFlavor = {
    WindowsVista: "windows-vista",
    MacTiger: "mac-tiger",
    MacLeopard: "mac-leopard",
    MacSnowLeopard: "mac-snowleopard"
};

(function parseQueryParameters()
{
    WebInspector.queryParamsObject = {};
    var queryParams = window.location.search;
    if (!queryParams)
        return;
    var params = queryParams.substring(1).split("&");
    for (var i = 0; i < params.length; ++i) {
        var pair = params[i].split("=");
        WebInspector.queryParamsObject[pair[0]] = pair[1];
    }
})();

WebInspector.loaded = function()
{
    if ("page" in WebInspector.queryParamsObject) {
        WebInspector.socket = new WebSocket("ws://" + window.location.host + "/devtools/page/" + WebInspector.queryParamsObject.page);
        WebInspector.socket.onmessage = function(message) { WebInspector_syncDispatch(message.data); }
        WebInspector.socket.onerror = function(error) { console.error(error); }
        WebInspector.socket.onopen = function() {
            InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket);
            InspectorFrontendHost.loaded = WebInspector.socket.send.bind(WebInspector.socket, "loaded");
            WebInspector.doLoadedDone();
        }
        return;
    }
    WebInspector.doLoadedDone();
}

WebInspector.doLoadedDone = function()
{
    InspectorBackend.setInjectedScriptSource("(" + injectedScriptConstructor + ");");

    var platform = WebInspector.platform;
    document.body.addStyleClass("platform-" + platform);
    var flavor = WebInspector.platformFlavor;
    if (flavor)
        document.body.addStyleClass("platform-" + flavor);
    var port = WebInspector.port;
    document.body.addStyleClass("port-" + port);

    InspectorFrontendHost.loaded();
    WebInspector.Settings.initialize();
    
    this._registerShortcuts();

    // set order of some sections explicitly
    WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
    WebInspector.shortcutsHelp.section(WebInspector.UIString("Elements Panel"));

    this.drawer = new WebInspector.Drawer();
    this.console = new WebInspector.ConsoleView(this.drawer);
    // TODO: Uncomment when enabling the Changes Panel
    // this.changes = new WebInspector.ChangesView(this.drawer);
    // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
    this.drawer.visibleView = this.console;
    this.domAgent = new WebInspector.DOMAgent();

    this.resourceCategories = {
        documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"),
        stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"),
        images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"),
        scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"),
        xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"),
        fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"),
        websocket: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSocket"), "rgb(186,186,186)"), // FIXME: Decide the color.
        other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
    };

    this.breakpointManager = new WebInspector.BreakpointManager();
    this.domBreakpointManager = new WebInspector.DOMBreakpointManager();
    this.cssModel = new WebInspector.CSSStyleModel();

    this.panels = {};
    this._createPanels();
    this._panelHistory = new WebInspector.PanelHistory();

    var toolbarElement = document.getElementById("toolbar");
    var previousToolbarItem = toolbarElement.children[0];

    this.panelOrder = [];
    for (var panelName in this.panels)
        previousToolbarItem = WebInspector.addPanelToolbarIcon(toolbarElement, this.panels[panelName], previousToolbarItem);

    this.Tips = {
        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
    };

    this.Warnings = {
        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
    };

    this.addMainEventListeners(document);

    window.addEventListener("resize", this.windowResize.bind(this), true);

    document.addEventListener("focus", this.focusChanged.bind(this), true);
    document.addEventListener("keydown", this.documentKeyDown.bind(this), false);
    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
    document.addEventListener("copy", this.documentCopy.bind(this), true);
    document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);

    var dockToggleButton = document.getElementById("dock-status-bar-item");
    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);

    if (this.attached)
        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
    else
        dockToggleButton.title = WebInspector.UIString("Dock to main window.");

    var errorWarningCount = document.getElementById("error-warning-count");
    errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
    this._updateErrorAndWarningCounts();

    this.styleChanges = 0;
    // TODO: Uncomment when enabling the Changes Panel
    // var changesElement = document.getElementById("changes-count");
    // changesElement.addEventListener("click", this.showChanges.bind(this), false);
    // this._updateErrorAndWarningCounts();

    var searchField = document.getElementById("search");
    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
    searchField.addEventListener("mousedown", this._searchFieldManualFocus.bind(this), false); // when the search field is manually selected
    searchField.addEventListener("keydown", this._searchKeyDown.bind(this), true);

    toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true);
    document.getElementById("close-button-left").addEventListener("click", this.close, true);
    document.getElementById("close-button-right").addEventListener("click", this.close, true);

    this.extensionServer.initExtensions();

    function populateInspectorState(inspectorState)
    {
        WebInspector.monitoringXHREnabled = inspectorState.monitoringXHREnabled;
        if ("pauseOnExceptionsState" in inspectorState)
            WebInspector.panels.scripts.updatePauseOnExceptionsState(inspectorState.pauseOnExceptionsState);
        if (inspectorState.resourceTrackingEnabled)
            WebInspector.panels.resources.resourceTrackingWasEnabled();
        else
            WebInspector.panels.resources.resourceTrackingWasDisabled();
    }
    InspectorBackend.getInspectorState(populateInspectorState);

    InspectorBackend.populateScriptObjects();

    // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available.
    InspectorBackend.getSupportedCSSProperties(WebInspector.CSSCompletions._load);
}

WebInspector.addPanelToolbarIcon = function(toolbarElement, panel, previousToolbarItem)
{
    var panelToolbarItem = panel.toolbarItem;
    this.panelOrder.push(panel);
    panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
    if (previousToolbarItem)
        toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
    else
        toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
    return panelToolbarItem;
}

var windowLoaded = function()
{
    var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
    if (localizedStringsURL) {
        var localizedStringsScriptElement = document.createElement("script");
        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
        localizedStringsScriptElement.type = "text/javascript";
        localizedStringsScriptElement.src = localizedStringsURL;
        document.head.appendChild(localizedStringsScriptElement);
    } else
        WebInspector.loaded();

    window.removeEventListener("load", windowLoaded, false);
    delete windowLoaded;
};

window.addEventListener("load", windowLoaded, false);

WebInspector.dispatch = function(message) {
    // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
    // This is important to LayoutTests.
    function delayDispatch()
    {
        WebInspector_syncDispatch(message);
        WebInspector.pendingDispatches--;
    }
    WebInspector.pendingDispatches++;
    setTimeout(delayDispatch, 0);
}

// This function is purposely put into the global scope for easy access.
WebInspector_syncDispatch = function(message)
{
    var messageObject = (typeof message === "string") ? JSON.parse(message) : message;

    var arguments = [];
    if (messageObject.data)
        for (var key in messageObject.data)
            arguments.push(messageObject.data[key]);

    if ("seq" in messageObject) { // just a response for some request
        if (messageObject.success)
            WebInspector.processResponse(messageObject.seq, arguments);
        else {
            WebInspector.removeResponseCallbackEntry(messageObject.seq)
            WebInspector.reportProtocolError(messageObject);
        }
        return;
    }

    if (messageObject.type === "event") {
        if (!(messageObject.event in WebInspector)) {
            console.error("Protocol Error: Attempted to dispatch an unimplemented WebInspector method '%s'", messageObject.event);
            return;
        }
        WebInspector[messageObject.event].apply(WebInspector, arguments);
    }
}

WebInspector.dispatchMessageFromBackend = function(messageObject)
{
    WebInspector.dispatch(messageObject);
}

WebInspector.reportProtocolError = function(messageObject)
{
    console.error("Protocol Error: InspectorBackend request with seq = %d failed.", messageObject.seq);
    for (var i = 0; i < messageObject.errors.length; ++i)
        console.error("    " + messageObject.errors[i]);
    WebInspector.removeResponseCallbackEntry(messageObject.seq);
}

WebInspector.windowResize = function(event)
{
    if (this.currentPanel)
        this.currentPanel.resize();
    this.drawer.resize();
}

WebInspector.windowFocused = function(event)
{
    // Fires after blur, so when focusing on either the main inspector
    // or an <iframe> within the inspector we should always remove the
    // "inactive" class.
    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
        document.body.removeStyleClass("inactive");
}

WebInspector.windowBlurred = function(event)
{
    // Leaving the main inspector or an <iframe> within the inspector.
    // We can add "inactive" now, and if we are moving the focus to another
    // part of the inspector then windowFocused will correct this.
    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
        document.body.addStyleClass("inactive");
}

WebInspector.focusChanged = function(event)
{
    this.currentFocusElement = event.target;
}

WebInspector.setAttachedWindow = function(attached)
{
    this.attached = attached;
}

WebInspector.close = function(event)
{
    if (this._isClosing)
        return;
    this._isClosing = true;
    InspectorFrontendHost.closeWindow();
}

WebInspector.disconnectFromBackend = function()
{
    InspectorFrontendHost.disconnectFromBackend();
}

WebInspector.documentClick = function(event)
{
    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
    if (!anchor || anchor.target === "_blank")
        return;

    // Prevent the link from navigating, since we don't do any navigation by following links normally.
    event.preventDefault();
    event.stopPropagation();

    function followLink()
    {
        // FIXME: support webkit-html-external-link links here.
        if (WebInspector.canShowSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel"))) {
            if (anchor.hasStyleClass("webkit-html-external-link")) {
                anchor.removeStyleClass("webkit-html-external-link");
                anchor.addStyleClass("webkit-html-resource-link");
            }

            WebInspector.showSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel"));
            return;
        }

        const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
        if (profileMatch) {
            WebInspector.showProfileForURL(anchor.href);
            return;
        }

        const urlMatch = WebInspector.GenericURLRegExp.exec(anchor.href);
        if (urlMatch && urlMatch[1] === "webkit-link-action") {
            if (urlMatch[2] === "show-panel") {
                const panel = urlMatch[4].substring(1);
                if (WebInspector.panels[panel])
                    WebInspector.currentPanel = WebInspector.panels[panel];
            }
            return;
        }

        WebInspector.showPanel("resources");
    }

    if (WebInspector.followLinkTimeout)
        clearTimeout(WebInspector.followLinkTimeout);

    if (anchor.preventFollowOnDoubleClick) {
        // Start a timeout if this is the first click, if the timeout is canceled
        // before it fires, then a double clicked happened or another link was clicked.
        if (event.detail === 1)
            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
        return;
    }

    followLink();
}

WebInspector.openResource = function(resourceURL, inResourcesPanel)
{
    var resource = WebInspector.resourceForURL(resourceURL);
    if (inResourcesPanel && resource) {
        WebInspector.panels.resources.showResource(resource);
        WebInspector.showPanel("resources");
    } else
        InspectorBackend.openInInspectedWindow(resource ? resource.url : resourceURL);
}

WebInspector._registerShortcuts = function()
{
    var shortcut = WebInspector.KeyboardShortcut;
    var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("All Panels"));
    var keys = [
        shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta),
        shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta)
    ];
    section.addRelatedKeys(keys, WebInspector.UIString("Next/previous panel"));
    section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console"));
    section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search"));
    keys = [
        shortcut.shortcutToString("g", shortcut.Modifiers.CtrlOrMeta),
        shortcut.shortcutToString("g", shortcut.Modifiers.CtrlOrMeta | shortcut.Modifiers.Shift)
    ];
    section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous"));
}

WebInspector.documentKeyDown = function(event)
{
    var isInputElement = event.target.nodeName === "INPUT";
    var isInEditMode = event.target.enclosingNodeOrSelfWithClass("text-prompt") || WebInspector.isEditingAnyField();
    const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms

    if (event.keyIdentifier === "F1" ||
        (event.keyIdentifier === helpKey && event.shiftKey && (!isInEditMode && !isInputElement || event.metaKey))) {
        WebInspector.shortcutsHelp.show();
        event.stopPropagation();
        event.preventDefault();
        return;
    }

    if (WebInspector.isEditingAnyField())
        return;

    if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) {
        this.currentFocusElement.handleKeyEvent(event);
        if (event.handled) {
            event.preventDefault();
            return;
        }
    }

    if (this.currentPanel && this.currentPanel.handleShortcut) {
        this.currentPanel.handleShortcut(event);
        if (event.handled) {
            event.preventDefault();
            return;
        }
    }

    var isMac = WebInspector.isMac();
    switch (event.keyIdentifier) {
        case "Left":
            var isBackKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
            if (isBackKey && this._panelHistory.canGoBack()) {
                this._panelHistory.goBack();
                event.preventDefault();
            }
            break;

        case "Right":
            var isForwardKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
            if (isForwardKey && this._panelHistory.canGoForward()) {
                this._panelHistory.goForward();
                event.preventDefault();
            }
            break;

        case "U+001B": // Escape key
            event.preventDefault();
            if (this.drawer.fullPanel)
                return;

            this.drawer.visible = !this.drawer.visible;
            break;

        case "U+0046": // F key
            if (isMac)
                var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
            else
                var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;

            if (isFindKey) {
                WebInspector.focusSearchField();
                event.preventDefault();
            }
            break;

        case "F3":
            if (!isMac) {
                WebInspector.focusSearchField();
                event.preventDefault();
            }
            break;

        case "U+0047": // G key
            if (isMac)
                var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
            else
                var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;

            if (isFindAgainKey) {
                if (event.shiftKey) {
                    if (this.currentPanel.jumpToPreviousSearchResult)
                        this.currentPanel.jumpToPreviousSearchResult();
                } else if (this.currentPanel.jumpToNextSearchResult)
                    this.currentPanel.jumpToNextSearchResult();
                event.preventDefault();
            }

            break;

        // Windows and Mac have two different definitions of [, so accept both.
        case "U+005B":
        case "U+00DB": // [ key
            if (isMac)
                var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
            else
                var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;

            if (isRotateLeft) {
                var index = this.panelOrder.indexOf(this.currentPanel);
                index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
                this.panelOrder[index].toolbarItem.click();
                event.preventDefault();
            }

            break;

        // Windows and Mac have two different definitions of ], so accept both.
        case "U+005D":
        case "U+00DD":  // ] key
            if (isMac)
                var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
            else
                var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;

            if (isRotateRight) {
                var index = this.panelOrder.indexOf(this.currentPanel);
                index = (index + 1) % this.panelOrder.length;
                this.panelOrder[index].toolbarItem.click();
                event.preventDefault();
            }

            break;

        case "U+0052": // R key
            if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) {
                InspectorBackend.reloadPage();
                event.preventDefault();
            }
            break;
        case "F5":
            if (!isMac)
                InspectorBackend.reloadPage();
            break;
    }
}

WebInspector.documentCanCopy = function(event)
{
    if (this.currentPanel && this.currentPanel.handleCopyEvent)
        event.preventDefault();
}

WebInspector.documentCopy = function(event)
{
    if (this.currentPanel && this.currentPanel.handleCopyEvent)
        this.currentPanel.handleCopyEvent(event);
}

WebInspector.contextMenuEventFired = function(event)
{
    if (event.handled || event.target.hasStyleClass("popup-glasspane"))
        event.preventDefault();
}

WebInspector.animateStyle = function(animations, duration, callback)
{
    var interval;
    var complete = 0;

    const intervalDuration = (1000 / 30); // 30 frames per second.
    const animationsLength = animations.length;
    const propertyUnit = {opacity: ""};
    const defaultUnit = "px";

    function cubicInOut(t, b, c, d)
    {
        if ((t/=d/2) < 1) return c/2*t*t*t + b;
        return c/2*((t-=2)*t*t + 2) + b;
    }

    // Pre-process animations.
    for (var i = 0; i < animationsLength; ++i) {
        var animation = animations[i];
        var element = null, start = null, end = null, key = null;
        for (key in animation) {
            if (key === "element")
                element = animation[key];
            else if (key === "start")
                start = animation[key];
            else if (key === "end")
                end = animation[key];
        }

        if (!element || !end)
            continue;

        if (!start) {
            var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
            start = {};
            for (key in end)
                start[key] = parseInt(computedStyle.getPropertyValue(key));
            animation.start = start;
        } else
            for (key in start)
                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
    }

    function animateLoop()
    {
        // Advance forward.
        complete += intervalDuration;
        var next = complete + intervalDuration;

        // Make style changes.
        for (var i = 0; i < animationsLength; ++i) {
            var animation = animations[i];
            var element = animation.element;
            var start = animation.start;
            var end = animation.end;
            if (!element || !end)
                continue;

            var style = element.style;
            for (key in end) {
                var endValue = end[key];
                if (next < duration) {
                    var startValue = start[key];
                    var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
                    style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
                } else
                    style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
            }
        }

        // End condition.
        if (complete >= duration) {
            clearInterval(interval);
            if (callback)
                callback();
        }
    }

    interval = setInterval(animateLoop, intervalDuration);
    return interval;
}

WebInspector.updateSearchLabel = function()
{
    if (!this.currentPanel)
        return;

    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
    if (this.attached)
        document.getElementById("search").setAttribute("placeholder", newLabel);
    else {
        document.getElementById("search").removeAttribute("placeholder");
        document.getElementById("search-toolbar-label").textContent = newLabel;
    }
}

WebInspector.focusSearchField = function()
{
    var searchField = document.getElementById("search");
    searchField.focus();
    searchField.select();
}

WebInspector.toggleAttach = function()
{
    if (!this.attached)
        InspectorFrontendHost.requestAttachWindow();
    else
        InspectorFrontendHost.requestDetachWindow();
}

WebInspector.toolbarDragStart = function(event)
{
    if ((!WebInspector.attached && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacLeopard && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacSnowLeopard) || WebInspector.port == "qt")
        return;

    var target = event.target;
    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
        return;

    var toolbar = document.getElementById("toolbar");
    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
        return;

    toolbar.lastScreenX = event.screenX;
    toolbar.lastScreenY = event.screenY;

    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
}

WebInspector.toolbarDragEnd = function(event)
{
    var toolbar = document.getElementById("toolbar");

    WebInspector.elementDragEnd(event);

    delete toolbar.lastScreenX;
    delete toolbar.lastScreenY;
}

WebInspector.toolbarDrag = function(event)
{
    var toolbar = document.getElementById("toolbar");

    if (WebInspector.attached) {
        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);

        InspectorFrontendHost.setAttachedWindowHeight(height);
    } else {
        var x = event.screenX - toolbar.lastScreenX;
        var y = event.screenY - toolbar.lastScreenY;

        // We cannot call window.moveBy here because it restricts the movement
        // of the window at the edges.
        InspectorFrontendHost.moveWindowBy(x, y);
    }

    toolbar.lastScreenX = event.screenX;
    toolbar.lastScreenY = event.screenY;

    event.preventDefault();
}

WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
{
    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
        this.elementDragEnd(event);

    this._elementDraggingEventListener = dividerDrag;
    this._elementEndDraggingEventListener = elementDragEnd;

    document.addEventListener("mousemove", dividerDrag, true);
    document.addEventListener("mouseup", elementDragEnd, true);

    document.body.style.cursor = cursor;

    event.preventDefault();
}

WebInspector.elementDragEnd = function(event)
{
    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);

    document.body.style.removeProperty("cursor");

    delete this._elementDraggingEventListener;
    delete this._elementEndDraggingEventListener;

    event.preventDefault();
}

WebInspector.toggleSearchingForNode = function()
{
    if (this.panels.elements) {
        this.showPanel("elements");
        this.panels.elements.toggleSearchingForNode();
    }
}

WebInspector.showConsole = function()
{
    this.drawer.showView(this.console);
}

WebInspector.showChanges = function()
{
    this.drawer.showView(this.changes);
}

WebInspector.showPanel = function(panel)
{
    if (!(panel in this.panels))
        panel = "elements";
    this.currentPanel = this.panels[panel];
}

WebInspector.selectDatabase = function(o)
{
    WebInspector.showPanel("storage");
    WebInspector.panels.storage.selectDatabase(o);
}

WebInspector.consoleMessagesCleared = function()
{
    WebInspector.console.clearMessages();
}

WebInspector.selectDOMStorage = function(o)
{
    WebInspector.showPanel("storage");
    WebInspector.panels.storage.selectDOMStorage(o);
}

WebInspector.updateResource = function(payload)
{
    var identifier = payload.id;
    var resource = this.resources[identifier];
    if (!resource) {
        resource = new WebInspector.Resource(identifier, payload.url);
        this.resources[identifier] = resource;
        this.resourceURLMap[resource.url] = resource;
        this.panels.resources.addResource(resource);
        this.panels.audits.resourceStarted(resource);
        if (this.panels.network)
            this.panels.network.addResource(resource);
    }

    if (payload.didRequestChange) {
        resource.domain = payload.host;
        resource.path = payload.path;
        resource.lastPathComponent = payload.lastPathComponent;
        resource.requestHeaders = payload.requestHeaders;
        resource.mainResource = payload.mainResource;
        resource.requestMethod = payload.requestMethod;
        resource.requestFormData = payload.requestFormData;
        resource.documentURL = payload.documentURL;
        if (typeof payload.webSocketRequestKey3 !== "undefined")
            resource.webSocketRequestKey3 = payload.webSocketRequestKey3;

        if (resource.mainResource)
            this.mainResource = resource;

        var match = payload.documentURL.match(WebInspector.GenericURLRegExp);
        if (match) {
            var protocol = match[1].toLowerCase();
            this._addCookieDomain(match[2]);
            this._addAppCacheDomain(match[2]);
        }
    }

    if (payload.didResponseChange) {
        resource.mimeType = payload.mimeType;
        resource.suggestedFilename = payload.suggestedFilename;
        resource.expectedContentLength = payload.expectedContentLength;
        resource.statusCode = payload.statusCode;
        resource.statusText = payload.statusText;
        resource.suggestedFilename = payload.suggestedFilename;
        resource.responseHeaders = payload.responseHeaders;
        resource.connectionID = payload.connectionID;
        resource.connectionReused = payload.connectionReused;
        resource.timing = payload.timing;
        resource.cached = payload.cached;
        if (typeof payload.webSocketChallengeResponse !== "undefined")
            resource.webSocketChallengeResponse = payload.webSocketChallengeResponse;
    }

    if (payload.didTypeChange)
        resource.type = payload.type;

    if (payload.didLengthChange)
        resource.resourceSize = payload.resourceSize;

    if (payload.didCompletionChange) {
        resource.failed = payload.failed;
        resource.finished = payload.finished;
        if (this.panels.audits)
            this.panels.audits.resourceFinished(resource);
        this.extensionServer.notifyResourceFinished(resource);
    }

    if (payload.didTimingChange) {
        if (payload.startTime)
            resource.startTime = payload.startTime;
        if (payload.responseReceivedTime)
            resource.responseReceivedTime = payload.responseReceivedTime;
        if (payload.endTime)
            resource.endTime = payload.endTime;

        if (payload.loadEventTime) {
            // This loadEventTime is for the main resource, and we want to show it
            // for all resources on this page. This means we want to set it as a member
            // of the resources panel instead of the individual resource.
            this.panels.resources.mainResourceLoadTime = payload.loadEventTime;
            this.panels.audits.mainResourceLoadTime = payload.loadEventTime;
            if (this.panels.network)
                this.panels.network.mainResourceLoadTime = payload.loadEventTime;
        }

        if (payload.domContentEventTime) {
            // This domContentEventTime is for the main resource, so it should go in
            // the resources panel for the same reasons as above.
            this.panels.resources.mainResourceDOMContentTime = payload.domContentEventTime;
            this.panels.audits.mainResourceDOMContentTime = payload.domContentEventTime;
            if (this.panels.network)
                this.panels.network.mainResourceDOMContentTime = payload.domContentEventTime;
        }
    }

    if (this.panels.network)
        this.panels.network.refreshResource(resource);
}

WebInspector.removeResource = function(identifier)
{
    var resource = this.resources[identifier];
    if (!resource)
        return;

    resource.category.removeResource(resource);
    delete this.resourceURLMap[resource.url];
    delete this.resources[identifier];

    if (this.panels.resources)
        this.panels.resources.removeResource(resource);
}

WebInspector.addDatabase = function(payload)
{
    if (!this.panels.storage)
        return;
    var database = new WebInspector.Database(
        payload.id,
        payload.domain,
        payload.name,
        payload.version);
    this.panels.storage.addDatabase(database);
}

WebInspector._addCookieDomain = function(domain)
{
    // Eliminate duplicate domains from the list.
    if (domain in this.cookieDomains)
        return;
    this.cookieDomains[domain] = true;

    if (!this.panels.storage)
        return;
    this.panels.storage.addCookieDomain(domain);
}

WebInspector._addAppCacheDomain = function(domain)
{
    // Eliminate duplicate domains from the list.
    if (domain in this.applicationCacheDomains)
        return;
    this.applicationCacheDomains[domain] = true;

    if (!this.panels.storage)
        return;
    this.panels.storage.addApplicationCache(domain);
}

WebInspector.addDOMStorage = function(payload)
{
    if (!this.panels.storage)
        return;
    var domStorage = new WebInspector.DOMStorage(
        payload.id,
        payload.host,
        payload.isLocalStorage);
    this.panels.storage.addDOMStorage(domStorage);
}

WebInspector.updateDOMStorage = function(storageId)
{
    this.panels.storage.updateDOMStorage(storageId);
}

WebInspector.updateApplicationCacheStatus = function(status)
{
    this.panels.storage.updateApplicationCacheStatus(status);
}

WebInspector.updateNetworkState = function(isNowOnline)
{
    this.panels.storage.updateNetworkState(isNowOnline);
}

WebInspector.searchingForNodeWasEnabled = function()
{
    this.panels.elements.searchingForNodeWasEnabled();
}

WebInspector.searchingForNodeWasDisabled = function()
{
    this.panels.elements.searchingForNodeWasDisabled();
}

WebInspector.attachDebuggerWhenShown = function()
{
    this.panels.scripts.attachDebuggerWhenShown();
}

WebInspector.debuggerWasEnabled = function()
{
    this.panels.scripts.debuggerWasEnabled();
}

WebInspector.debuggerWasDisabled = function()
{
    this.panels.scripts.debuggerWasDisabled();
}

WebInspector.profilerWasEnabled = function()
{
    this.panels.profiles.profilerWasEnabled();
}

WebInspector.profilerWasDisabled = function()
{
    this.panels.profiles.profilerWasDisabled();
}

WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine, scriptWorldType)
{
    this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine, undefined, undefined, scriptWorldType);
}

WebInspector.restoredBreakpoint = function(sourceID, sourceURL, line, enabled, condition)
{
    this.breakpointManager.restoredBreakpoint(sourceID, sourceURL, line, enabled, condition);
}

WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
{
    this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
}

WebInspector.pausedScript = function(details)
{
    this.panels.scripts.debuggerPaused(details);
    this.breakpointManager.debuggerPaused(details);
    InspectorFrontendHost.bringToFront();
}

WebInspector.resumedScript = function()
{
    this.breakpointManager.debuggerResumed();
    this.panels.scripts.debuggerResumed();
}

WebInspector.reset = function()
{
    for (var panelName in this.panels) {
        var panel = this.panels[panelName];
        if ("reset" in panel)
            panel.reset();
    }

    this.sessionSettings.reset();
    this.breakpointManager.reset();

    for (var category in this.resourceCategories)
        this.resourceCategories[category].removeAllResources();

    this.resources = {};
    this.resourceURLMap = {};
    this.cookieDomains = {};
    this.applicationCacheDomains = {};
    this.highlightDOMNode(0);

    delete this.mainResource;

    this.console.clearMessages();
    this.extensionServer.notifyInspectorReset();
}

WebInspector.resetProfilesPanel = function()
{
    if (WebInspector.panels.profiles)
        WebInspector.panels.profiles.resetProfiles();
}

WebInspector.bringToFront = function()
{
    InspectorFrontendHost.bringToFront();
}

WebInspector.inspectedURLChanged = function(url)
{
    InspectorFrontendHost.inspectedURLChanged(url);
    this.extensionServer.notifyInspectedURLChanged();
}

WebInspector.resourceURLChanged = function(resource, oldURL)
{
    delete this.resourceURLMap[oldURL];
    this.resourceURLMap[resource.url] = resource;
}

WebInspector.didCommitLoad = function()
{
    // Cleanup elements panel early on inspected page refresh.
    WebInspector.setDocument(null);
    this.extensionServer.notifyInspectedPageLoaded();
}

WebInspector.updateConsoleMessageExpiredCount = function(count)
{
    var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count);
    WebInspector.console.addMessage(WebInspector.ConsoleMessage.createTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning));
}

WebInspector.addConsoleMessage = function(payload)
{
    var consoleMessage = new WebInspector.ConsoleMessage(
        payload.source,
        payload.type,
        payload.level,
        payload.line,
        payload.url,
        payload.groupLevel,
        payload.repeatCount,
        payload.message,
        payload.parameters,
        payload.stackTrace);
    this.console.addMessage(consoleMessage);
}

WebInspector.updateConsoleMessageRepeatCount = function(count)
{
    this.console.updateMessageRepeatCount(count);
}

WebInspector.log = function(message, messageLevel)
{
    // remember 'this' for setInterval() callback
    var self = this;

    // return indication if we can actually log a message
    function isLogAvailable()
    {
        return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console;
    }

    // flush the queue of pending messages
    function flushQueue()
    {
        var queued = WebInspector.log.queued;
        if (!queued)
            return;

        for (var i = 0; i < queued.length; ++i)
            logMessage(queued[i]);

        delete WebInspector.log.queued;
    }

    // flush the queue if it console is available
    // - this function is run on an interval
    function flushQueueIfAvailable()
    {
        if (!isLogAvailable())
            return;

        clearInterval(WebInspector.log.interval);
        delete WebInspector.log.interval;

        flushQueue();
    }

    // actually log the message
    function logMessage(message)
    {
        var repeatCount = 1;
        if (message == WebInspector.log.lastMessage)
            repeatCount = WebInspector.log.repeatCount + 1;

        WebInspector.log.lastMessage = message;
        WebInspector.log.repeatCount = repeatCount;

        // ConsoleMessage expects a proxy object
        message = new WebInspector.RemoteObject.fromPrimitiveValue(message);

        // post the message
        var msg = new WebInspector.ConsoleMessage(
            WebInspector.ConsoleMessage.MessageSource.Other,
            WebInspector.ConsoleMessage.MessageType.Log,
            messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
            -1,
            null,
            null,
            repeatCount,
            null,
            [message],
            null);

        self.console.addMessage(msg);
    }

    // if we can't log the message, queue it
    if (!isLogAvailable()) {
        if (!WebInspector.log.queued)
            WebInspector.log.queued = [];

        WebInspector.log.queued.push(message);

        if (!WebInspector.log.interval)
            WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);

        return;
    }

    // flush the pending queue if any
    flushQueue();

    // log the message
    logMessage(message);
}

WebInspector.addProfileHeader = function(profile)
{
    this.panels.profiles.addProfileHeader(profile);
}

WebInspector.setRecordingProfile = function(isProfiling)
{
    this.panels.profiles.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
    if (this._previousIsProfiling !== isProfiling) {
        if (!this._temporaryRecordingProfile) {
            this._temporaryRecordingProfile = {
                typeId: WebInspector.CPUProfileType.TypeId,
                title: WebInspector.UIString("Recording…"),
                uid: -1,
                isTemporary: true
            };
        }
        this._previousIsProfiling = isProfiling;
        if (isProfiling)
            this.panels.profiles.addProfileHeader(this._temporaryRecordingProfile);
        else
            this.panels.profiles.removeProfileHeader(this._temporaryRecordingProfile);
    }
    this.panels.profiles.updateProfileTypeButtons();
}

WebInspector.drawLoadingPieChart = function(canvas, percent) {
    var g = canvas.getContext("2d");
    var darkColor = "rgb(122, 168, 218)";
    var lightColor = "rgb(228, 241, 251)";
    var cx = 8;
    var cy = 8;
    var r = 7;

    g.beginPath();
    g.arc(cx, cy, r, 0, Math.PI * 2, false);
    g.closePath();

    g.lineWidth = 1;
    g.strokeStyle = darkColor;
    g.fillStyle = lightColor;
    g.fill();
    g.stroke();

    var startangle = -Math.PI / 2;
    var endangle = startangle + (percent * Math.PI * 2);

    g.beginPath();
    g.moveTo(cx, cy);
    g.arc(cx, cy, r, startangle, endangle, false);
    g.closePath();

    g.fillStyle = darkColor;
    g.fill();
}

WebInspector.updateFocusedNode = function(nodeId)
{
    this._updateFocusedNode(nodeId);
    this.highlightDOMNodeForTwoSeconds(nodeId);
}

WebInspector.displayNameForURL = function(url)
{
    if (!url)
        return "";
    var resource = this.resourceURLMap[url];
    if (resource)
        return resource.displayName;

    if (!WebInspector.mainResource)
        return url.trimURL("");

    var lastPathComponent = WebInspector.mainResource.lastPathComponent;
    var index = WebInspector.mainResource.url.indexOf(lastPathComponent);
    if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) {
        var baseURL = WebInspector.mainResource.url.substring(0, index);
        if (url.indexOf(baseURL) === 0)
            return url.substring(index);
    }

    return url.trimURL(WebInspector.mainResource.domain);
}

WebInspector.resourceForURL = function(url)
{
    if (url in this.resourceURLMap)
        return this.resourceURLMap[url];

    // No direct match found. Search for resources that contain
    // a substring of the URL.
    for (var resourceURL in this.resourceURLMap) {
        if (resourceURL.hasSubstring(url))
            return this.resourceURLMap[resourceURL];
    }

    return null;
}

WebInspector._choosePanelToShowSourceLine = function(url, line, preferredPanel)
{
    preferredPanel = preferredPanel || "resources";
    var panel = this.panels[preferredPanel];
    if (panel && panel.canShowSourceLine(url, line))
        return panel;
    panel = this.panels.resources;
    return panel.canShowSourceLine(url, line) ? panel : null;
}

WebInspector.canShowSourceLine = function(url, line, preferredPanel)
{
    return !!this._choosePanelToShowSourceLine(url, line, preferredPanel);
}

WebInspector.showSourceLine = function(url, line, preferredPanel)
{
    this.currentPanel = this._choosePanelToShowSourceLine(url, line, preferredPanel);
    if (!this.currentPanel)
        return false;
    this.currentPanel.showSourceLine(url, line);
    return true;
}

WebInspector.linkifyStringAsFragment = function(string)
{
    var container = document.createDocumentFragment();
    var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;

    while (string) {
        var linkString = linkStringRegEx.exec(string);
        if (!linkString)
            break;

        linkString = linkString[0];
        var title = linkString;
        var linkIndex = string.indexOf(linkString);
        var nonLink = string.substring(0, linkIndex);
        container.appendChild(document.createTextNode(nonLink));

        var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
        if (profileStringMatches)
            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);

        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
        container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
        string = string.substring(linkIndex + linkString.length, string.length);
    }

    if (string)
        container.appendChild(document.createTextNode(string));

    return container;
}

WebInspector.showProfileForURL = function(url)
{
    WebInspector.showPanel("profiles");
    WebInspector.panels.profiles.showProfileForURL(url);
}

WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
{
    if (!linkText)
        linkText = url;
    classes = (classes ? classes + " " : "");
    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";

    var a = document.createElement("a");
    a.href = url;
    a.className = classes;
    if (typeof tooltipText === "undefined")
        a.title = url;
    else if (typeof tooltipText !== "string" || tooltipText.length)
        a.title = tooltipText;
    a.textContent = linkText;

    return a;
}

WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
{
    // Use the DOM version of this function so as to avoid needing to escape attributes.
    // FIXME:  Get rid of linkifyURL entirely.
    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
}

WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, classes, tooltipText)
{
    var linkText = WebInspector.displayNameForURL(url);
    if (lineNumber)
        linkText += ":" + lineNumber;
    var node = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText);
    node.setAttribute("line_number", lineNumber);
    node.setAttribute("preferred_panel", preferredPanel);
    return node;
}

WebInspector.resourceURLForRelatedNode = function(node, url)
{
    if (!url || url.indexOf("://") > 0)
        return url;

    for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
        if (frameOwnerCandidate.documentURL) {
            var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url);
            if (result)
                return result;
            break;
        }
    }

    // documentURL not found or has bad value
    for (var resourceURL in WebInspector.resourceURLMap) {
        var match = resourceURL.match(WebInspector.URLRegExp);
        if (match && match[4] === url)
            return resourceURL;
    }
    return url;
},

WebInspector.completeURL = function(baseURL, href)
{
    var match = baseURL.match(WebInspector.URLRegExp);
    if (match) {
        var path = href;
        if (path.charAt(0) !== "/") {
            var basePath = match[4] || "/";
            path = basePath.substring(0, basePath.lastIndexOf("/")) + "/" + path;
        } else if (path.length > 1 && path.charAt(1) === "/") {
            // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
            return match[1] + ":" + path;
        }
        return match[1] + "://" + match[2] + (match[3] ? (":" + match[3]) : "") + path;
    }
    return null;
}

WebInspector.addMainEventListeners = function(doc)
{
    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
    doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
    doc.addEventListener("click", this.documentClick.bind(this), true);
}

WebInspector._searchFieldManualFocus = function(event)
{
    this.currentFocusElement = event.target;
    this._previousFocusElement = event.target;
}

WebInspector._searchKeyDown = function(event)
{
    // Escape Key will clear the field and clear the search results
    if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
        // If focus belongs here and text is empty - nothing to do, return unhandled.
        if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement)
            return;
        event.preventDefault();
        event.stopPropagation();
        // When search was selected manually and is currently blank, we'd like Esc stay unhandled
        // and hit console drawer handler.
        event.target.value = "";

        this.performSearch(event);
        this.currentFocusElement = this.previousFocusElement;
        if (this.currentFocusElement === event.target)
            this.currentFocusElement.select();
        return false;
    }

    if (!isEnterKey(event))
        return false;

    // Select all of the text so the user can easily type an entirely new query.
    event.target.select();

    // Only call performSearch if the Enter key was pressed. Otherwise the search
    // performance is poor because of searching on every key. The search field has
    // the incremental attribute set, so we still get incremental searches.
    this.performSearch(event);

    // Call preventDefault since this was the Enter key. This prevents a "search" event
    // from firing for key down. This stops performSearch from being called twice in a row.
    event.preventDefault();
}

WebInspector.performSearch = function(event)
{
    var forceSearch = event.keyIdentifier === "Enter";
    this.doPerformSearch(event.target.value, forceSearch, event.shiftKey, false);
}

WebInspector.doPerformSearch = function(query, forceSearch, isBackwardSearch, repeatSearch)
{
    var isShortSearch = (query.length < 3);

    // Clear a leftover short search flag due to a non-conflicting forced search.
    if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query)
        delete this.shortSearchWasForcedByKeyEvent;

    // Indicate this was a forced search on a short query.
    if (isShortSearch && forceSearch)
        this.shortSearchWasForcedByKeyEvent = true;

    if (!query || !query.length || (!forceSearch && isShortSearch)) {
        // Prevent clobbering a short search forced by the user.
        if (this.shortSearchWasForcedByKeyEvent) {
            delete this.shortSearchWasForcedByKeyEvent;
            return;
        }

        delete this.currentQuery;

        for (var panelName in this.panels) {
            var panel = this.panels[panelName];
            if (panel.currentQuery && panel.searchCanceled)
                panel.searchCanceled();
            delete panel.currentQuery;
        }

        this.updateSearchMatchesCount();

        return;
    }

    if (!repeatSearch && query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
        // When this is the same query and a forced search, jump to the next
        // search result for a good user experience.
        if (forceSearch) {
            if (!isBackwardSearch && this.currentPanel.jumpToNextSearchResult)
                this.currentPanel.jumpToNextSearchResult();
            else if (isBackwardSearch && this.currentPanel.jumpToPreviousSearchResult)
                this.currentPanel.jumpToPreviousSearchResult();
        }
        return;
    }

    this.currentQuery = query;

    this.updateSearchMatchesCount();

    if (!this.currentPanel.performSearch)
        return;

    this.currentPanel.currentQuery = query;
    this.currentPanel.performSearch(query);
}

WebInspector.addNodesToSearchResult = function(nodeIds)
{
    WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
}

WebInspector.updateSearchMatchesCount = function(matches, panel)
{
    if (!panel)
        panel = this.currentPanel;

    panel.currentSearchMatches = matches;

    if (panel !== this.currentPanel)
        return;

    if (!this.currentPanel.currentQuery) {
        document.getElementById("search-results-matches").addStyleClass("hidden");
        return;
    }

    if (matches) {
        if (matches === 1)
            var matchesString = WebInspector.UIString("1 match");
        else
            var matchesString = WebInspector.UIString("%d matches", matches);
    } else
        var matchesString = WebInspector.UIString("Not Found");

    var matchesToolbarElement = document.getElementById("search-results-matches");
    matchesToolbarElement.removeStyleClass("hidden");
    matchesToolbarElement.textContent = matchesString;
}

WebInspector.UIString = function(string)
{
    if (window.localizedStrings && string in window.localizedStrings)
        string = window.localizedStrings[string];
    else {
        if (!(string in WebInspector.missingLocalizedStrings)) {
            if (!WebInspector.InspectorBackendStub)
                console.error("Localized string \"" + string + "\" not found.");
            WebInspector.missingLocalizedStrings[string] = true;
        }

        if (Preferences.showMissingLocalizedStrings)
            string += " (not localized)";
    }

    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
}

WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
{
    return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
}

WebInspector.isMac = function()
{
    if (!("_isMac" in this))
        this._isMac = WebInspector.platform === "mac";

    return this._isMac;
}

WebInspector.isBeingEdited = function(element)
{
    return element.__editing;
}

WebInspector.isEditingAnyField = function()
{
    return this.__editing;
}

WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context, multiline)
{
    if (element.__editing)
        return;
    element.__editing = true;
    WebInspector.__editing = true;

    var oldText = getContent(element);
    var moveDirection = "";

    element.addStyleClass("editing");

    var oldTabIndex = element.tabIndex;
    if (element.tabIndex < 0)
        element.tabIndex = 0;

    function blurEventListener() {
        editingCommitted.call(element);
    }

    function getContent(element) {
        if (element.tagName === "INPUT" && element.type === "text")
            return element.value;
        else
            return element.textContent;
    }

    function cleanUpAfterEditing() {
        delete this.__editing;
        delete WebInspector.__editing;

        this.removeStyleClass("editing");
        this.tabIndex = oldTabIndex;
        this.scrollTop = 0;
        this.scrollLeft = 0;

        element.removeEventListener("blur", blurEventListener, false);
        element.removeEventListener("keydown", keyDownEventListener, true);

        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
    }

    function editingCancelled() {
        if (this.tagName === "INPUT" && this.type === "text")
            this.value = oldText;
        else
            this.textContent = oldText;

        cleanUpAfterEditing.call(this);

        if (cancelledCallback)
            cancelledCallback(this, context);
    }

    function editingCommitted() {
        cleanUpAfterEditing.call(this);

        if (committedCallback)
            committedCallback(this, getContent(this), oldText, context, moveDirection);
    }

    function keyDownEventListener(event) {
        var isMetaOrCtrl = WebInspector.isMac() ?
            event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
            event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
        if (isEnterKey(event) && (!multiline || isMetaOrCtrl)) {
            editingCommitted.call(element);
            event.preventDefault();
            event.stopPropagation();
        } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
            editingCancelled.call(element);
            event.preventDefault();
            event.stopPropagation();
        } else if (event.keyIdentifier === "U+0009") // Tab key
            moveDirection = (event.shiftKey ? "backward" : "forward");
    }

    element.addEventListener("blur", blurEventListener, false);
    element.addEventListener("keydown", keyDownEventListener, true);

    WebInspector.currentFocusElement = element;
    return {
        cancel: editingCancelled.bind(element),
        commit: editingCommitted.bind(element)
    };
}

WebInspector._toolbarItemClicked = function(event)
{
    var toolbarItem = event.currentTarget;
    this.currentPanel = toolbarItem.panel;
}

// This table maps MIME types to the Resource.Types which are valid for them.
// The following line:
//    "text/html":                {0: 1},
// means that text/html is a valid MIME type for resources that have type
// WebInspector.Resource.Type.Document (which has a value of 0).
WebInspector.MIMETypes = {
    "text/html":                   {0: true},
    "text/xml":                    {0: true},
    "text/plain":                  {0: true},
    "application/xhtml+xml":       {0: true},
    "text/css":                    {1: true},
    "text/xsl":                    {1: true},
    "image/jpeg":                  {2: true},
    "image/png":                   {2: true},
    "image/gif":                   {2: true},
    "image/bmp":                   {2: true},
    "image/vnd.microsoft.icon":    {2: true},
    "image/x-icon":                {2: true},
    "image/x-xbitmap":             {2: true},
    "font/ttf":                    {3: true},
    "font/opentype":               {3: true},
    "application/x-font-type1":    {3: true},
    "application/x-font-ttf":      {3: true},
    "application/x-truetype-font": {3: true},
    "text/javascript":             {4: true},
    "text/ecmascript":             {4: true},
    "application/javascript":      {4: true},
    "application/ecmascript":      {4: true},
    "application/x-javascript":    {4: true},
    "text/javascript1.1":          {4: true},
    "text/javascript1.2":          {4: true},
    "text/javascript1.3":          {4: true},
    "text/jscript":                {4: true},
    "text/livescript":             {4: true},
}

WebInspector.PanelHistory = function()
{
    this._history = [];
    this._historyIterator = -1;
}

WebInspector.PanelHistory.prototype = {
    canGoBack: function()
    {
        return this._historyIterator > 0;
    },

    goBack: function()
    {
        this._inHistory = true;
        WebInspector.currentPanel = WebInspector.panels[this._history[--this._historyIterator]];
        delete this._inHistory;
    },

    canGoForward: function()
    {
        return this._historyIterator < this._history.length - 1;
    },

    goForward: function()
    {
        this._inHistory = true;
        WebInspector.currentPanel = WebInspector.panels[this._history[++this._historyIterator]];
        delete this._inHistory;
    },

    setPanel: function(panelName)
    {
        if (this._inHistory)
            return;

        this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
        if (!this._history.length || this._history[this._history.length - 1] !== panelName)
            this._history.push(panelName);
        this._historyIterator = this._history.length - 1;
    }
}

/* InspectorBackendStub.js */

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


WebInspector.InspectorBackendStub = function()
{
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "populateScriptObjects", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "getSettings", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "getInspectorState", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "storeLastActivePanel", "arguments": {"panelName": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "saveApplicationSettings", "arguments": {"settings": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "saveSessionSettings", "arguments": {"settings": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "setSearchingForNode", "arguments": {"enabled": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "setMonitoringXHREnabled", "arguments": {"enable": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "setResourceTrackingEnabled", "arguments": {"enabled": "boolean","always": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "getResourceContent", "arguments": {"identifier": "number","encode": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "reloadPage", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "startTimelineProfiler", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "stopTimelineProfiler", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "enableDebugger", "arguments": {"always": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "disableDebugger", "arguments": {"always": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "setBreakpoint", "arguments": {"sourceID": "string","lineNumber": "number","enabled": "boolean","condition": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "removeBreakpoint", "arguments": {"sourceID": "string","lineNumber": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "activateBreakpoints", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "deactivateBreakpoints", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "pause", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "resume", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "stepOverStatement", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "stepIntoStatement", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "stepOutOfFunction", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "setPauseOnExceptionsState", "arguments": {"pauseOnExceptionsState": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "editScriptSource", "arguments": {"sourceID": "string","newContent": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Debug", "command": "getScriptSource", "arguments": {"sourceID": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "setNativeBreakpoint", "arguments": {"breakpoint": "object"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "removeNativeBreakpoint", "arguments": {"breakpointId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "enableProfiler", "arguments": {"always": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "disableProfiler", "arguments": {"always": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "startProfiling", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "stopProfiling", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "getProfileHeaders", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "getProfile", "arguments": {"type": "string","uid": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "removeProfile", "arguments": {"type": "string","uid": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "clearProfiles", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Profiler", "command": "takeHeapSnapshot", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "setInjectedScriptSource", "arguments": {"scriptSource": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "dispatchOnInjectedScript", "arguments": {"injectedScriptId": "number","methodName": "string","arguments": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "addScriptToEvaluateOnLoad", "arguments": {"scriptSource": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "removeAllScriptsToEvaluateOnLoad", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getChildNodes", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setAttribute", "arguments": {"elementId": "number","name": "string","value": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "removeAttribute", "arguments": {"elementId": "number","name": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setTextNodeValue", "arguments": {"nodeId": "number","value": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getEventListenersForNode", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "copyNode", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "removeNode", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "changeTagName", "arguments": {"nodeId": "number","newTagName": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getOuterHTML", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setOuterHTML", "arguments": {"nodeId": "number","outerHTML": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "addInspectedNode", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "performSearch", "arguments": {"query": "string","runSynchronously": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "searchCanceled", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "pushNodeByPathToFrontend", "arguments": {"path": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setDOMBreakpoint", "arguments": {"nodeId": "number","type": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "removeDOMBreakpoint", "arguments": {"nodeId": "number","type": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "clearConsoleMessages", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "highlightDOMNode", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "hideDOMNodeHighlight", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "openInInspectedWindow", "arguments": {"url": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getStyles", "arguments": {"nodeId": "number","authOnly": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getAllStyles", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getInlineStyle", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getComputedStyle", "arguments": {"nodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getStyleSheet", "arguments": {"styleSheetId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getStyleSourceData", "arguments": {"styleSheetId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "applyStyleText", "arguments": {"styleId": "number","styleText": "string","propertyName": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setStyleText", "arguments": {"styleId": "number","styleText": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setStyleProperty", "arguments": {"styleId": "number","name": "string","value": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "toggleStyleEnabled", "arguments": {"styleId": "number","propertyName": "string","disabled": "boolean"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "setRuleSelector", "arguments": {"ruleId": "number","selector": "string","selectedNodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "addRule", "arguments": {"selector": "string","selectedNodeId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "DOM", "command": "getSupportedCSSProperties", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "getCookies", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "deleteCookie", "arguments": {"cookieName": "string","domain": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "ApplicationCache", "command": "getApplicationCaches", "arguments": {}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "releaseWrapperObjectGroup", "arguments": {"injectedScriptId": "number","objectGroup": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "didEvaluateForTestInFrontend", "arguments": {"testCallId": "number","jsonResult": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "getDatabaseTableNames", "arguments": {"databaseId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Backend", "command": "executeSQL", "arguments": {"databaseId": "number","query": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "getDOMStorageEntries", "arguments": {"storageId": "number"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "setDOMStorageItem", "arguments": {"storageId": "number","key": "string","value": "string"}}');
    this._registerDelegate('{"seq": 0, "domain": "Controller", "command": "removeDOMStorageItem", "arguments": {"storageId": "number","key": "string"}}');
}

WebInspector.InspectorBackendStub.prototype = {
    _registerDelegate: function(commandInfo)
    {
        var commandObject = JSON.parse(commandInfo);
        this[commandObject.command] = this.sendMessageToBackend.bind(this, commandInfo);
    },

    sendMessageToBackend: function()
    {
        var args = Array.prototype.slice.call(arguments);
        var request = JSON.parse(args.shift());

        for (var key in request.arguments) {
            if (args.length === 0) {
                console.error("Protocol Error: Invalid number of arguments for 'InspectorBackend.%s' call. It should have the next arguments '%s'.", request.command, JSON.stringify(request.arguments));
                return;
            }
            var value = args.shift();
            if (typeof value !== request.arguments[key]) {
                console.error("Protocol Error: Invalid type of argument '%s' for 'InspectorBackend.%s' call. It should be '%s' but it is '%s'.", key, request.command, request.arguments[key], typeof value);
                return;
            }
            request.arguments[key] = value;
        }

        if (args.length === 1) {
            if (typeof args[0] !== "function" && typeof args[0] !== "undefined") {
                console.error("Protocol Error: Optional callback argument for 'InspectorBackend.%s' call should be a function but its type is '%s'.", request.command, typeof args[0]);
                return;
            }
            request.seq = WebInspector.Callback.wrap(args[0]);
        }

        var message = JSON.stringify(request);
        InspectorFrontendHost.sendMessageToBackend(message);
    }
}

InspectorBackend = new WebInspector.InspectorBackendStub();
/* InspectorFrontendHostStub.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

if (!window.InspectorFrontendHost) {

WebInspector.InspectorFrontendHostStub = function()
{
    this._attachedWindowHeight = 0;
}

WebInspector._platformFlavor = WebInspector.PlatformFlavor.MacLeopard;

WebInspector.InspectorFrontendHostStub.prototype = {
    platform: function()
    {
        var match = navigator.userAgent.match(/Windows NT/);
        if (match)
            return "windows";
        match = navigator.userAgent.match(/Mac OS X/);
        if (match)
            return "mac";
        return "linux";
    },

    port: function()
    {
        return "unknown";
    },

    bringToFront: function()
    {
        this._windowVisible = true;
    },

    closeWindow: function()
    {
        this._windowVisible = false;
    },

    disconnectFromBackend: function()
    {
        this._windowVisible = false;
    },

    attach: function()
    {
    },

    detach: function()
    {
    },

    search: function(sourceRow, query)
    {
    },

    setAttachedWindowHeight: function(height)
    {
    },

    moveWindowBy: function(x, y)
    {
    },

    setExtensionAPI: function(script)
    {
    },

    loaded: function()
    {
    },

    localizedStringsURL: function()
    {
        return undefined;
    },

    hiddenPanels: function()
    {
        return "";
    },

    inspectedURLChanged: function(url)
    {
    },

    copyText: function()
    {
    },

    canAttachWindow: function()
    {
        return false;
    },

    sendMessageToBackend: function(message)
    {
    }
}

InspectorFrontendHost = new WebInspector.InspectorFrontendHostStub();

}

/* ExtensionRegistryStub.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

if (!window.InspectorExtensionRegistry) {

WebInspector.InspectorExtensionRegistryStub = function()
{
}

WebInspector.InspectorExtensionRegistryStub.prototype = {
    getExtensionsAsync: function()
    {
    }
};

InspectorExtensionRegistry = new WebInspector.InspectorExtensionRegistryStub();

}

/* Object.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Object = function() {
}

WebInspector.Object.prototype = {
    addEventListener: function(eventType, listener, thisObject)
    {
        if (!("_listeners" in this))
            this._listeners = {};
        if (!(eventType in this._listeners))
            this._listeners[eventType] = [];
        this._listeners[eventType].push({ thisObject: thisObject, listener: listener });
    },

    removeEventListener: function(eventType, listener, thisObject)
    {
        if (!("_listeners" in this) || !(eventType in this._listeners))
            return;
        var listeners = this._listeners[eventType];
        for (var i = 0; i < listeners.length; ++i) {
            if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
                listeners.splice(i, 1);
            else if (!listener && thisObject && listeners[i].thisObject === thisObject)
                listeners.splice(i, 1);
        }

        if (!listeners.length)
            delete this._listeners[eventType];
    },

    removeAllListeners: function()
    {
        delete this._listeners;
    },

    dispatchEventToListeners: function(eventType, eventData)
    {
        if (!("_listeners" in this) || !(eventType in this._listeners))
            return;

        var stoppedPropagation = false;

        function stopPropagation()
        {
            stoppedPropagation = true;
        }

        function preventDefault()
        {
            this.defaultPrevented = true;
        }

        var event = {target: this, type: eventType, data: eventData, defaultPrevented: false};
        event.stopPropagation = stopPropagation;
        event.preventDefault = preventDefault;

        var listeners = this._listeners[eventType].slice(0);
        for (var i = 0; i < listeners.length; ++i) {
            listeners[i].listener.call(listeners[i].thisObject, event);
            if (stoppedPropagation)
                break;
        }

        return event.defaultPrevented;
    }
}

/* Settings.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


var Preferences = {
    canEditScriptSource: false,
    maxInlineTextChildLength: 80,
    minConsoleHeight: 75,
    minSidebarWidth: 100,
    minElementsSidebarWidth: 200,
    minScriptsSidebarWidth: 200,
    styleRulesExpandedState: {},
    showMissingLocalizedStrings: false,
    samplingCPUProfiler: false,
    showColorNicknames: true,
    debuggerAlwaysEnabled: false,
    profilerAlwaysEnabled: false,
    onlineDetectionEnabled: true,
    nativeInstrumentationEnabled: false,
    resourceExportEnabled: false,
    networkPanelEnabled: false
}

WebInspector.Settings = function(sessionScope)
{
    this._sessionScope = sessionScope;
    this._store = {};
}

WebInspector.Settings.initialize = function()
{
    WebInspector.applicationSettings = new WebInspector.Settings(false);
    WebInspector.sessionSettings = new WebInspector.Settings(true);

    function populateApplicationSettings(settingsString)
    {
        WebInspector.applicationSettings._load(settingsString);
        WebInspector.applicationSettings.installSetting("eventListenersFilter", "event-listeners-filter", "all");
        WebInspector.applicationSettings.installSetting("colorFormat", "color-format", "hex");
        WebInspector.applicationSettings.installSetting("resourcesLargeRows", "resources-large-rows", true);
        WebInspector.applicationSettings.installSetting("watchExpressions", "watch-expressions", []);
        WebInspector.applicationSettings.installSetting("lastViewedScriptFile", "last-viewed-script-file");
        WebInspector.applicationSettings.installSetting("showInheritedComputedStyleProperties", "show-inherited-computed-style-properties", false);
        WebInspector.applicationSettings.installSetting("showUserAgentStyles", "show-user-agent-styles", true);
        WebInspector.applicationSettings.installSetting("resourceViewTab", "resource-view-tab", "content");
        WebInspector.applicationSettings.installSetting("consoleHistory", "console-history", []);
        WebInspector.applicationSettings.installSetting("resourcesSortOptions", "resources-sort-options", {timeOption: "responseTime", sizeOption: "transferSize"});

        WebInspector.applicationSettings.dispatchEventToListeners("loaded");
    }

    function populateSessionSettings(settingsString)
    {
        WebInspector.sessionSettings._load(settingsString);
        WebInspector.sessionSettings.dispatchEventToListeners("loaded");
    }

    InspectorBackend.getSettings(function(settings) {
        populateApplicationSettings(settings.application);
        populateSessionSettings(settings.session);
    });
}

WebInspector.Settings.prototype = {
    reset: function()
    {
        this._store = {};
        // FIXME: restore default values (bug 42820)
        this.dispatchEventToListeners("loaded");
    },

    _load: function(settingsString)
    {
        try {
            var loadedStore = JSON.parse(settingsString);
        } catch (e) {
            // May fail;
            loadedStore = {};
        }
        if (!loadedStore)
            return;
        for (var propertyName in loadedStore)
            this._store[propertyName] = loadedStore[propertyName];
    },

    installSetting: function(name, propertyName, defaultValue)
    {
        this.__defineGetter__(name, this._get.bind(this, propertyName));
        this.__defineSetter__(name, this._set.bind(this, propertyName));
        if (!(propertyName in this._store))
            this._store[propertyName] = defaultValue;
    },

    _get: function(propertyName)
    {
        return this._store[propertyName];
    },

    _set: function(propertyName, newValue)
    {
        this._store[propertyName] = newValue;
        try {
            var store = JSON.stringify(this._store);
            if (this._sessionScope)
                InspectorBackend.saveSessionSettings(store);
            else
                InspectorBackend.saveApplicationSettings(store);
        } catch (e) {
            // May fail;
        }
    }
}

WebInspector.Settings.prototype.__proto__ = WebInspector.Object.prototype;

/* CSSStyleModel.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.CSSStyleModel = function()
{
}

WebInspector.CSSStyleModel.prototype = {
    getStylesAsync: function(nodeId, authOnly, userCallback)
    {
        InspectorBackend.getStyles(nodeId, authOnly, userCallback);
    },

    getComputedStyleAsync: function(nodeId, userCallback)
    {
        InspectorBackend.getComputedStyle(nodeId, userCallback);
    },

    setRuleSelector: function(ruleId, newContent, nodeId, successCallback, failureCallback)
    {
        function callback(newRulePayload, doesAffectSelectedNode)
        {
            if (!newRulePayload)
                failureCallback();
            else
                successCallback(WebInspector.CSSStyleDeclaration.parseRule(newRulePayload), doesAffectSelectedNode);
        }

        InspectorBackend.setRuleSelector(ruleId, newContent, nodeId, callback);
    },

    addRule: function(nodeId, newContent, successCallback, failureCallback)
    {
        function callback(rule, doesAffectSelectedNode)
        {
            if (!rule) {
                // Invalid syntax for a selector
                failureCallback();
            } else {
                var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule);
                styleRule.rule = rule;
                successCallback(styleRule, doesAffectSelectedNode);
            }
        }

        InspectorBackend.addRule(newContent, nodeId, callback);
    },

    toggleStyleEnabled: function(styleId, propertyName, disabled, userCallback)
    {
        function callback(newPayload)
        {
            if (!newPayload) {
                userCallback(null);
                return;
            }

            var newStyle = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
            userCallback(newStyle);
        }

        InspectorBackend.toggleStyleEnabled(styleId, propertyName, disabled, callback);
    },

    setCSSText: function(styleId, cssText)
    {
        InspectorBackend.setStyleText(styleId, cssText);
    },

    applyStyleText: function(styleId, styleText, propertyName, successCallback, failureCallback)
    {
        function callback(success, newPayload, changedProperties)
        {
            if (!success)
                failureCallback();
            else {
                var newStyle = newPayload ? WebInspector.CSSStyleDeclaration.parseStyle(newPayload) : null;
                successCallback(newStyle, changedProperties);
            }
        }

        InspectorBackend.applyStyleText(styleId, styleText, propertyName, callback);
    }
}

/* Checkbox.js */

/*
 * Copyright (C) 2010 Google Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Checkbox = function(label, className, tooltip)
{
    this.element = document.createElement('label');
    this._inputElement = document.createElement('input');
    this._inputElement.type = "checkbox";

    this.element.className = className;
    this.element.appendChild(this._inputElement);
    this.element.appendChild(document.createTextNode(label));
    if (tooltip)
        this.element.title = tooltip;
}

WebInspector.Checkbox.prototype = {
    set checked(checked)
    {
        this._inputElement.checked = checked;
    },

    get checked()
    {
        return this._inputElement.checked;
    },

    addEventListener: function(listener)
    {
        function listenerWrapper(event)
        {
            if (listener)
                listener(event);
            event.stopPropagation();
            return true;
        }

        this._inputElement.addEventListener("click", listenerWrapper, false);
        this.element.addEventListener("click", listenerWrapper, false);
    }
}

/* ContextMenu.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ContextMenu = function() {
    this._items = [];
    this._handlers = {};
}

WebInspector.ContextMenu.prototype = {
    show: function(event)
    {
        // Remove trailing separator.
        while (this._items.length > 0 && !("id" in this._items[this._items.length - 1]))
            this._items.splice(this._items.length - 1, 1);

        if (this._items.length) {
            WebInspector._contextMenu = this;
            InspectorFrontendHost.showContextMenu(event, this._items);
        }
    },

    appendItem: function(label, handler, disabled)
    {
        var id = this._items.length;
        this._items.push({type: "item", id: id, label: label, enabled: !disabled});
        this._handlers[id] = handler;
    },

    appendCheckboxItem: function(label, handler, checked, disabled)
    {
        var id = this._items.length;
        this._items.push({type: "checkbox", id: id, label: label, checked: !!checked, enabled: !disabled});
        this._handlers[id] = handler;
    },

    appendSeparator: function()
    {
        // No separator dupes allowed.
        if (this._items.length === 0)
            return;
        if (!("id" in this._items[this._items.length - 1]))
            return;
        this._items.push({type: "separator"});
    },

    _itemSelected: function(id)
    {
        if (this._handlers[id])
            this._handlers[id].call(this);
    }
}

WebInspector.contextMenuItemSelected = function(id)
{
    if (WebInspector._contextMenu)
        WebInspector._contextMenu._itemSelected(id);
}

WebInspector.contextMenuCleared = function()
{
    // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected
    // so we can't delete last menu object from WebInspector. Fix the contract.
}

/* KeyboardShortcut.js */

/*
 * Copyright (C) 2009 Apple Inc. All rights reserved.
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.KeyboardShortcut = function()
{
};

/**
 * Constants for encoding modifier key set as a bit mask.
 * @see #_makeKeyFromCodeAndModifiers
 */
WebInspector.KeyboardShortcut.Modifiers = {
    None: 0,   // Constant for empty modifiers set.
    Shift: 1,
    Ctrl: 2,
    Alt: 4,
    Meta: 8,   // Command key on Mac, Win key on other platforms.
    get CtrlOrMeta()
    {
        // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms
        return WebInspector.isMac() ? this.Meta : this.Ctrl;
    }
};

WebInspector.KeyboardShortcut.Keys = {
    Backspace: { code: 8, name: "\u21a4" },
    Tab: { code: 9, name: { mac: "\u21e5", other: "<Tab>" } },
    Enter: { code: 13, name: { mac: "\u21a9", other: "<Enter>" } },
    Esc: { code: 27, name: { mac: "\u238b", other: "<Esc>" } },
    Space: { code: 32, name: "<Space>" },
    PageUp: { code: 33,  name: { mac: "\u21de", other: "<PageUp>" } },      // also NUM_NORTH_EAST
    PageDown: { code: 34, name: { mac: "\u21df", other: "<PageDown>" } },   // also NUM_SOUTH_EAST
    End: { code: 35, name: { mac: "\u2197", other: "<End>" } },             // also NUM_SOUTH_WEST
    Home: { code: 36, name: { mac: "\u2196", other: "<Home>" } },           // also NUM_NORTH_WEST
    Left: { code: 37, name: "\u2190" },           // also NUM_WEST
    Up: { code: 38, name: "\u2191" },             // also NUM_NORTH
    Right: { code: 39, name: "\u2192" },          // also NUM_EAST
    Down: { code: 40, name: "\u2193" },           // also NUM_SOUTH
    Delete: { code: 46, name: "<Del>" },
    Zero: { code: 48, name: "0" },
    F1: { code: 112, name: "F1" },
    F2: { code: 113, name: "F2" },
    F3: { code: 114, name: "F3" },
    F4: { code: 115, name: "F4" },
    F5: { code: 116, name: "F5" },
    F6: { code: 117, name: "F6" },
    F7: { code: 118, name: "F7" },
    F8: { code: 119, name: "F8" },
    F9: { code: 120, name: "F9" },
    F10: { code: 121, name: "F10" },
    F11: { code: 122, name: "F11" },
    F12: { code: 123, name: "F12" },
    Semicolon: { code: 186, name: ";" },
    Plus: { code: 187, name: "+" },
    Comma: { code: 188, name: "," },
    Minus: { code: 189, name: "-" },
    Period: { code: 190, name: "." },
    Slash: { code: 191, name: "/" },
    Apostrophe: { code: 192, name: "`" },
    SingleQuote: { code: 222, name: "\'" }
};

/**
 * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits.
 * It is useful for matching pressed keys.
 * keyCode is the Code of the key, or a character "a-z" which is converted to a keyCode value.
 * optModifiers is an Optional list of modifiers passed as additional paramerters.
 */
WebInspector.KeyboardShortcut.makeKey = function(keyCode, optModifiers)
{
    if (typeof keyCode === "string")
        keyCode = keyCode.charCodeAt(0) - 32;
    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
    for (var i = 1; i < arguments.length; i++)
        modifiers |= arguments[i];
    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers);
};

WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent)
{
    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
    if (keyboardEvent.shiftKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift;
    if (keyboardEvent.ctrlKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl;
    if (keyboardEvent.altKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt;
    if (keyboardEvent.metaKey)
        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta;
    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers);
};

WebInspector.KeyboardShortcut.makeDescriptor = function(key, optModifiers)
{
    var modifiers = 0;
    for (var i = 1; i < arguments.length; i++)
        modifiers |= arguments[i];

    return {
        key: WebInspector.KeyboardShortcut.makeKey(typeof key === "string" ? key : key.code, modifiers),
        name: WebInspector.KeyboardShortcut.shortcutToString(key, modifiers)
    };
}

WebInspector.KeyboardShortcut.shortcutToString = function(key, modifiers)
{
    return WebInspector.KeyboardShortcut._modifiersToString(modifiers) + WebInspector.KeyboardShortcut._keyName(key);
}

WebInspector.KeyboardShortcut._keyName = function(key)
{
    if (typeof key === "string")
        return key.toUpperCase();
    if (typeof key.name === "string")
        return key.name;
    return key.name[WebInspector.platform] || key.name.other;
}

WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers)
{
    return (keyCode & 255) | (modifiers << 8);
};

WebInspector.KeyboardShortcut._modifiersToString = function(modifiers)
{
    const cmdKey = "\u2318";
    const optKey = "\u2325";
    const shiftKey = "\u21e7";
    const ctrlKey = "\u2303";

    var isMac = WebInspector.isMac();
    var res = "";
    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Ctrl)
        res += isMac ? ctrlKey : "<Ctrl> + ";
    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Alt)
        res += isMac ? optKey : "<Alt> + ";
    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Shift)
        res += isMac ? shiftKey : "<Shift> + ";
    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Meta)
        res += isMac ? cmdKey : "<Win> + ";

    return res;
};

/* TextPrompt.js */

/*
 * Copyright (C) 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TextPrompt = function(element, completions, stopCharacters)
{
    this.element = element;
    this.element.addStyleClass("text-prompt");
    this.completions = completions;
    this.completionStopCharacters = stopCharacters;
    this.history = [];
    this.historyOffset = 0;
    this.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
}

WebInspector.TextPrompt.prototype = {
    get text()
    {
        return this.element.textContent;
    },

    set text(x)
    {
        if (!x) {
            // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
            this.element.removeChildren();
            this.element.appendChild(document.createElement("br"));
        } else
            this.element.textContent = x;

        this.moveCaretToEndOfPrompt();
    },

    _onKeyDown: function(event)
    {
        function defaultAction()
        {
            this.clearAutoComplete();
            this.autoCompleteSoon();
        }

        var handled = false;
        switch (event.keyIdentifier) {
            case "Up":
                this._upKeyPressed(event);
                break;
            case "Down":
                this._downKeyPressed(event);
                break;
            case "U+0009": // Tab
                this._tabKeyPressed(event);
                break;
            case "Right":
            case "End":
                if (!this.acceptAutoComplete())
                    this.autoCompleteSoon();
                break;
            case "Alt":
            case "Meta":
            case "Shift":
            case "Control":
                break;
            case "U+0050": // Ctrl+P = Previous
                if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
                    handled = true;
                    this._moveBackInHistory();
                    break;
                }
                defaultAction.call(this);
                break;
            case "U+004E": // Ctrl+N = Next
                if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
                    handled = true;
                    this._moveForwardInHistory();
                    break;
                }
                defaultAction.call(this);
                break;
            case "U+0041": // Ctrl+A = Move caret to the start of prompt on non-Mac.
                if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
                    handled = true;
                    this._moveCaretToStartOfPrompt();
                    break;
                }
                defaultAction.call(this);
                break;
            case "U+0045": // Ctrl+E = Move caret to the end of prompt on non-Mac.
                if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
                    handled = true;
                    this.moveCaretToEndOfPrompt();
                    break;
                }
                defaultAction.call(this);
                break;
            default:
                defaultAction.call(this);
                break;
        }

        if (handled) {
            event.preventDefault();
            event.stopPropagation();
        }
    },

    acceptAutoComplete: function()
    {
        if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
            return false;

        var text = this.autoCompleteElement.textContent;
        var textNode = document.createTextNode(text);
        this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
        delete this.autoCompleteElement;

        var finalSelectionRange = document.createRange();
        finalSelectionRange.setStart(textNode, text.length);
        finalSelectionRange.setEnd(textNode, text.length);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);

        return true;
    },

    clearAutoComplete: function(includeTimeout)
    {
        if (includeTimeout && "_completeTimeout" in this) {
            clearTimeout(this._completeTimeout);
            delete this._completeTimeout;
        }

        if (!this.autoCompleteElement)
            return;

        if (this.autoCompleteElement.parentNode)
            this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement);
        delete this.autoCompleteElement;

        if (!this._userEnteredRange || !this._userEnteredText)
            return;

        this._userEnteredRange.deleteContents();
        this.element.pruneEmptyTextNodes();

        var userTextNode = document.createTextNode(this._userEnteredText);
        this._userEnteredRange.insertNode(userTextNode);

        var selectionRange = document.createRange();
        selectionRange.setStart(userTextNode, this._userEnteredText.length);
        selectionRange.setEnd(userTextNode, this._userEnteredText.length);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(selectionRange);

        delete this._userEnteredRange;
        delete this._userEnteredText;
    },

    autoCompleteSoon: function()
    {
        if (!("_completeTimeout" in this))
            this._completeTimeout = setTimeout(this.complete.bind(this, true), 250);
    },

    complete: function(auto, reverse)
    {
        this.clearAutoComplete(true);
        var selection = window.getSelection();
        if (!selection.rangeCount)
            return;

        var selectionRange = selection.getRangeAt(0);
        if (!selectionRange.commonAncestorContainer.isDescendant(this.element))
            return;
        if (auto && !this.isCaretAtEndOfPrompt())
            return;
        var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward");
        this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange, reverse));
    },

    _completionsReady: function(selection, auto, originalWordPrefixRange, reverse, completions)
    {
        if (!completions || !completions.length)
            return;

        var selectionRange = selection.getRangeAt(0);

        var fullWordRange = document.createRange();
        fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset);
        fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);

        if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString())
            return;

        var wordPrefixLength = originalWordPrefixRange.toString().length;

        if (auto)
            var completionText = completions[0];
        else {
            if (completions.length === 1) {
                var completionText = completions[0];
                wordPrefixLength = completionText.length;
            } else {
                var commonPrefix = completions[0];
                for (var i = 0; i < completions.length; ++i) {
                    var completion = completions[i];
                    var lastIndex = Math.min(commonPrefix.length, completion.length);
                    for (var j = wordPrefixLength; j < lastIndex; ++j) {
                        if (commonPrefix[j] !== completion[j]) {
                            commonPrefix = commonPrefix.substr(0, j);
                            break;
                        }
                    }
                }
                wordPrefixLength = commonPrefix.length;

                if (selection.isCollapsed)
                    var completionText = completions[0];
                else {
                    var currentText = fullWordRange.toString();

                    var foundIndex = null;
                    for (var i = 0; i < completions.length; ++i) {
                        if (completions[i] === currentText)
                            foundIndex = i;
                    }

                    var nextIndex = foundIndex + (reverse ? -1 : 1);
                    if (foundIndex === null || nextIndex >= completions.length)
                        var completionText = completions[0];
                    else if (nextIndex < 0)
                        var completionText = completions[completions.length - 1];
                    else
                        var completionText = completions[nextIndex];
                }
            }
        }

        this._userEnteredRange = fullWordRange;
        this._userEnteredText = fullWordRange.toString();

        fullWordRange.deleteContents();
        this.element.pruneEmptyTextNodes();

        var finalSelectionRange = document.createRange();

        if (auto) {
            var prefixText = completionText.substring(0, wordPrefixLength);
            var suffixText = completionText.substring(wordPrefixLength);

            var prefixTextNode = document.createTextNode(prefixText);
            fullWordRange.insertNode(prefixTextNode);

            this.autoCompleteElement = document.createElement("span");
            this.autoCompleteElement.className = "auto-complete-text";
            this.autoCompleteElement.textContent = suffixText;

            prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);

            finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
            finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
        } else {
            var completionTextNode = document.createTextNode(completionText);
            fullWordRange.insertNode(completionTextNode);

            if (completions.length > 1)
                finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
            else
                finalSelectionRange.setStart(completionTextNode, completionText.length);

            finalSelectionRange.setEnd(completionTextNode, completionText.length);
        }

        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);
    },

    isCaretInsidePrompt: function()
    {
        return this.element.isInsertionCaretInside();
    },

    isCaretAtEndOfPrompt: function()
    {
        var selection = window.getSelection();
        if (!selection.rangeCount || !selection.isCollapsed)
            return false;

        var selectionRange = selection.getRangeAt(0);
        var node = selectionRange.startContainer;
        if (node !== this.element && !node.isDescendant(this.element))
            return false;

        if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
            return false;

        var foundNextText = false;
        while (node) {
            if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
                if (foundNextText)
                    return false;
                foundNextText = true;
            }

            node = node.traverseNextNode(this.element);
        }

        return true;
    },

    isCaretOnFirstLine: function()
    {
        var selection = window.getSelection();
        var focusNode = selection.focusNode;
        if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this.element)
            return true;

        if (focusNode.textContent.substring(0, selection.focusOffset).indexOf("\n") !== -1)
            return false;
        focusNode = focusNode.previousSibling;

        while (focusNode) {
            if (focusNode.nodeType !== Node.TEXT_NODE)
                return true;
            if (focusNode.textContent.indexOf("\n") !== -1)
                return false;
            focusNode = focusNode.previousSibling;
        }

        return true;
    },

    isCaretOnLastLine: function()
    {
        var selection = window.getSelection();
        var focusNode = selection.focusNode;
        if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this.element)
            return true;

        if (focusNode.textContent.substring(selection.focusOffset).indexOf("\n") !== -1)
            return false;
        focusNode = focusNode.nextSibling;

        while (focusNode) {
            if (focusNode.nodeType !== Node.TEXT_NODE)
                return true;
            if (focusNode.textContent.indexOf("\n") !== -1)
                return false;
            focusNode = focusNode.nextSibling;
        }

        return true;
    },

    _moveCaretToStartOfPrompt: function()
    {
        var selection = window.getSelection();
        var selectionRange = document.createRange();

        selectionRange.setStart(this.element, 0);
        selectionRange.setEnd(this.element, 0);

        selection.removeAllRanges();
        selection.addRange(selectionRange);
    },

    moveCaretToEndOfPrompt: function()
    {
        var selection = window.getSelection();
        var selectionRange = document.createRange();

        var offset = this.element.childNodes.length;
        selectionRange.setStart(this.element, offset);
        selectionRange.setEnd(this.element, offset);

        selection.removeAllRanges();
        selection.addRange(selectionRange);
    },

    _tabKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        this.complete(false, event.shiftKey);
    },

    _upKeyPressed: function(event)
    {
        if (!this.isCaretOnFirstLine())
            return;

        event.preventDefault();
        event.stopPropagation();

        this._moveBackInHistory();
    },

    _downKeyPressed: function(event)
    {
        if (!this.isCaretOnLastLine())
            return;

        event.preventDefault();
        event.stopPropagation();

        this._moveForwardInHistory();
    },

    _moveBackInHistory: function()
    {
        if (this.historyOffset == this.history.length)
            return;

        this.clearAutoComplete(true);

        if (this.historyOffset === 0)
            this.tempSavedCommand = this.text;

        ++this.historyOffset;
        this.text = this.history[this.history.length - this.historyOffset];

        this.element.scrollIntoViewIfNeeded();
        var firstNewlineIndex = this.text.indexOf("\n");
        if (firstNewlineIndex === -1)
            this.moveCaretToEndOfPrompt();
        else {
            var selection = window.getSelection();
            var selectionRange = document.createRange();

            selectionRange.setStart(this.element.firstChild, firstNewlineIndex);
            selectionRange.setEnd(this.element.firstChild, firstNewlineIndex);

            selection.removeAllRanges();
            selection.addRange(selectionRange);
        }
    },

    _moveForwardInHistory: function()
    {
        if (this.historyOffset === 0)
            return;

        this.clearAutoComplete(true);

        --this.historyOffset;

        if (this.historyOffset === 0) {
            this.text = this.tempSavedCommand;
            delete this.tempSavedCommand;
            return;
        }

        this.text = this.history[this.history.length - this.historyOffset];
        this.element.scrollIntoViewIfNeeded();
    }
}

/* Popover.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Popover = function(contentElement)
{
    this.element = document.createElement("div");
    this.element.className = "popover";

    this._popupArrowElement = document.createElement("div");
    this._popupArrowElement.className = "arrow";
    this.element.appendChild(this._popupArrowElement);

    this.contentElement = contentElement;
    this._contentDiv = document.createElement("div");
    this._contentDiv.className = "content";
}

WebInspector.Popover.prototype = {
    show: function(anchor, preferredWidth, preferredHeight)
    {
        // This should not happen, but we hide previous popup to be on the safe side.
        if (WebInspector.Popover._popoverElement)
            document.body.removeChild(WebInspector.Popover._popoverElement);
        WebInspector.Popover._popoverElement = this.element;

        // Temporarily attach in order to measure preferred dimensions.
        this.contentElement.positionAt(0, 0);
        document.body.appendChild(this.contentElement);
        var preferredWidth = preferredWidth || this.contentElement.offsetWidth;
        var preferredHeight = preferredHeight || this.contentElement.offsetHeight;

        this._contentDiv.appendChild(this.contentElement);
        this.element.appendChild(this._contentDiv);
        document.body.appendChild(this.element);
        this._positionElement(anchor, preferredWidth, preferredHeight);
    },

    hide: function()
    {
        if (WebInspector.Popover._popoverElement) {
            delete WebInspector.Popover._popoverElement;
            document.body.removeChild(this.element);
        }
    },

    _positionElement: function(anchorElement, preferredWidth, preferredHeight)
    {
        const borderWidth = 25;
        const scrollerWidth = 11;
        const arrowHeight = 15;
        const arrowOffset = 10;
        const borderRadius = 10;

        // Skinny tooltips are not pretty, their arrow location is not nice.
        preferredWidth = Math.max(preferredWidth, 50);
        const totalWidth = window.innerWidth;
        const totalHeight = window.innerHeight;

        var anchorBox = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight};
        while (anchorElement !== document.body) {
            if (anchorElement.scrollLeft)
                anchorBox.x -= anchorElement.scrollLeft;
            if (anchorElement.scrollTop)
                anchorBox.y -= anchorElement.scrollTop;
            anchorElement = anchorElement.parentElement;
        }

        var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight };

        var verticalAlignment;
        var roomAbove = anchorBox.y;
        var roomBelow = totalHeight - anchorBox.y - anchorBox.height;

        if (roomAbove > roomBelow) {
            // Positioning above the anchor.
            if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius)
                newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight;
            else {
                newElementPosition.y = borderRadius * 2;
                newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight;
            }
            verticalAlignment = "bottom";
        } else {
            // Positioning below the anchor.
            newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight;
            if (newElementPosition.y + newElementPosition.height + arrowHeight - borderWidth >= totalHeight)
                newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height - borderRadius * 2 - arrowHeight;
            // Align arrow.
            verticalAlignment = "top";
        }

        var horizontalAlignment;
        if (anchorBox.x + newElementPosition.width < totalWidth) {
            newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset);
            horizontalAlignment = "left";
        } else if (newElementPosition.width + borderRadius * 2 < totalWidth) {
            newElementPosition.x = totalWidth - newElementPosition.width - borderRadius;
            horizontalAlignment = "right";
            // Position arrow accurately.
            var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset);
            arrowRightPosition += anchorBox.width / 2;
            this._popupArrowElement.style.right = arrowRightPosition + "px";
        } else {
            newElementPosition.x = borderRadius;
            newElementPosition.width = totalWidth - borderRadius * 2;
            newElementPosition.height += scrollerWidth;
            horizontalAlignment = "left";
            if (verticalAlignment === "bottom")
                newElementPosition.y -= scrollerWidth;
            // Position arrow accurately.
            this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px";
            this._popupArrowElement.style.left += anchorBox.width / 2;
        }

        this.element.className = "popover " + verticalAlignment + "-" + horizontalAlignment + "-arrow";
        this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth);
        this.element.style.width = newElementPosition.width + borderWidth * 2 + "px";
        this.element.style.height = newElementPosition.height + borderWidth * 2 + "px";
    }
}

WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopup, showOnClick, onHide)
{
    this._panelElement = panelElement;
    this._getAnchor = getAnchor;
    this._showPopup = showPopup;
    this._showOnClick = showOnClick;
    this._onHide = onHide;
    panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false);
    panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false);
}

WebInspector.PopoverHelper.prototype = {
    _mouseDown: function(event)
    {
        this._killHidePopupTimer();
        this._handleMouseAction(event, true);
    },

    _mouseMove: function(event)
    {
        // Pretend that nothing has happened.
        if (this._hoverElement === event.target || (this._hoverElement && this._hoverElement.isAncestor(event.target)))
            return;

        // User has 500ms to reach the popup.
        if (this._popup && !this._hidePopupTimer) {
            var self = this;
            function doHide()
            {
                self._hidePopup();
                delete self._hidePopupTimer;
            }
            this._hidePopupTimer = setTimeout(doHide, 500);
        }

        this._handleMouseAction(event);
    },

    _handleMouseAction: function(event, isMouseDown)
    {
        this._resetHoverTimer();

        this._hoverElement = this._getAnchor(event.target);
        if (!this._hoverElement)
            return;

        const toolTipDelay = isMouseDown ? 0 : (this._popup ? 600 : 1000);
        this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay);
    },

    _resetHoverTimer: function()
    {
        if (this._hoverTimer) {
            clearTimeout(this._hoverTimer);
            delete this._hoverTimer;
        }
    },

    hidePopup: function()
    {
        this._resetHoverTimer();
        this._hidePopup();
    },

    _hidePopup: function()
    {
        if (!this._popup)
            return;

        if (this._onHide)
            this._onHide();

        this._popup.hide();
        delete this._popup;
    },

    _mouseHover: function(element)
    {
        delete this._hoverTimer;

        this._popup = this._showPopup(element);
        if (this._popup)
            this._popup.contentElement.addEventListener("mousemove", this._killHidePopupTimer.bind(this), true);
    },

    _killHidePopupTimer: function()
    {
        if (this._hidePopupTimer) {
            clearTimeout(this._hidePopupTimer);
            delete this._hidePopupTimer;

            // We know that we reached the popup, but we might have moved over other elements.
            // Discard pending command.
            this._resetHoverTimer();
        }
    }
}

/* TabbedPane.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TabbedPane = function(element)
{
    this.element = element || document.createElement("div");

    this.tabsElement = document.createElement("div");
    this.tabsElement.className = "tabbed-pane-header";
    this.element.appendChild(this.tabsElement);

    this._tabObjects = {};
}

WebInspector.TabbedPane.prototype = {
    appendTab: function(id, tabTitle, contentElement, tabClickListener)
    {
        var tabElement = document.createElement("li");
        tabElement.textContent = tabTitle;
        tabElement.addEventListener("click", tabClickListener, false);
        this.tabsElement.appendChild(tabElement);
        this.element.appendChild(contentElement);
        this._tabObjects[id] = {tab: tabElement, content: contentElement};
    },
    
    tabObjectForId: function(id)
    {
        return this._tabObjects[id];
    },

    hideTab: function(id)
    {
        var tabObject = this._tabObjects[id];
        if (tabObject)
            tabObject.tab.addStyleClass("hidden");
    },

    selectTabById: function(selectId)
    {
        var selected = false;
        for (var id in this._tabObjects) {
            var tabObject = this._tabObjects[id];
            if (id === selectId) {
                selected = true;
                tabObject.tab.addStyleClass("selected");
                tabObject.content.removeStyleClass("hidden");
            } else {
                tabObject.tab.removeStyleClass("selected");
                tabObject.content.addStyleClass("hidden");
            }
        }
        return selected;
    }
}

/* Placard.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Placard = function(title, subtitle)
{
    this.element = document.createElement("div");
    this.element.className = "placard";
    this.element.placard = this;

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";

    this.subtitleElement = document.createElement("div");
    this.subtitleElement.className = "subtitle";

    this.element.appendChild(this.subtitleElement);
    this.element.appendChild(this.titleElement);

    this.title = title;
    this.subtitle = subtitle;
    this.selected = false;
}

WebInspector.Placard.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.titleElement.textContent = x;
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        if (this._subtitle === x)
            return;
        this._subtitle = x;
        this.subtitleElement.innerHTML = x;
    },

    get selected()
    {
        return this._selected;
    },

    set selected(x)
    {
        if (x)
            this.select();
        else
            this.deselect();
    },

    select: function()
    {
        if (this._selected)
            return;
        this._selected = true;
        this.element.addStyleClass("selected");
    },

    deselect: function()
    {
        if (!this._selected)
            return;
        this._selected = false;
        this.element.removeStyleClass("selected");
    },

    toggleSelected: function()
    {
        this.selected = !this.selected;
    }
}

/* View.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.View = function(element)
{
    this.element = element || document.createElement("div");
    this._visible = false;
}

WebInspector.View.prototype = {
    get visible()
    {
        return this._visible;
    },

    set visible(x)
    {
        if (this._visible === x)
            return;

        if (x)
            this.show();
        else
            this.hide();
    },

    show: function(parentElement)
    {
        this._visible = true;
        if (parentElement && parentElement !== this.element.parentNode) {
            this.detach();
            parentElement.appendChild(this.element);
        }
        if (!this.element.parentNode && this.attach)
            this.attach();
        this.element.addStyleClass("visible");
    },

    hide: function()
    {
        this.element.removeStyleClass("visible");
        this._visible = false;
    },

    detach: function()
    {
        if (this.element.parentNode)
            this.element.parentNode.removeChild(this.element);
    }
}

WebInspector.View.prototype.__proto__ = WebInspector.Object.prototype;

/* Callback.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Callback = function()
{
    this._lastCallbackId = 1;
    this._callbacks = {};
}

WebInspector.Callback.prototype = {
    wrap: function(callback)
    {
        var callbackId = this._lastCallbackId++;
        this._callbacks[callbackId] = callback || function() {};
        return callbackId;
    },

    processResponse: function(callbackId, args)
    {
        var callback = this._callbacks[callbackId];
        callback.apply(null, args);
        delete this._callbacks[callbackId];
    },

    removeResponseCallbackEntry: function(callbackId)
    {
        delete this._callbacks[callbackId];
    }
}

WebInspector.Callback._INSTANCE = new WebInspector.Callback();
WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE);
WebInspector.processResponse = WebInspector.Callback._INSTANCE.processResponse.bind(WebInspector.Callback._INSTANCE);
WebInspector.removeResponseCallbackEntry = WebInspector.Callback._INSTANCE.removeResponseCallbackEntry.bind(WebInspector.Callback._INSTANCE);

/* Drawer.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Drawer = function()
{
    WebInspector.View.call(this, document.getElementById("drawer"));

    this._savedHeight = 200; // Default.
    this.state = WebInspector.Drawer.State.Hidden;
    this.fullPanel = false;

    this.mainElement = document.getElementById("main");
    this.toolbarElement = document.getElementById("toolbar");
    this.mainStatusBar = document.getElementById("main-status-bar");
    this.mainStatusBar.addEventListener("mousedown", this._startStatusBarDragging.bind(this), true);
    this.viewStatusBar = document.getElementById("other-drawer-status-bar-items");
    this._counters = document.getElementById("counters");
    this._drawerStatusBar = document.getElementById("drawer-status-bar");
}

WebInspector.Drawer.prototype = {
    get visibleView()
    {
        return this._visibleView;
    },

    set visibleView(x)
    {
        if (this._visibleView === x) {
            if (this.visible && this.fullPanel)
                return;
            this.visible = !this.visible;
            return;
        }

        var firstTime = !this._visibleView;
        if (this._visibleView)
            this._visibleView.hide();

        this._visibleView = x;

        if (x && !firstTime) {
            this._safelyRemoveChildren();
            this.viewStatusBar.removeChildren(); // optimize this? call old.detach()
            x.attach(this.element, this.viewStatusBar);
            x.show();
            this.visible = true;
        }
    },

    get savedHeight()
    {
        var height = this._savedHeight || this.element.offsetHeight;
        return Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - this.mainElement.totalOffsetTop - Preferences.minConsoleHeight);
    },

    showView: function(view)
    {
        if (!this.visible || this.visibleView !== view)
            this.visibleView = view;
    },

    show: function()
    {
        if (this._animating || this.visible)
            return;

        if (this.visibleView)
            this.visibleView.show();

        WebInspector.View.prototype.show.call(this);

        this._animating = true;

        document.body.addStyleClass("drawer-visible");

        var anchoredItems = document.getElementById("anchored-status-bar-items");
        var height = (this.fullPanel ? window.innerHeight - this.toolbarElement.offsetHeight : this.savedHeight);
        var animations = [
            {element: this.element, end: {height: height}},
            {element: document.getElementById("main"), end: {bottom: height}},
            {element: document.getElementById("main-status-bar"), start: {"padding-left": anchoredItems.offsetWidth - 1}, end: {"padding-left": 0}},
            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 0}, end: {opacity: 1}}
        ];

        var drawerStatusBar = document.getElementById("drawer-status-bar");
        drawerStatusBar.insertBefore(anchoredItems, drawerStatusBar.firstChild);

        if (this._currentPanelCounters) {
            var oldRight = this._drawerStatusBar.clientWidth - (this._counters.offsetLeft + this._currentPanelCounters.offsetWidth);
            var newRight = WebInspector.Panel.counterRightMargin;
            var rightPadding = (oldRight - newRight);
            animations.push({element: this._currentPanelCounters, start: {"padding-right": rightPadding}, end: {"padding-right": 0}});
            this._currentPanelCounters.parentNode.removeChild(this._currentPanelCounters);
            this.mainStatusBar.appendChild(this._currentPanelCounters);
        }

        function animationFinished()
        {
            if ("updateStatusBarItems" in WebInspector.currentPanel)
                WebInspector.currentPanel.updateStatusBarItems();
            if (this.visibleView.afterShow)
                this.visibleView.afterShow();
            delete this._animating;
            delete this._currentAnimationInterval;
            this.state = (this.fullPanel ? WebInspector.Drawer.State.Full : WebInspector.Drawer.State.Variable);
            if (this._currentPanelCounters)
                this._currentPanelCounters.removeAttribute("style");
        }

        this._currentAnimationInterval = WebInspector.animateStyle(animations, this._animationDuration(), animationFinished.bind(this));
    },

    hide: function()
    {
        if (this._animating || !this.visible)
            return;

        WebInspector.View.prototype.hide.call(this);

        if (this.visibleView)
            this.visibleView.hide();

        this._animating = true;

        if (!this.fullPanel)
            this._savedHeight = this.element.offsetHeight;

        if (this.element === WebInspector.currentFocusElement || this.element.isAncestor(WebInspector.currentFocusElement))
            WebInspector.currentFocusElement = WebInspector.previousFocusElement;

        var anchoredItems = document.getElementById("anchored-status-bar-items");

        // Temporarily set properties and classes to mimic the post-animation values so panels
        // like Elements in their updateStatusBarItems call will size things to fit the final location.
        this.mainStatusBar.style.setProperty("padding-left", (anchoredItems.offsetWidth - 1) + "px");
        document.body.removeStyleClass("drawer-visible");
        if ("updateStatusBarItems" in WebInspector.currentPanel)
            WebInspector.currentPanel.updateStatusBarItems();
        document.body.addStyleClass("drawer-visible");

        var animations = [
            {element: document.getElementById("main"), end: {bottom: 0}},
            {element: document.getElementById("main-status-bar"), start: {"padding-left": 0}, end: {"padding-left": anchoredItems.offsetWidth - 1}},
            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 1}, end: {opacity: 0}}
        ];

        if (this._currentPanelCounters) {
            var newRight = this._drawerStatusBar.clientWidth - this._counters.offsetLeft;
            var oldRight = this.mainStatusBar.clientWidth - (this._currentPanelCounters.offsetLeft + this._currentPanelCounters.offsetWidth);
            var rightPadding = (newRight - oldRight);
            animations.push({element: this._currentPanelCounters, start: {"padding-right": 0}, end: {"padding-right": rightPadding}});
        }

        function animationFinished()
        {
            WebInspector.currentPanel.resize();
            var mainStatusBar = document.getElementById("main-status-bar");
            mainStatusBar.insertBefore(anchoredItems, mainStatusBar.firstChild);
            mainStatusBar.style.removeProperty("padding-left");

            if (this._currentPanelCounters) {
                this._currentPanelCounters.setAttribute("style", null);
                this._currentPanelCounters.parentNode.removeChild(this._currentPanelCounters);
                this._counters.insertBefore(this._currentPanelCounters, this._counters.firstChild);
            }

            document.body.removeStyleClass("drawer-visible");
            delete this._animating;
            delete this._currentAnimationInterval;
            this.state = WebInspector.Drawer.State.Hidden;
        }

        this._currentAnimationInterval = WebInspector.animateStyle(animations, this._animationDuration(), animationFinished.bind(this));
    },

    resize: function()
    {
        if (this.state === WebInspector.Drawer.State.Hidden)
            return;

        var height;
        var mainElement = document.getElementById("main");
        if (this.state === WebInspector.Drawer.State.Variable) {
            height = parseInt(this.element.style.height);
            height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - mainElement.totalOffsetTop - Preferences.minConsoleHeight);
        } else
            height = window.innerHeight - this.toolbarElement.offsetHeight;

        mainElement.style.bottom = height + "px";
        this.element.style.height = height + "px";
    },

    enterPanelMode: function()
    {
        this._cancelAnimationIfNeeded();
        this.fullPanel = true;
        
        if (this.visible) {
            this._savedHeight = this.element.offsetHeight;
            var height = window.innerHeight - this.toolbarElement.offsetHeight;
            this._animateDrawerHeight(height, WebInspector.Drawer.State.Full);
        }
    },

    exitPanelMode: function()
    {
        this._cancelAnimationIfNeeded();
        this.fullPanel = false;

        if (this.visible) {
            // If this animation gets cancelled, we want the state of the drawer to be Variable,
            // so that the new animation can't do an immediate transition between Hidden/Full states.
            this.state = WebInspector.Drawer.State.Variable;
            var height = this.savedHeight;
            this._animateDrawerHeight(height, WebInspector.Drawer.State.Variable);
        }
    },

    immediatelyExitPanelMode: function()
    {
        this.visible = false;
        this.fullPanel = false;
    },

    set currentPanelCounters(x)
    {
        if (!x) {
            if (this._currentPanelCounters)
                this._currentPanelCounters.parentElement.removeChild(this._currentPanelCounters);
            delete this._currentPanelCounters;
            return;
        }

        this._currentPanelCounters = x;
        if (this.visible)
            this.mainStatusBar.appendChild(x);
        else
            this._counters.insertBefore(x, this._counters.firstChild);
    },

    _cancelAnimationIfNeeded: function()
    {
        if (this._animating) {
            clearInterval(this._currentAnimationInterval);
            delete this._animating;
            delete this._currentAnimationInterval;
        }
    },

    _animateDrawerHeight: function(height, finalState)
    {
        this._animating = true;
        var animations = [
            {element: this.element, end: {height: height}},
            {element: document.getElementById("main"), end: {bottom: height}}
        ];

        function animationFinished()
        {
            delete this._animating;
            delete this._currentAnimationInterval;
            this.state = finalState;
        }

        this._currentAnimationInterval = WebInspector.animateStyle(animations, this._animationDuration(), animationFinished.bind(this));
    },

    _animationDuration: function()
    {
        // Immediate if going between Hidden and Full in full panel mode
        if (this.fullPanel && (this.state === WebInspector.Drawer.State.Hidden || this.state === WebInspector.Drawer.State.Full))
            return 0;

        return (window.event && window.event.shiftKey ? 2000 : 250);
    },

    _safelyRemoveChildren: function()
    {
        var child = this.element.firstChild;
        while (child) {
            if (child.id !== "drawer-status-bar") {
                var moveTo = child.nextSibling;
                this.element.removeChild(child);
                child = moveTo;
            } else
                child = child.nextSibling;
        }
    },

    _startStatusBarDragging: function(event)
    {
        if (!this.visible || event.target !== this.mainStatusBar)
            return;

        WebInspector.elementDragStart(this.mainStatusBar, this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), event, "row-resize");

        this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop;

        event.stopPropagation();
    },

    _statusBarDragging: function(event)
    {
        var mainElement = document.getElementById("main");
        var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
        height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - mainElement.totalOffsetTop - Preferences.minConsoleHeight);

        mainElement.style.bottom = height + "px";
        this.element.style.height = height + "px";

        event.preventDefault();
        event.stopPropagation();
    },

    _endStatusBarDragging: function(event)
    {
        WebInspector.elementDragEnd(event);

        this._savedHeight = this.element.offsetHeight;
        delete this._statusBarDragOffset;

        event.stopPropagation();
    }
}

WebInspector.Drawer.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.Drawer.State = {
    Hidden: 0,
    Variable: 1,
    Full: 2
};

/* ChangesView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ChangesView = function(drawer)
{
    WebInspector.View.call(this);
    this.element.innerHTML = "<div style=\"bottom:25%;color:rgb(192,192,192);font-size:12px;height:65px;left:0px;margin:auto;position:absolute;right:0px;text-align:center;top:0px;\"><h1>Not Implemented Yet</h1></div>";

    this.drawer = drawer;

    this.clearButton = document.createElement("button");
    this.clearButton.id = "clear-changes-status-bar-item";
    this.clearButton.title = WebInspector.UIString("Clear changes log.");
    this.clearButton.className = "status-bar-item clear-status-bar-item";
    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);

    this.toggleChangesButton = document.getElementById("changes-status-bar-item");
    this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
    this.toggleChangesButton.addEventListener("click", this._toggleChangesButtonClicked.bind(this), false);
    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
    anchoredStatusBar.appendChild(this.toggleChangesButton);
}

WebInspector.ChangesView.prototype = {
    _clearButtonClicked: function()
    {
        // Not Implemented Yet
    },

    _toggleChangesButtonClicked: function()
    {
        this.drawer.visibleView = this;
    },

    attach: function(mainElement, statusBarElement)
    {
        mainElement.appendChild(this.element);
        statusBarElement.appendChild(this.clearButton);
    },

    show: function()
    {
        this.toggleChangesButton.addStyleClass("toggled-on");
        this.toggleChangesButton.title = WebInspector.UIString("Hide changes view.");
    },

    hide: function()
    {
        this.toggleChangesButton.removeStyleClass("toggled-on");
        this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
    }
}

WebInspector.ChangesView.prototype.__proto__ = WebInspector.View.prototype;

/* ConsoleView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>";

WebInspector.ConsoleView = function(drawer)
{
    WebInspector.View.call(this, document.getElementById("console-view"));

    this.messages = [];
    this.drawer = drawer;

    this.clearButton = document.getElementById("clear-console-status-bar-item");
    this.clearButton.title = WebInspector.UIString("Clear console log.");
    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);

    this.messagesElement = document.getElementById("console-messages");
    this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
    this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);

    this.promptElement = document.getElementById("console-prompt");
    this.promptElement.className = "source-code";
    this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");
    WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);

    this.topGroup = new WebInspector.ConsoleGroup(null, 0);
    this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
    this.groupLevel = 0;
    this.currentGroup = this.topGroup;

    this.toggleConsoleButton = document.getElementById("console-status-bar-item");
    this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);

    // Will hold the list of filter elements
    this.filterBarElement = document.getElementById("console-filter");

    function createDividerElement() {
        var dividerElement = document.createElement("div");
        dividerElement.addStyleClass("scope-bar-divider");
        this.filterBarElement.appendChild(dividerElement);
    }

    var updateFilterHandler = this._updateFilter.bind(this);
    function createFilterElement(category) {
        var categoryElement = document.createElement("li");
        categoryElement.category = category;
        categoryElement.addStyleClass(categoryElement.category);            
        categoryElement.addEventListener("click", updateFilterHandler, false);

        var label = category.toString();
        categoryElement.appendChild(document.createTextNode(label));

        this.filterBarElement.appendChild(categoryElement);
        return categoryElement;
    }
    
    this.allElement = createFilterElement.call(this, WebInspector.UIString("All"));
    createDividerElement.call(this);
    this.errorElement = createFilterElement.call(this, WebInspector.UIString("Errors"));
    this.warningElement = createFilterElement.call(this, WebInspector.UIString("Warnings"));
    this.logElement = createFilterElement.call(this, WebInspector.UIString("Logs"));

    this.filter(this.allElement, false);
    this._registerShortcuts();

    this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
    
    this._customFormatters = {
        "object": this._formatobject,
        "array":  this._formatarray,
        "node":   this._formatnode,
        "string": this._formatstring
    };
}

WebInspector.ConsoleView.prototype = {
    _settingsLoaded: function()
    {
        this.prompt.history = WebInspector.applicationSettings.consoleHistory;
    },
    
    _updateFilter: function(e)
    {
        var isMac = WebInspector.isMac();
        var selectMultiple = false;
        if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;
        if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;

        this.filter(e.target, selectMultiple);
    },
    
    filter: function(target, selectMultiple)
    {
        function unselectAll()
        {
            this.allElement.removeStyleClass("selected");
            this.errorElement.removeStyleClass("selected");
            this.warningElement.removeStyleClass("selected");
            this.logElement.removeStyleClass("selected");
            
            this.messagesElement.removeStyleClass("filter-all");
            this.messagesElement.removeStyleClass("filter-errors");
            this.messagesElement.removeStyleClass("filter-warnings");
            this.messagesElement.removeStyleClass("filter-logs");
        }
        
        var targetFilterClass = "filter-" + target.category.toLowerCase();

        if (target.category == "All") {
            if (target.hasStyleClass("selected")) {
                // We can't unselect all, so we break early here
                return;
            }

            unselectAll.call(this);
        } else {
            // Something other than all is being selected, so we want to unselect all
            if (this.allElement.hasStyleClass("selected")) {
                this.allElement.removeStyleClass("selected");
                this.messagesElement.removeStyleClass("filter-all");
            }
        }
        
        if (!selectMultiple) {
            // If multiple selection is off, we want to unselect everything else
            // and just select ourselves.
            unselectAll.call(this);
            
            target.addStyleClass("selected");
            this.messagesElement.addStyleClass(targetFilterClass);
            
            return;
        }
        
        if (target.hasStyleClass("selected")) {
            // If selectMultiple is turned on, and we were selected, we just
            // want to unselect ourselves.
            target.removeStyleClass("selected");
            this.messagesElement.removeStyleClass(targetFilterClass);
        } else {
            // If selectMultiple is turned on, and we weren't selected, we just
            // want to select ourselves.
            target.addStyleClass("selected");
            this.messagesElement.addStyleClass(targetFilterClass);
        }
    },
    
    _toggleConsoleButtonClicked: function()
    {
        this.drawer.visibleView = this;
    },

    attach: function(mainElement, statusBarElement)
    {
        mainElement.appendChild(this.element);
        statusBarElement.appendChild(this.clearButton);
        statusBarElement.appendChild(this.filterBarElement);
    },

    show: function()
    {
        this.toggleConsoleButton.addStyleClass("toggled-on");
        this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
        if (!this.prompt.isCaretInsidePrompt())
            this.prompt.moveCaretToEndOfPrompt();
    },

    afterShow: function()
    {
        WebInspector.currentFocusElement = this.promptElement;  
    },

    hide: function()
    {
        this.toggleConsoleButton.removeStyleClass("toggled-on");
        this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    },

    _scheduleScrollIntoView: function()
    {
        if (this._scrollIntoViewTimer)
            return;

        function scrollIntoView()
        {
            this.promptElement.scrollIntoView(false);
            delete this._scrollIntoViewTimer;
        }
        this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
    },

    addMessage: function(msg)
    {
        if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
            this._incrementErrorWarningCount(msg);

            // Add message to the resource panel
            if (msg.url in WebInspector.resourceURLMap) {
                msg.resource = WebInspector.resourceURLMap[msg.url];
                if (WebInspector.panels.resources)
                    WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
            }

            this.commandSincePreviousMessage = false;
            this.previousMessage = msg;
        } else if (msg instanceof WebInspector.ConsoleCommand) {
            if (this.previousMessage) {
                this.commandSincePreviousMessage = true;
            }
        }

        this.messages.push(msg);

        if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
            if (this.groupLevel < 1)
                return;

            this.groupLevel--;

            this.currentGroup = this.currentGroup.parentGroup;
        } else {
            if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
                this.groupLevel++;

                var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
                this.currentGroup.messagesElement.appendChild(group.element);
                this.currentGroup = group;
            }

            this.currentGroup.addMessage(msg);
        }

        this._scheduleScrollIntoView();
    },

    updateMessageRepeatCount: function(count)
    {
        var msg = this.previousMessage;
        var prevRepeatCount = msg.totalRepeatCount;
        
        if (!this.commandSincePreviousMessage) {
            msg.repeatDelta = count - prevRepeatCount;
            msg.repeatCount = msg.repeatCount + msg.repeatDelta;
            msg.totalRepeatCount = count;
            msg._updateRepeatCount();
            this._incrementErrorWarningCount(msg);
        } else {
            var msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount, msg._messageText, msg._parameters, msg._stackTrace);
            msgCopy.totalRepeatCount = count;
            msgCopy._formatMessage();
            this.addMessage(msgCopy);
        }
    },

    _incrementErrorWarningCount: function(msg)
    {
        switch (msg.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                WebInspector.warnings += msg.repeatDelta;
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                WebInspector.errors += msg.repeatDelta;
                break;
        }
    },

    requestClearMessages: function()
    {
        InspectorBackend.clearConsoleMessages();
    },

    clearMessages: function()
    {
        if (WebInspector.panels.resources)
            WebInspector.panels.resources.clearMessages();

        this.messages = [];

        this.groupLevel = 0;
        this.currentGroup = this.topGroup;
        this.topGroup.messagesElement.removeChildren();

        WebInspector.errors = 0;
        WebInspector.warnings = 0;

        delete this.commandSincePreviousMessage;
        delete this.previousMessage;
    },

    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    {
        // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
        var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward");
        var expressionString = expressionRange.toString();
        var lastIndex = expressionString.length - 1;

        var dotNotation = (expressionString[lastIndex] === ".");
        var bracketNotation = (expressionString[lastIndex] === "[");

        if (dotNotation || bracketNotation)
            expressionString = expressionString.substr(0, lastIndex);

        var prefix = wordRange.toString();
        if (!expressionString && !prefix)
            return;

        var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
        // Collect comma separated object properties for the completion.

        var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation);
        var callFrameId = WebInspector.panels.scripts.selectedCallFrameId();
        var injectedScriptAccess;
        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
            var selectedCallFrame = WebInspector.panels.scripts.sidebarPanes.callstack.selectedCallFrame;
            injectedScriptAccess = InjectedScriptAccess.get(selectedCallFrame.worldId);
        } else
            injectedScriptAccess = InjectedScriptAccess.getDefault();
        injectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions);
    },

    _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
        if (isException)
            return;

        if (bracketNotation) {
            if (prefix.length && prefix[0] === "'")
                var quoteUsed = "'";
            else
                var quoteUsed = "\"";
        }

        var results = [];
        var properties = Object.sortedProperties(result);

        for (var i = 0; i < properties.length; ++i) {
            var property = properties[i];

            if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
                continue;

            if (bracketNotation) {
                if (!/^[0-9]+$/.test(property))
                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
                property += "]";
            }

            if (property.length < prefix.length)
                continue;
            if (property.indexOf(prefix) !== 0)
                continue;

            results.push(property);
            if (bestMatchOnly)
                break;
        }
        completionsReadyCallback(results);
    },

    _clearButtonClicked: function()
    {
        this.requestClearMessages();
    },

    _handleContextMenuEvent: function(event)
    {
        if (!window.getSelection().isCollapsed) {
            // If there is a selection, we want to show our normal context menu
            // (with Copy, etc.), and not Clear Console.
            return;
        }

        var contextMenu = new WebInspector.ContextMenu();

        function monitoringXHRWasChanged(newState)
        {
            WebInspector.monitoringXHREnabled = newState;
        }
        var itemAction = InspectorBackend.setMonitoringXHREnabled.bind(InspectorBackend, !WebInspector.monitoringXHREnabled, monitoringXHRWasChanged);
        contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.monitoringXHREnabled);
        contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this));
        contextMenu.show(event);
    },

    _messagesSelectStart: function(event)
    {
        if (this._selectionTimeout)
            clearTimeout(this._selectionTimeout);

        this.prompt.clearAutoComplete();

        function moveBackIfOutside()
        {
            delete this._selectionTimeout;
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
            this.prompt.autoCompleteSoon();
        }

        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    },

    _messagesClicked: function(event)
    {
        var link = event.target.enclosingNodeOrSelfWithNodeName("a");
        if (!link || !link.representedNode)
            return;

        WebInspector.updateFocusedNode(link.representedNode.id);
        event.stopPropagation();
        event.preventDefault();
    },

    _registerShortcuts: function()
    {
        this._shortcuts = {};

        var shortcut = WebInspector.KeyboardShortcut;
        var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
        // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers.
        this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this);
        this._shortcuts[shortcutK.key].isMacOnly = true;

        var clearConsoleHandler = this.requestClearMessages.bind(this);
        var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
        this._shortcuts[shortcutL.key] = clearConsoleHandler;

        var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
        var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ];
        section.addAlternateKeys(keys, WebInspector.UIString("Clear Console"));

        keys = [
            shortcut.shortcutToString(shortcut.Keys.Tab),
            shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion"));
        section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.Down),
            shortcut.shortcutToString(shortcut.Keys.Up)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
        keys = [
            shortcut.shortcutToString("N", shortcut.Modifiers.Alt),
            shortcut.shortcutToString("P", shortcut.Modifiers.Alt)
        ];
        if (WebInspector.isMac())
            section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
        section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
    },

    _promptKeyDown: function(event)
    {
        if (isEnterKey(event)) {
            this._enterKeyPressed(event);
            return;
        }

        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
                handler();
                event.preventDefault();
                return;
            }
        }
    },

    evalInInspectedWindow: function(expression, objectGroup, callback)
    {
        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
            WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback);
            return;
        }
        this.doEvalInWindow(expression, objectGroup, callback);
    },

    doEvalInWindow: function(expression, objectGroup, callback)
    {
        if (!expression) {
            // There is no expression, so the completion should happen against global properties.
            expression = "this";
        }

        function evalCallback(result)
        {
            callback(WebInspector.RemoteObject.fromPayload(result));
        };
        InjectedScriptAccess.getDefault().evaluate(expression, objectGroup, evalCallback);
    },

    _enterKeyPressed: function(event)
    {
        if (event.altKey || event.ctrlKey)
            return;

        event.preventDefault();
        event.stopPropagation();

        this.prompt.clearAutoComplete(true);

        var str = this.prompt.text;
        if (!str.length)
            return;

        var commandMessage = new WebInspector.ConsoleCommand(str);
        this.addMessage(commandMessage);

        var self = this;
        function printResult(result)
        {
            self.prompt.history.push(str);
            self.prompt.historyOffset = 0;
            self.prompt.text = "";

            WebInspector.applicationSettings.consoleHistory = self.prompt.history.slice(-30);

            self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
        }
        this.evalInInspectedWindow(str, "console", printResult);
    },

    _format: function(output, forceObjectFormat)
    {
        var isProxy = (output != null && typeof output === "object");
        var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output));

        var formatter = this._customFormatters[type];
        if (!formatter || !isProxy) {
            formatter = this._formatvalue;
            output = output.description;
        }

        var span = document.createElement("span");
        span.className = "console-formatted-" + type + " source-code";
        formatter.call(this, output, span);
        return span;
    },

    _formatvalue: function(val, elem)
    {
        elem.appendChild(document.createTextNode(val));
    },

    _formatobject: function(obj, elem)
    {
        elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
    },

    _formatnode: function(object, elem)
    {
        function printNode(nodeId)
        {
            if (!nodeId)
                return;
            var treeOutline = new WebInspector.ElementsTreeOutline();
            treeOutline.showInElementsPanelEnabled = true;
            treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
            treeOutline.element.addStyleClass("outline-disclosure");
            if (!treeOutline.children[0].hasChildren)
                treeOutline.element.addStyleClass("single-node");
            elem.appendChild(treeOutline.element);
        }

        object.pushNodeToFrontend(printNode);
    },

    _formatarray: function(arr, elem)
    {
        arr.getOwnProperties(false, this._printArray.bind(this, elem));
    },

    _formatstring: function(output, elem)
    {
        var span = document.createElement("span");
        span.className = "console-formatted-string source-code";
        span.appendChild(WebInspector.linkifyStringAsFragment(output.description));

        // Make black quotes.
        elem.removeStyleClass("console-formatted-string");
        elem.appendChild(document.createTextNode("\""));
        elem.appendChild(span);
        elem.appendChild(document.createTextNode("\""));
    },

    _printArray: function(elem, properties)
    {
        if (!properties)
            return;

        var elements = [];
        for (var i = 0; i < properties.length; ++i) {
            var name = properties[i].name;
            if (name == parseInt(name))
                elements[name] = this._formatAsArrayEntry(properties[i].value);
        }

        elem.appendChild(document.createTextNode("["));
        for (var i = 0; i < elements.length; ++i) {
            var element = elements[i];
            if (element)
                elem.appendChild(element);
            else
                elem.appendChild(document.createTextNode("undefined"))
            if (i < elements.length - 1)
                elem.appendChild(document.createTextNode(", "));
        }
        elem.appendChild(document.createTextNode("]"));
    },

    _formatAsArrayEntry: function(output)
    {
        // Prevent infinite expansion of cross-referencing arrays.
        return this._format(output, WebInspector.RemoteObject.type(output) === "array");
    }
}

WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount, message, parameters, stackTrace)
{
    this.source = source;
    this.type = type;
    this.level = level;
    this.line = line;
    this.url = url;
    this.groupLevel = groupLevel;
    this.repeatCount = repeatCount;
    this.repeatDelta = repeatCount;
    this.totalRepeatCount = repeatCount;
    this._messageText = message;
    this._parameters = parameters;
    this._stackTrace = stackTrace;
    this._formatMessage();
}

WebInspector.ConsoleMessage.createTextMessage = function(text, level)
{
    level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
    return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, null, [text], null);
}

WebInspector.ConsoleMessage.prototype = {
    _formatMessage: function()
    {
        switch (this.type) {
            case WebInspector.ConsoleMessage.MessageType.Assert:
            case WebInspector.ConsoleMessage.MessageType.Trace:
            case WebInspector.ConsoleMessage.MessageType.UncaughtException:
                var ol = document.createElement("ol");
                ol.addStyleClass("stack-trace");
                if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
                    ol.addStyleClass("trace-message");
                var treeOutline = new TreeOutline(ol);

                var root = treeOutline;
                if (this.type === WebInspector.ConsoleMessage.MessageType.UncaughtException ||
                    this.type === WebInspector.ConsoleMessage.MessageType.Assert) {
                    var messageText;
                    if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
                        messageText = this._format(this._parameters);
                    else
                        messageText = document.createTextNode(this._messageText);

                    var content = document.createElement("div");
                    this._addMessageHeader(content, messageText);
                    root = new TreeElement(content, null, true);
                    content.treeElementForTest = root;
                    treeOutline.appendChild(root);
                }

                this._populateStackTraceTreeElement(root);
                this.formattedMessage = ol;
                break;
            case WebInspector.ConsoleMessage.MessageType.Object:
                var obj = this._parameters ? this._parameters[0] : undefined;
                this.formattedMessage = this._format(["%O", obj]);
                break;
            default:
                var args = this._parameters || [this._messageText];
                this.formattedMessage = this._format(args);
                break;
        }

        // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
        this.message = this.formattedMessage.textContent;
    },

    isErrorOrWarning: function()
    {
        return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
    },

    _format: function(parameters)
    {
        // This node is used like a Builder. Values are continually appended onto it.
        var formattedResult = document.createElement("span");
        if (!parameters.length)
            return formattedResult;

        // Formatting code below assumes that parameters are all wrappers whereas frontend console
        // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
        for (var i = 0; i < parameters.length; ++i) {
            if (typeof parameters[i] === "object")
                parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
            else
                parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
        }

        // There can be string log and string eval result. We distinguish between them based on message type.
        var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;

        // Multiple parameters with the first being a format string. Save unused substitutions.
        if (shouldFormatMessage) {
            // Multiple parameters with the first being a format string. Save unused substitutions.
            var result = this._formatWithSubstitutionString(parameters, formattedResult);
            parameters = result.unusedSubstitutions;
            if (parameters.length)
                formattedResult.appendChild(document.createTextNode(" "));
        }

        // Single parameter, or unused substitutions from above.
        for (var i = 0; i < parameters.length; ++i) {
            // Inline strings when formatting.
            if (shouldFormatMessage && parameters[i].type === "string")
                formattedResult.appendChild(document.createTextNode(parameters[i].description));
            else
                formattedResult.appendChild(WebInspector.console._format(parameters[i]));
            if (i < parameters.length - 1)
                formattedResult.appendChild(document.createTextNode(" "));
        }
        return formattedResult;
    },

    _formatWithSubstitutionString: function(parameters, formattedResult)
    {
        var formatters = {}
        for (var i in String.standardFormatters)
            formatters[i] = String.standardFormatters[i];

        function consoleFormatWrapper(force)
        {
            return function(obj) {
                return WebInspector.console._format(obj, force);
            };
        }

        // Firebug uses %o for formatting objects.
        formatters.o = consoleFormatWrapper();
        // Firebug allows both %i and %d for formatting integers.
        formatters.i = formatters.d;
        // Support %O to force object formatting, instead of the type-based %o formatting.
        formatters.O = consoleFormatWrapper(true);

        function append(a, b)
        {
            if (!(b instanceof Node))
                a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
            else
                a.appendChild(b);
            return a;
        }

        // String.format does treat formattedResult like a Builder, result is an object.
        return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
    },

    toMessageElement: function()
    {
        if (this._element)
            return this._element;

        var element = document.createElement("div");
        element.message = this;
        element.className = "console-message";

        this._element = element;

        switch (this.source) {
            case WebInspector.ConsoleMessage.MessageSource.HTML:
                element.addStyleClass("console-html-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.WML:
                element.addStyleClass("console-wml-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.XML:
                element.addStyleClass("console-xml-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.JS:
                element.addStyleClass("console-js-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.CSS:
                element.addStyleClass("console-css-source");
                break;
            case WebInspector.ConsoleMessage.MessageSource.Other:
                element.addStyleClass("console-other-source");
                break;
        }

        switch (this.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Tip:
                element.addStyleClass("console-tip-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Log:
                element.addStyleClass("console-log-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Debug:
                element.addStyleClass("console-debug-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                element.addStyleClass("console-warning-level");
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                element.addStyleClass("console-error-level");
                break;
        }

        if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
            element.addStyleClass("console-group-title");

        if (this.elementsTreeOutline) {
            element.addStyleClass("outline-disclosure");
            element.appendChild(this.elementsTreeOutline.element);
            return element;
        }

        if (this.type === WebInspector.ConsoleMessage.MessageType.Trace ||
            this.type === WebInspector.ConsoleMessage.MessageType.Assert ||
            this.type === WebInspector.ConsoleMessage.MessageType.UncaughtException)
            element.appendChild(this.formattedMessage);
        else
            this._addMessageHeader(element, this.formattedMessage);

        if (this.repeatCount > 1)
            this._updateRepeatCount();

        return element;
    },

    _populateStackTraceTreeElement: function(parentTreeElement)
    {
        for (var i = 0; i < this._stackTrace.length; i++) {
            var frame = this._stackTrace[i];

            var content = document.createElement("div");
            var messageTextElement = document.createElement("span");
            messageTextElement.className = "console-message-text source-code";
            var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
            messageTextElement.appendChild(document.createTextNode(functionName));
            content.appendChild(messageTextElement);

            var urlElement = WebInspector.linkifyResourceAsNode(frame.sourceURL, "scripts", frame.lineNumber, "console-message-url"); 
            content.appendChild(urlElement);

            var treeElement = new TreeElement(content);
            parentTreeElement.appendChild(treeElement);
        }
    },

    _addMessageHeader: function(parentElement, formattedMessage)
    {
        if (this.url && this.url !== "undefined") {
            var urlElement = WebInspector.linkifyResourceAsNode(this.url, "scripts", this.line, "console-message-url"); 
            parentElement.appendChild(urlElement);
        }

        var messageTextElement = document.createElement("span");
        messageTextElement.className = "console-message-text source-code";
        if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
            messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: ")));
        messageTextElement.appendChild(formattedMessage);
        parentElement.appendChild(messageTextElement);
    },

    _updateRepeatCount: function() {
        if (!this.repeatCountElement) {
            this.repeatCountElement = document.createElement("span");
            this.repeatCountElement.className = "bubble";
    
            this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
            this._element.addStyleClass("repeated-message");
        }
        this.repeatCountElement.textContent = this.repeatCount;
    },

    toString: function()
    {
        var sourceString;
        switch (this.source) {
            case WebInspector.ConsoleMessage.MessageSource.HTML:
                sourceString = "HTML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.WML:
                sourceString = "WML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.XML:
                sourceString = "XML";
                break;
            case WebInspector.ConsoleMessage.MessageSource.JS:
                sourceString = "JS";
                break;
            case WebInspector.ConsoleMessage.MessageSource.CSS:
                sourceString = "CSS";
                break;
            case WebInspector.ConsoleMessage.MessageSource.Other:
                sourceString = "Other";
                break;
        }

        var typeString;
        switch (this.type) {
            case WebInspector.ConsoleMessage.MessageType.Log:
            case WebInspector.ConsoleMessage.MessageType.UncaughtException:
                typeString = "Log";
                break;
            case WebInspector.ConsoleMessage.MessageType.Object:
                typeString = "Object";
                break;
            case WebInspector.ConsoleMessage.MessageType.Trace:
                typeString = "Trace";
                break;
            case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
            case WebInspector.ConsoleMessage.MessageType.StartGroup:
                typeString = "Start Group";
                break;
            case WebInspector.ConsoleMessage.MessageType.EndGroup:
                typeString = "End Group";
                break;
            case WebInspector.ConsoleMessage.MessageType.Assert:
                typeString = "Assert";
                break;
            case WebInspector.ConsoleMessage.MessageType.Result:
                typeString = "Result";
                break;
        }
        
        var levelString;
        switch (this.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Tip:
                levelString = "Tip";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Log:
                levelString = "Log";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                levelString = "Warning";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Debug:
                levelString = "Debug";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                levelString = "Error";
                break;
        }

        return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
    },

    isEqual: function(msg, disreguardGroup)
    {
        if (!msg)
            return false;

        var ret = (this.source == msg.source)
            && (this.type == msg.type)
            && (this.level == msg.level)
            && (this.line == msg.line)
            && (this.url == msg.url)
            && (this.message == msg.message);

        return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
    }
}

// Note: Keep these constants in sync with the ones in Console.h
WebInspector.ConsoleMessage.MessageSource = {
    HTML: 0,
    WML: 1,
    XML: 2,
    JS: 3,
    CSS: 4,
    Other: 5
}

WebInspector.ConsoleMessage.MessageType = {
    Log: 0,
    Object: 1,
    Trace: 2,
    StartGroup: 3,
    StartGroupCollapsed: 4,
    EndGroup: 5,
    Assert: 6,
    UncaughtException: 7,
    Result: 8
}

WebInspector.ConsoleMessage.MessageLevel = {
    Tip: 0,
    Log: 1,
    Warning: 2,
    Error: 3,
    Debug: 4
}

WebInspector.ConsoleCommand = function(command)
{
    this.command = command;
}

WebInspector.ConsoleCommand.prototype = {
    toMessageElement: function()
    {
        var element = document.createElement("div");
        element.command = this;
        element.className = "console-user-command";

        var commandTextElement = document.createElement("span");
        commandTextElement.className = "console-message-text source-code";
        commandTextElement.textContent = this.command;
        element.appendChild(commandTextElement);

        return element;
    }
}

WebInspector.ConsoleCommandResult = function(result, originatingCommand)
{
    var level = (result.isError() ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
    this.originatingCommand = originatingCommand;
    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, -1, null, null, 1, null, [result]);
}

WebInspector.ConsoleCommandResult.prototype = {
    toMessageElement: function()
    {
        var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
        element.addStyleClass("console-user-command-result");
        return element;
    }
}

WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;

WebInspector.ConsoleGroup = function(parentGroup, level)
{
    this.parentGroup = parentGroup;
    this.level = level;

    var element = document.createElement("div");
    element.className = "console-group";
    element.group = this;
    this.element = element;

    var messagesElement = document.createElement("div");
    messagesElement.className = "console-group-messages";
    element.appendChild(messagesElement);
    this.messagesElement = messagesElement;
}

WebInspector.ConsoleGroup.prototype = {
    addMessage: function(msg)
    {
        var element = msg.toMessageElement();

        if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
            this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
            element.addEventListener("click", this._titleClicked.bind(this), false);
            var groupElement = element.enclosingNodeOrSelfWithClass("console-group");
            if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
                groupElement.addStyleClass("collapsed");
        } else
            this.messagesElement.appendChild(element);

        if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
            element.previousSibling.addStyleClass("console-adjacent-user-command-result");
    },

    _titleClicked: function(event)
    {
        var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
        if (groupTitleElement) {
            var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
            if (groupElement)
                if (groupElement.hasStyleClass("collapsed"))
                    groupElement.removeStyleClass("collapsed");
                else
                    groupElement.addStyleClass("collapsed");
            groupTitleElement.scrollIntoViewIfNeeded(true);
        }

        event.stopPropagation();
        event.preventDefault();
    }
}


/* Panel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Panel = function(name)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("panel");
    this.element.addStyleClass(name);
    this._panelName = name;

    WebInspector.applicationSettings.installSetting(this._sidebarWidthSettingName(), this._panelName + "-sidebar-width", undefined);
}

// Should by in sync with style declarations.
WebInspector.Panel.counterRightMargin = 25;

WebInspector.Panel.prototype = {
    get toolbarItem()
    {
        if (this._toolbarItem)
            return this._toolbarItem;

        // Sample toolbar item as markup:
        // <button class="toolbar-item resources toggleable">
        // <div class="toolbar-icon"></div>
        // <div class="toolbar-label">Resources</div>
        // </button>

        this._toolbarItem = document.createElement("button");
        this._toolbarItem.className = "toolbar-item toggleable";
        this._toolbarItem.panel = this;

        this._toolbarItem.addStyleClass(this._panelName);

        var iconElement = document.createElement("div");
        iconElement.className = "toolbar-icon";
        this._toolbarItem.appendChild(iconElement);

        if ("toolbarItemLabel" in this) {
            var labelElement = document.createElement("div");
            labelElement.className = "toolbar-label";
            labelElement.textContent = this.toolbarItemLabel;
            this._toolbarItem.appendChild(labelElement);
        }

        return this._toolbarItem;
    },

    get name()
    {
        return this._panelName;
    },

    show: function()
    {
        WebInspector.View.prototype.show.call(this);

        var statusBarItems = this.statusBarItems;
        if (statusBarItems) {
            this._statusBarItemContainer = document.createElement("div");
            for (var i = 0; i < statusBarItems.length; ++i)
                this._statusBarItemContainer.appendChild(statusBarItems[i]);
            document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer);
        }

        if ("_toolbarItem" in this)
            this._toolbarItem.addStyleClass("toggled-on");

        WebInspector.currentFocusElement = this.defaultFocusedElement;

        this.restoreSidebarWidth();
        this._restoreScrollPositions();
    },

    hide: function()
    {
        this._storeScrollPositions();
        WebInspector.View.prototype.hide.call(this);

        if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode)
            this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer);
        delete this._statusBarItemContainer;
        if ("_toolbarItem" in this)
            this._toolbarItem.removeStyleClass("toggled-on");
    },

    get defaultFocusedElement()
    {
        return this.sidebarTreeElement || this.element;
    },

    attach: function()
    {
        if (!this.element.parentNode)
            document.getElementById("main-panels").appendChild(this.element);
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var view = this._searchResults[i];
                if (view.searchCanceled)
                    view.searchCanceled();
                delete view.currentQuery;
            }
        }

        WebInspector.updateSearchMatchesCount(0, this);

        if (this._currentSearchChunkIntervalIdentifier) {
            clearInterval(this._currentSearchChunkIntervalIdentifier);
            delete this._currentSearchChunkIntervalIdentifier;
        }

        this._totalSearchMatches = 0;
        this._currentSearchResultIndex = 0;
        this._searchResults = [];
    },

    performSearch: function(query)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled(true);

        var searchableViews = this.searchableViews;
        if (!searchableViews || !searchableViews.length)
            return;

        var parentElement = this.viewsContainerElement;
        var visibleView = this.visibleView;
        var sortFuction = this.searchResultsSortFunction;

        var matchesCountUpdateTimeout = null;

        function updateMatchesCount()
        {
            WebInspector.updateSearchMatchesCount(this._totalSearchMatches, this);
            matchesCountUpdateTimeout = null;
        }

        function updateMatchesCountSoon()
        {
            if (matchesCountUpdateTimeout)
                return;
            // Update the matches count every half-second so it doesn't feel twitchy.
            matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
        }

        function finishedCallback(view, searchMatches)
        {
            if (!searchMatches)
                return;

            this._totalSearchMatches += searchMatches;
            this._searchResults.push(view);

            if (sortFuction)
                this._searchResults.sort(sortFuction);

            if (this.searchMatchFound)
                this.searchMatchFound(view, searchMatches);

            updateMatchesCountSoon.call(this);

            if (view === visibleView)
                view.jumpToFirstSearchResult();
        }

        var i = 0;
        var panel = this;
        var boundFinishedCallback = finishedCallback.bind(this);
        var chunkIntervalIdentifier = null;

        // Split up the work into chunks so we don't block the
        // UI thread while processing.

        function processChunk()
        {
            var view = searchableViews[i];

            if (++i >= searchableViews.length) {
                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
                    delete panel._currentSearchChunkIntervalIdentifier;
                clearInterval(chunkIntervalIdentifier);
            }

            if (!view)
                return;

            if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement)
                view.detach();

            view.currentQuery = query;
            view.performSearch(query, boundFinishedCallback);
        }

        processChunk();

        chunkIntervalIdentifier = setInterval(processChunk, 25);
        this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
    },

    jumpToNextSearchResult: function()
    {
        if (!this.showView || !this._searchResults || !this._searchResults.length)
            return;

        var showFirstResult = false;

        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
        if (this._currentSearchResultIndex === -1) {
            this._currentSearchResultIndex = 0;
            showFirstResult = true;
        }

        var currentView = this._searchResults[this._currentSearchResultIndex];

        if (currentView.showingLastSearchResult()) {
            if (++this._currentSearchResultIndex >= this._searchResults.length)
                this._currentSearchResultIndex = 0;
            currentView = this._searchResults[this._currentSearchResultIndex];
            showFirstResult = true;
        }

        if (currentView !== this.visibleView) {
            this.showView(currentView);
            WebInspector.focusSearchField();
        }

        if (showFirstResult)
            currentView.jumpToFirstSearchResult();
        else
            currentView.jumpToNextSearchResult();
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this.showView || !this._searchResults || !this._searchResults.length)
            return;

        var showLastResult = false;

        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
        if (this._currentSearchResultIndex === -1) {
            this._currentSearchResultIndex = 0;
            showLastResult = true;
        }

        var currentView = this._searchResults[this._currentSearchResultIndex];

        if (currentView.showingFirstSearchResult()) {
            if (--this._currentSearchResultIndex < 0)
                this._currentSearchResultIndex = (this._searchResults.length - 1);
            currentView = this._searchResults[this._currentSearchResultIndex];
            showLastResult = true;
        }

        if (currentView !== this.visibleView) {
            this.showView(currentView);
            WebInspector.focusSearchField();
        }

        if (showLastResult)
            currentView.jumpToLastSearchResult();
        else
            currentView.jumpToPreviousSearchResult();
    },

    createSidebar: function(parentElement, resizerParentElement)
    {
        if (this.sidebarElement)
            return;

        if (!parentElement)
            parentElement = this.element;

        if (!resizerParentElement)
            resizerParentElement = parentElement;

        this.sidebarElement = document.createElement("div");
        this.sidebarElement.className = "sidebar";
        parentElement.appendChild(this.sidebarElement);

        this.sidebarResizeElement = document.createElement("div");
        this.sidebarResizeElement.className = "sidebar-resizer-vertical";
        this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
        resizerParentElement.appendChild(this.sidebarResizeElement);

        this.sidebarTreeElement = document.createElement("ol");
        this.sidebarTreeElement.className = "sidebar-tree";
        this.sidebarElement.appendChild(this.sidebarTreeElement);

        this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
        this.sidebarTree.panel = this;
    },

    _sidebarWidthSettingName: function()
    {
        return this._panelName + "SidebarWidth";
    },

    _startSidebarDragging: function(event)
    {
        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
    },

    _sidebarDragging: function(event)
    {
        this.updateSidebarWidth(event.pageX);

        event.preventDefault();
    },

    _endSidebarDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
        this.saveSidebarWidth();
    },

    updateSidebarWidth: function(width)
    {
        if (!this.sidebarElement)
            return;

        if (this.sidebarElement.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        if (!("_currentSidebarWidth" in this))
            this._currentSidebarWidth = this.sidebarElement.offsetWidth;

        if (typeof width === "undefined")
            width = this._currentSidebarWidth;

        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);

        this._currentSidebarWidth = width;
        this.setSidebarWidth(width);

        this.updateMainViewWidth(width);
    },

    setSidebarWidth: function(width)
    {
        this.sidebarElement.style.width = width + "px";
        this.sidebarResizeElement.style.left = (width - 3) + "px";
    },

    restoreSidebarWidth: function()
    {
        var sidebarWidth = WebInspector.applicationSettings[this._sidebarWidthSettingName()];
        this.updateSidebarWidth(sidebarWidth);
    },

    saveSidebarWidth: function()
    {
        if (!this.sidebarElement)
            return;
        WebInspector.applicationSettings[this._sidebarWidthSettingName()] = this.sidebarElement.offsetWidth;
    },

    updateMainViewWidth: function(width)
    {
        // Should be implemented by ancestors.
    },

    resize: function()
    {
        var visibleView = this.visibleView;
        if (visibleView && "resize" in visibleView)
            visibleView.resize();
    },

    canShowSourceLine: function(url, line)
    {
        return false;
    },

    showSourceLine: function(url, line)
    {
        return false;
    },

    elementsToRestoreScrollPositionsFor: function()
    {
        return [];
    },

    _storeScrollPositions: function()
    {
        var elements = this.elementsToRestoreScrollPositionsFor();
        for (var i = 0; i < elements.length; ++i) {
            var container = elements[i];
            container._scrollTop = container.scrollTop;
        }
    },

    _restoreScrollPositions: function()
    {
        var elements = this.elementsToRestoreScrollPositionsFor();
        for (var i = 0; i < elements.length; ++i) {
            var container = elements[i];
            if (container._scrollTop)
                container.scrollTop = container._scrollTop;
        }
    }
}

WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype;

/* TimelineGrid.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelineGrid = function()
{
    this.element = document.createElement("div");

    this._itemsGraphsElement = document.createElement("div");
    this._itemsGraphsElement.id = "resources-graphs";
    this.element.appendChild(this._itemsGraphsElement);

    this._dividersElement = document.createElement("div");
    this._dividersElement.className = "resources-dividers";
    this.element.appendChild(this._dividersElement);

    this._eventDividersElement = document.createElement("div");
    this._eventDividersElement.className = "resources-event-dividers";
    this.element.appendChild(this._eventDividersElement);

    this._dividersLabelBarElement = document.createElement("div");
    this._dividersLabelBarElement.className = "resources-dividers-label-bar";
    this.element.appendChild(this._dividersLabelBarElement);
}

WebInspector.TimelineGrid.prototype = {
    get itemsGraphsElement()
    {
        return this._itemsGraphsElement;
    },

    
    updateDividers: function(force, calculator, paddingLeft)
    {
        var dividerCount = Math.round(this._dividersElement.offsetWidth / 64);
        var slice = calculator.boundarySpan / dividerCount;
        if (!force && this._currentDividerSlice === slice)
            return false;

        if (typeof paddingLeft !== "number")
            paddingLeft = 0;
        this._currentDividerSlice = slice;

        // Reuse divider elements and labels.
        var divider = this._dividersElement.firstChild;
        var dividerLabelBar = this._dividersLabelBarElement.firstChild;

        var dividersLabelBarElementClientWidth = this._dividersLabelBarElement.clientWidth;
        var clientWidth = dividersLabelBarElementClientWidth - paddingLeft;
        for (var i = paddingLeft ? 0 : 1; i <= dividerCount; ++i) {
            if (!divider) {
                divider = document.createElement("div");
                divider.className = "resources-divider";
                this._dividersElement.appendChild(divider);

                dividerLabelBar = document.createElement("div");
                dividerLabelBar.className = "resources-divider";
                var label = document.createElement("div");
                label.className = "resources-divider-label";
                dividerLabelBar._labelElement = label;
                dividerLabelBar.appendChild(label);
                this._dividersLabelBarElement.appendChild(dividerLabelBar);
                dividersLabelBarElementClientWidth = this._dividersLabelBarElement.clientWidth;
            }

            if (i === (paddingLeft ? 0 : 1)) {
                divider.addStyleClass("first");
                dividerLabelBar.addStyleClass("first");
            } else {
                divider.removeStyleClass("first");
                dividerLabelBar.removeStyleClass("first");
            }

            if (i === dividerCount) {
                divider.addStyleClass("last");
                dividerLabelBar.addStyleClass("last");
            } else {
                divider.removeStyleClass("last");
                dividerLabelBar.removeStyleClass("last");
            }

            var left = paddingLeft + clientWidth * (i / dividerCount);
            var percentLeft = 100 * left / dividersLabelBarElementClientWidth;
            this._setDividerAndBarLeft(divider, dividerLabelBar, percentLeft);

            if (!isNaN(slice))
                dividerLabelBar._labelElement.textContent = calculator.formatValue(slice * i);
            else
                dividerLabelBar._labelElement.textContent = "";

            divider = divider.nextSibling;
            dividerLabelBar = dividerLabelBar.nextSibling;
        }

        // Remove extras.
        while (divider) {
            var nextDivider = divider.nextSibling;
            this._dividersElement.removeChild(divider);
            divider = nextDivider;
        }
        while (dividerLabelBar) {
            var nextDivider = dividerLabelBar.nextSibling;
            this._dividersLabelBarElement.removeChild(dividerLabelBar);
            dividerLabelBar = nextDivider;
        }
        return true;
    },

    _setDividerAndBarLeft: function(divider, dividerLabelBar, percentLeft)
    {
        var percentStyleLeft = parseFloat(divider.style.left);
        if (!isNaN(percentStyleLeft) && Math.abs(percentStyleLeft - percentLeft) < 0.1)
            return;
        divider.style.left = percentLeft + "%";
        dividerLabelBar.style.left = percentLeft + "%";
    },

    addEventDivider: function(divider)
    {
        this._eventDividersElement.appendChild(divider);
    },

    addEventDividers: function(dividers)
    {
        this.element.removeChild(this._eventDividersElement);
        for (var i = 0; i < dividers.length; ++i)
            if (dividers[i])
                this._eventDividersElement.appendChild(dividers[i]);
        this.element.appendChild(this._eventDividersElement);
    },

    removeEventDividers: function()
    {
        this._eventDividersElement.removeChildren();
    },

    hideEventDividers: function()
    {
        this._eventDividersElement.addStyleClass("hidden");
    },

    showEventDividers: function()
    {
        this._eventDividersElement.removeStyleClass("hidden");
    },

    setScrollAndDividerTop: function(scrollTop, dividersTop)
    {
        this._dividersElement.style.top = scrollTop + "px";
        this._eventDividersElement.style.top = scrollTop + "px";
        this._dividersLabelBarElement.style.top = dividersTop + "px";
    }
}

/* Resource.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Resource = function(identifier, url)
{
    this.identifier = identifier;
    this._url = url;
    this._startTime = -1;
    this._endTime = -1;
    this._requestMethod = "";
    this._category = WebInspector.resourceCategories.other;
}

// Keep these in sync with WebCore::InspectorResource::Type
WebInspector.Resource.Type = {
    Document:   0,
    Stylesheet: 1,
    Image:      2,
    Font:       3,
    Script:     4,
    XHR:        5,
    Media:      6,
    WebSocket:  7,
    Other:      8,

    isTextType: function(type)
    {
        return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
    },

    toUIString: function(type)
    {
        return WebInspector.UIString(WebInspector.Resource.Type.toString(type));
    },

    // Returns locale-independent string identifier of resource type (primarily for use in extension API).
    // The IDs need to be kept in sync with webInspector.resoureces.Types object in ExtensionAPI.js.
    toString: function(type)
    {
        switch (type) {
            case this.Document:
                return "document";
            case this.Stylesheet:
                return "stylesheet";
            case this.Image:
                return "image";
            case this.Font:
                return "font";
            case this.Script:
                return "script";
            case this.XHR:
                return "XHR";
            case this.Media:
                return "media";
            case this.WebSocket:
                return "WebSocket";
            case this.Other:
            default:
                return "other";
        }
    }
}

WebInspector.Resource.prototype = {
    get url()
    {
        return this._url;
    },

    set url(x)
    {
        if (this._url === x)
            return;

        var oldURL = this._url;
        this._url = x;
        delete this._parsedQueryParameters;
        // FIXME: We should make the WebInspector object listen for the "url changed" event.
        // Then resourceURLChanged can be removed.
        WebInspector.resourceURLChanged(this, oldURL);

        this.dispatchEventToListeners("url changed");
    },

    get documentURL()
    {
        return this._documentURL;
    },

    set documentURL(x)
    {
        if (this._documentURL === x)
            return;
        this._documentURL = x;
    },

    get domain()
    {
        return this._domain;
    },

    set domain(x)
    {
        if (this._domain === x)
            return;
        this._domain = x;
    },

    get lastPathComponent()
    {
        return this._lastPathComponent;
    },

    set lastPathComponent(x)
    {
        if (this._lastPathComponent === x)
            return;
        this._lastPathComponent = x;
        this._lastPathComponentLowerCase = x ? x.toLowerCase() : null;
    },

    get displayName()
    {
        var title = this.lastPathComponent;
        if (!title)
            title = this.displayDomain;
        if (!title && this.url)
            title = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
        if (title === "/")
            title = this.url;
        return title;
    },

    get displayDomain()
    {
        // WebInspector.Database calls this, so don't access more than this.domain.
        if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain)))
            return this.domain;
        return "";
    },

    get startTime()
    {
        return this._startTime || -1;
    },

    set startTime(x)
    {
        if (this._startTime === x)
            return;

        this._startTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get responseReceivedTime()
    {
        return this._responseReceivedTime || -1;
    },

    set responseReceivedTime(x)
    {
        if (this._responseReceivedTime === x)
            return;

        this._responseReceivedTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get endTime()
    {
        return this._endTime || -1;
    },

    set endTime(x)
    {
        if (this._endTime === x)
            return;

        this._endTime = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get duration()
    {
        if (this._endTime === -1 || this._startTime === -1)
            return -1;
        return this._endTime - this._startTime;
    },

    get latency()
    {
        if (this._responseReceivedTime === -1 || this._startTime === -1)
            return -1;
        return this._responseReceivedTime - this._startTime;
    },

    get receiveDuration()
    {
        if (this._endTime === -1 || this._responseReceivedTime === -1)
            return -1;
        return this._endTime - this._responseReceivedTime;
    },

    get resourceSize()
    {
        return this._resourceSize || 0;
    },

    set resourceSize(x)
    {
        if (this._resourceSize === x)
            return;

        this._resourceSize = x;

        if (WebInspector.panels.resources)
            WebInspector.panels.resources.refreshResource(this);
    },

    get transferSize()
    {
        // FIXME: this is wrong for chunked-encoding resources.
        return this.cached ? 0 : Number(this.responseHeaders["Content-Length"] || this.resourceSize || 0);
    },

    get expectedContentLength()
    {
        return this._expectedContentLength || 0;
    },

    set expectedContentLength(x)
    {
        if (this._expectedContentLength === x)
            return;
        this._expectedContentLength = x;
    },

    get finished()
    {
        return this._finished;
    },

    set finished(x)
    {
        if (this._finished === x)
            return;

        this._finished = x;

        if (x) {
            this._checkWarnings();
            this.dispatchEventToListeners("finished");
        }
    },

    get failed()
    {
        return this._failed;
    },

    set failed(x)
    {
        this._failed = x;
    },

    get category()
    {
        return this._category;
    },

    set category(x)
    {
        if (this._category === x)
            return;

        var oldCategory = this._category;
        if (oldCategory)
            oldCategory.removeResource(this);

        this._category = x;

        if (this._category)
            this._category.addResource(this);

        if (WebInspector.panels.resources) {
            WebInspector.panels.resources.refreshResource(this);
            WebInspector.panels.resources.recreateViewForResourceIfNeeded(this);
        }
    },

    get cached()
    {
        return this._cached;
    },

    set cached(x)
    {
        this._cached = x;
        this.dispatchEventToListeners("cached changed");
    },

    get mimeType()
    {
        return this._mimeType;
    },

    set mimeType(x)
    {
        if (this._mimeType === x)
            return;

        this._mimeType = x;
    },

    get type()
    {
        return this._type;
    },

    set type(x)
    {
        if (this._type === x)
            return;

        this._type = x;

        switch (x) {
            case WebInspector.Resource.Type.Document:
                this.category = WebInspector.resourceCategories.documents;
                break;
            case WebInspector.Resource.Type.Stylesheet:
                this.category = WebInspector.resourceCategories.stylesheets;
                break;
            case WebInspector.Resource.Type.Script:
                this.category = WebInspector.resourceCategories.scripts;
                break;
            case WebInspector.Resource.Type.Image:
                this.category = WebInspector.resourceCategories.images;
                break;
            case WebInspector.Resource.Type.Font:
                this.category = WebInspector.resourceCategories.fonts;
                break;
            case WebInspector.Resource.Type.XHR:
                this.category = WebInspector.resourceCategories.xhr;
                break;
            case WebInspector.Resource.Type.WebSocket:
                this.category = WebInspector.resourceCategories.websocket;
                break;
            case WebInspector.Resource.Type.Other:
            default:
                this.category = WebInspector.resourceCategories.other;
                break;
        }
    },

    get requestHeaders()
    {
        if (this._requestHeaders === undefined)
            this._requestHeaders = {};
        return this._requestHeaders;
    },

    set requestHeaders(x)
    {
        if (this._requestHeaders === x)
            return;

        this._requestHeaders = x;
        delete this._sortedRequestHeaders;

        this.dispatchEventToListeners("requestHeaders changed");
    },

    get sortedRequestHeaders()
    {
        if (this._sortedRequestHeaders !== undefined)
            return this._sortedRequestHeaders;

        this._sortedRequestHeaders = [];
        for (var key in this.requestHeaders)
            this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]});
        this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });

        return this._sortedRequestHeaders;
    },

    requestHeaderValue: function(headerName)
    {
        return this._headerValue(this.requestHeaders, headerName);
    },

    get requestFormData()
    {
        return this._requestFormData;
    },

    set requestFormData(x)
    {
        this._requestFormData = x;
        delete this._parsedFormParameters;
    },

    get responseHeaders()
    {
        if (this._responseHeaders === undefined)
            this._responseHeaders = {};
        return this._responseHeaders;
    },

    set responseHeaders(x)
    {
        if (this._responseHeaders === x)
            return;

        this._responseHeaders = x;
        delete this._sortedResponseHeaders;

        this.dispatchEventToListeners("responseHeaders changed");
    },

    get sortedResponseHeaders()
    {
        if (this._sortedResponseHeaders !== undefined)
            return this._sortedResponseHeaders;

        this._sortedResponseHeaders = [];
        for (var key in this.responseHeaders)
            this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]});
        this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });

        return this._sortedResponseHeaders;
    },

    responseHeaderValue: function(headerName)
    {
        return this._headerValue(this.responseHeaders, headerName);
    },

    get queryParameters()
    {
        if (this._parsedQueryParameters)
            return this._parsedQueryParameters;
        var queryString = this.url.split("?", 2)[1];
        if (!queryString)
            return;
        this._parsedQueryParameters = this._parseParameters(queryString);
        return this._parsedQueryParameters;
    },

    get formParameters()
    {
        if (this._parsedFormParameters)
            return this._parsedFormParameters;
        if (!this.requestFormData)
            return;
        var requestContentType = this.requestHeaderValue("Content-Type");
        if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
            return;
        this._parsedFormParameters = this._parseParameters(this.requestFormData);
        return this._parsedFormParameters;
    },

    _parseParameters: function(queryString)
    {
        function parseNameValue(pair)
        {
            var parameter = {};
            var splitPair = pair.split("=", 2);

            parameter.name = splitPair[0];
            if (splitPair.length === 1)
                parameter.value = "";
            else
                parameter.value = splitPair[1];
            return parameter;
        }
        return queryString.split("&").map(parseNameValue);
    },

    _headerValue: function(headers, headerName)
    {
        headerName = headerName.toLowerCase();
        for (var header in headers) {
            if (header.toLowerCase() === headerName)
                return headers[header];
        }
    },

    get scripts()
    {
        if (!("_scripts" in this))
            this._scripts = [];
        return this._scripts;
    },

    addScript: function(script)
    {
        if (!script)
            return;
        this.scripts.unshift(script);
        script.resource = this;
    },

    removeAllScripts: function()
    {
        if (!this._scripts)
            return;

        for (var i = 0; i < this._scripts.length; ++i) {
            if (this._scripts[i].resource === this)
                delete this._scripts[i].resource;
        }

        delete this._scripts;
    },

    removeScript: function(script)
    {
        if (!script)
            return;

        if (script.resource === this)
            delete script.resource;

        if (!this._scripts)
            return;

        this._scripts.remove(script);
    },

    get errors()
    {
        return this._errors || 0;
    },

    set errors(x)
    {
        this._errors = x;
    },

    get warnings()
    {
        return this._warnings || 0;
    },

    set warnings(x)
    {
        this._warnings = x;
    },

    _mimeTypeIsConsistentWithType: function()
    {
        // If status is an error, content is likely to be of an inconsistent type,
        // as it's going to be an error message. We do not want to emit a warning
        // for this, though, as this will already be reported as resource loading failure.
        if (this.statusCode >= 400)
            return true;

        if (typeof this.type === "undefined"
         || this.type === WebInspector.Resource.Type.Other
         || this.type === WebInspector.Resource.Type.XHR
         || this.type === WebInspector.Resource.Type.WebSocket)
            return true;

        if (this.mimeType in WebInspector.MIMETypes)
            return this.type in WebInspector.MIMETypes[this.mimeType];

        return false;
    },

    _checkWarnings: function()
    {
        for (var warning in WebInspector.Warnings)
            this._checkWarning(WebInspector.Warnings[warning]);
    },

    _checkWarning: function(warning)
    {
        var msg;
        switch (warning.id) {
            case WebInspector.Warnings.IncorrectMIMEType.id:
                if (!this._mimeTypeIsConsistentWithType())
                    msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
                        WebInspector.ConsoleMessage.MessageType.Log, 
                        WebInspector.ConsoleMessage.MessageLevel.Warning,
                        -1,
                        this.url,
                        null,
                        1,
                        String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, WebInspector.Resource.Type.toUIString(this.type), this.mimeType),
                        null,
                        null);
                break;
        }

        if (msg)
            WebInspector.console.addMessage(msg);
    }
}

WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.Resource.CompareByStartTime = function(a, b)
{
    return a.startTime - b.startTime;
}

WebInspector.Resource.CompareByResponseReceivedTime = function(a, b)
{
    var aVal = a.responseReceivedTime;
    var bVal = b.responseReceivedTime;
    if (aVal === -1 ^ bVal === -1)
        return bVal - aVal;
    return aVal - bVal;
}

WebInspector.Resource.CompareByEndTime = function(a, b)
{
    var aVal = a.endTime;
    var bVal = b.endTime;
    if (aVal === -1 ^ bVal === -1)
        return bVal - aVal;
    return aVal - bVal;
}

WebInspector.Resource.CompareByDuration = function(a, b)
{
    return a.duration - b.duration;
}

WebInspector.Resource.CompareByLatency = function(a, b)
{
    return a.latency - b.latency;
}

WebInspector.Resource.CompareBySize = function(a, b)
{
    return a.resourceSize - b.resourceSize;
}

WebInspector.Resource.CompareByTransferSize = function(a, b)
{
    return a.transferSize - b.transferSize;
}

/* ResourceCategory.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourceCategory = function(name, title, color)
{
    this.name = name;
    this.title = title;
    this.color = color;
    this.resources = [];
}

WebInspector.ResourceCategory.prototype = {

    toString: function()
    {
        return this.title;
    },

    addResource: function(resource)
    {
        var a = resource;
        var resourcesLength = this.resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var b = this.resources[i];
            if (a._lastPathComponentLowerCase && b._lastPathComponentLowerCase)
                if (a._lastPathComponentLowerCase < b._lastPathComponentLowerCase)
                    break;
            else if (a.name && b.name)
                if (a.name < b.name)
                    break;
        }

        this.resources.splice(i, 0, resource);
    },

    removeResource: function(resource)
    {
        this.resources.remove(resource, true);
    },

    removeAllResources: function(resource)
    {
        this.resources = [];
    }
}

/* Database.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Database = function(id, domain, name, version)
{
    this._id = id;
    this._domain = domain;
    this._name = name;
    this._version = version;
}

WebInspector.Database.successCallbacks = {};
WebInspector.Database.errorCallbacks = {};

WebInspector.Database.prototype = {
    get id()
    {
        return this._id;
    },

    get name()
    {
        return this._name;
    },

    set name(x)
    {
        this._name = x;
    },

    get version()
    {
        return this._version;
    },

    set version(x)
    {
        this._version = x;
    },

    get domain()
    {
        return this._domain;
    },

    set domain(x)
    {
        this._domain = x;
    },

    get displayDomain()
    {
        return WebInspector.Resource.prototype.__lookupGetter__("displayDomain").call(this);
    },

    getTableNames: function(callback)
    {
        function sortingCallback(names)
        {
            callback(names.sort());
        }
        InspectorBackend.getDatabaseTableNames(this._id, sortingCallback);
    },
    
    executeSql: function(query, onSuccess, onError)
    {
        function callback(success, transactionId)
        {
            if (!success) {
                onError(WebInspector.UIString("Database not found."));
                return;
            }
            WebInspector.Database.successCallbacks[transactionId] = onSuccess;
            WebInspector.Database.errorCallbacks[transactionId] = onError;
        }
        InspectorBackend.executeSQL(this._id, query, callback);
    }
}

WebInspector.sqlTransactionSucceeded = function(transactionId, columnNames, values)
{
    var callback = WebInspector.Database.successCallbacks[transactionId];
    if (!callback)
        return;
    delete WebInspector.Database.successCallbacks[transactionId];
    callback(columnNames, values);
}

WebInspector.sqlTransactionFailed = function(transactionId, errorObj)
{
    var callback = WebInspector.Database.errorCallbacks[transactionId];
    if (!callback)
        return;
    delete WebInspector.Database.errorCallbacks[transactionId];
    callback(errorObj);
}

/* DOMStorage.js */

/*
 * Copyright (C) 2008 Nokia Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMStorage = function(id, domain, isLocalStorage)
{
    this._id = id;
    this._domain = domain;
    this._isLocalStorage = isLocalStorage;
}

WebInspector.DOMStorage.prototype = {
    get id()
    {
        return this._id;
    },

    get domStorage()
    {
        return this._domStorage;
    },

    get domain()
    {
        return this._domain;
    },

    get isLocalStorage()
    {
        return this._isLocalStorage;
    },

    getEntries: function(callback)
    {
        InspectorBackend.getDOMStorageEntries(this._id, callback);
    },
    
    setItem: function(key, value, callback)
    {
        InspectorBackend.setDOMStorageItem(this._id, key, value, callback);
    },
    
    removeItem: function(key, callback)
    {
        InspectorBackend.removeDOMStorageItem(this._id, key, callback);
    }
}


/* DOMStorageItemsView.js */

/*
 * Copyright (C) 2008 Nokia Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMStorageItemsView = function(domStorage)
{
    WebInspector.View.call(this);

    this.domStorage = domStorage;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
    this.deleteButton.visible = false;
    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
}

WebInspector.DOMStorageItemsView.prototype = {
    get statusBarItems()
    {
        return [this.refreshButton.element, this.deleteButton.element];
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.update();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this.deleteButton.visible = false;
    },

    update: function()
    {
        this.element.removeChildren();
        var callback = this._showDOMStorageEntries.bind(this);
        this.domStorage.getEntries(callback);
    },

    _showDOMStorageEntries: function(entries)
    {
        this._dataGrid = this._dataGridForDOMStorageEntries(entries);
        this.element.appendChild(this._dataGrid.element);
        this._dataGrid.autoSizeColumns(10);
        this.deleteButton.visible = true;
    },

    resize: function()
    {
        if (this._dataGrid)
            this._dataGrid.updateWidths();
    },

    _dataGridForDOMStorageEntries: function(entries)
    {
        var columns = {};
        columns[0] = {};
        columns[1] = {};
        columns[0].title = WebInspector.UIString("Key");
        columns[1].title = WebInspector.UIString("Value");

        var nodes = [];

        var keys = [];
        var length = entries.length;
        for (var i = 0; i < entries.length; i++) {
            var data = {};

            var key = entries[i][0];
            data[0] = key;
            var value = entries[i][1];
            data[1] = value;
            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = true;
            nodes.push(node);
            keys.push(key);
        }

        var dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this));
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);
        dataGrid.addCreationNode(false);
        if (length > 0)
            nodes[0].selected = true;
        return dataGrid;
    },

    _deleteButtonClicked: function(event)
    {
        if (!this._dataGrid || !this._dataGrid.selectedNode)
            return;

        this._deleteCallback(this._dataGrid.selectedNode);
    },

    _refreshButtonClicked: function(event)
    {
        this.update();
    },
    
    _editingCallback: function(editingNode, columnIdentifier, oldText, newText)
    {
        var domStorage = this.domStorage;
        if (columnIdentifier === 0) {
            if (oldText)
                domStorage.removeItem(oldText);

            domStorage.setItem(newText, editingNode.data[1]);
        } else {
            domStorage.setItem(editingNode.data[0], newText);
        }
        
        this.update();
    },
    
    _deleteCallback: function(node)
    {
        if (!node || node.isCreationNode)
            return;

        if (this.domStorage)
            this.domStorage.removeItem(node.data[0]);
            
        this.update();
    }
}

WebInspector.DOMStorageItemsView.prototype.__proto__ = WebInspector.View.prototype;

/* DataGrid.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.         IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
{
    this.element = document.createElement("div");
    this.element.className = "data-grid";
    this.element.tabIndex = 0;
    this.element.addEventListener("keydown", this._keyDown.bind(this), false);

    this._headerTable = document.createElement("table");
    this._headerTable.className = "header";
    this._headerTableHeaders = {};

    this._dataTable = document.createElement("table");
    this._dataTable.className = "data";

    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
    
    this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
    
    // FIXME: Add a createCallback which is different from editCallback and has different
    // behavior when creating a new node.
    if (editCallback) {
        this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
        this._editCallback = editCallback;
    }
    if (deleteCallback)
        this._deleteCallback = deleteCallback;
    
    this.aligned = {};

    var scrollContainer = document.createElement("div");
    scrollContainer.className = "data-container";
    scrollContainer.appendChild(this._dataTable);

    this.element.appendChild(this._headerTable);
    this.element.appendChild(scrollContainer);

    var headerRow = document.createElement("tr");
    var columnGroup = document.createElement("colgroup");
    this._columnCount = 0;

    for (var columnIdentifier in columns) {
        var column = columns[columnIdentifier];
        if (column.disclosure)
            this.disclosureColumnIdentifier = columnIdentifier;

        var col = document.createElement("col");
        if (column.width)
            col.style.width = column.width;
        column.element = col;
        columnGroup.appendChild(col);

        var cell = document.createElement("th");
        cell.className = columnIdentifier + "-column";
        cell.columnIdentifier = columnIdentifier;
        this._headerTableHeaders[columnIdentifier] = cell;

        var div = document.createElement("div");
        if (column.titleDOMFragment)
            div.appendChild(column.titleDOMFragment);
        else
            div.textContent = column.title;
        cell.appendChild(div);

        if (column.sort) {
            cell.addStyleClass("sort-" + column.sort);
            this._sortColumnCell = cell;
        }

        if (column.sortable) {
            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
            cell.addStyleClass("sortable");
        }

        if (column.aligned)
            this.aligned[columnIdentifier] = column.aligned;

        headerRow.appendChild(cell);

        ++this._columnCount;
    }

    columnGroup.span = this._columnCount;

    var cell = document.createElement("th");
    cell.className = "corner";
    headerRow.appendChild(cell);

    this._headerTableColumnGroup = columnGroup;
    this._headerTable.appendChild(this._headerTableColumnGroup);
    this.headerTableBody.appendChild(headerRow);

    var fillerRow = document.createElement("tr");
    fillerRow.className = "filler";

    for (var columnIdentifier in columns) {
        var column = columns[columnIdentifier];
        var cell = document.createElement("td");
        cell.className = columnIdentifier + "-column";
        fillerRow.appendChild(cell);
    }

    this._dataTableColumnGroup = columnGroup.cloneNode(true);
    this._dataTable.appendChild(this._dataTableColumnGroup);
    this.dataTableBody.appendChild(fillerRow);

    this.columns = columns || {};
    this._columnsArray = [];
    for (var columnIdentifier in columns) {
        columns[columnIdentifier].ordinal = this._columnsArray.length;
        this._columnsArray.push(columns[columnIdentifier]);
    }

    for (var i = 0; i < this._columnsArray.length; ++i)
        this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];

    this.children = [];
    this.selectedNode = null;
    this.expandNodesWhenArrowing = false;
    this.root = true;
    this.hasChildren = false;
    this.expanded = true;
    this.revealed = true;
    this.selected = false;
    this.dataGrid = this;
    this.indentWidth = 15;
    this.resizers = [];
    this._columnWidthsInitialized = false;
}

WebInspector.DataGrid.prototype = {
    _ondblclick: function(event)
    {
        if (this._editing || this._editingNode)
            return;

        this._startEditing(event.target);
    },

    _startEditingColumnOfDataGridNode: function(node, column)
    {
        this._editing = true;
        this._editingNode = node;
        this._editingNode.select();

        var element = this._editingNode._element.children[column];
        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    _startEditing: function(target)
    {
        var element = target.enclosingNodeOrSelfWithNodeName("td");
        if (!element)
            return;

        this._editingNode = this.dataGridNodeFromNode(target);
        if (!this._editingNode) {
            if (!this.creationNode)
                return;
            this._editingNode = this.creationNode;
        }

        // Force editing the 1st column when editing the creation node
        if (this._editingNode.isCreationNode)
            return this._startEditingColumnOfDataGridNode(this._editingNode, 0);

        this._editing = true;
        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    _editingCommitted: function(element, newText, oldText, context, moveDirection)
    {
        // FIXME: We need more column identifiers here throughout this function.
        // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
        
        // FIXME: Better way to do this than regular expressions?
        var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1]);

        var textBeforeEditing = this._editingNode.data[columnIdentifier];
        var currentEditingNode = this._editingNode;

        function moveToNextIfNeeded(wasChange) {
            if (!moveDirection)
                return;

            if (moveDirection === "forward") {
                if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
                    return;

                if (columnIdentifier === 0)
                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);

                var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
                if (nextDataGridNode)
                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
                if (currentEditingNode.isCreationNode && wasChange) {
                    addCreationNode(false);
                    return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
                }
                return;
            }

            if (moveDirection === "backward") {
                if (columnIdentifier === 1)
                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
                    var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);

                if (nextDataGridNode)
                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
                return;
            }
        }

        if (textBeforeEditing == newText) {
            this._editingCancelled(element);
            moveToNextIfNeeded.call(this, false);
            return;
        }

        // Update the text in the datagrid that we typed
        this._editingNode.data[columnIdentifier] = newText;
        
        // Make the callback - expects an editing node (table row), the column number that is being edited,
        // the text that used to be there, and the new text.
        this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);

        if (this._editingNode.isCreationNode)
            this.addCreationNode(false);

        this._editingCancelled(element);
        moveToNextIfNeeded.call(this, true);
    },

    _editingCancelled: function(element, context)
    {
        delete this._editing;
        this._editingNode = null;
    },
    
    get sortColumnIdentifier()
    {
        if (!this._sortColumnCell)
            return null;
        return this._sortColumnCell.columnIdentifier;
    },

    get sortOrder()
    {
        if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
            return "ascending";
        if (this._sortColumnCell.hasStyleClass("sort-descending"))
            return "descending";
        return null;
    },

    get headerTableBody()
    {
        if ("_headerTableBody" in this)
            return this._headerTableBody;

        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
        if (!this._headerTableBody) {
            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
        }

        return this._headerTableBody;
    },

    get dataTableBody()
    {
        if ("_dataTableBody" in this)
            return this._dataTableBody;

        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
        if (!this._dataTableBody) {
            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
        }

        return this._dataTableBody;
    },

    autoSizeColumns: function(minPercent, maxPercent)
    {
        if (minPercent)
            minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
        var widths = {};
        var columns = this.columns;
        for (var columnIdentifier in columns)
            widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;

        for (var i = 0; i < this.children.length; ++i) {
            var node = this.children[i];
            for (var columnIdentifier in columns) {
                var text = node.data[columnIdentifier] || "";
                if (text.length > widths[columnIdentifier])
                    widths[columnIdentifier] = text.length;
            }
        }

        var totalColumnWidths = 0;
        for (var columnIdentifier in columns)
            totalColumnWidths += widths[columnIdentifier];

        var recoupPercent = 0;
        for (var columnIdentifier in columns) {
            var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
            if (minPercent && width < minPercent) {
                recoupPercent += (minPercent - width);
                width = minPercent;
            } else if (maxPercent && width > maxPercent) {
                recoupPercent -= (width - maxPercent);
                width = maxPercent;
            }
            widths[columnIdentifier] = width;
        }

        while (minPercent && recoupPercent > 0) {
            for (var columnIdentifier in columns) {
                if (widths[columnIdentifier] > minPercent) {
                    --widths[columnIdentifier];
                    --recoupPercent;
                    if (!recoupPercent)
                        break;
                }
            }
        }

        while (maxPercent && recoupPercent < 0) {
            for (var columnIdentifier in columns) {
                if (widths[columnIdentifier] < maxPercent) {
                    ++widths[columnIdentifier];
                    ++recoupPercent;
                    if (!recoupPercent)
                        break;
                }
            }
        }

        for (var columnIdentifier in columns)
            columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
        this._columnWidthsInitialized = false;
        this.updateWidths();
    },

    // Updates the widths of the table, including the positions of the column
    // resizers.
    //
    // IMPORTANT: This function MUST be called once after the element of the
    // DataGrid is attached to its parent element and every subsequent time the
    // width of the parent element is changed in order to make it possible to
    // resize the columns.
    //
    // If this function is not called after the DataGrid is attached to its
    // parent element, then the DataGrid's columns will not be resizable.
    updateWidths: function()
    {
        var headerTableColumns = this._headerTableColumnGroup.children;
        
        var tableWidth = this._dataTable.offsetWidth;
        var numColumns = headerTableColumns.length;
        
        if (!this._columnWidthsInitialized) {
            // Give all the columns initial widths now so that during a resize,
            // when the two columns that get resized get a percent value for
            // their widths, all the other columns already have percent values
            // for their widths.
            for (var i = 0; i < numColumns; i++) {
                var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
                var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
                this._headerTableColumnGroup.children[i].style.width = percentWidth;
                this._dataTableColumnGroup.children[i].style.width = percentWidth;
            }
            this._columnWidthsInitialized = true;
        }
        this._positionResizers();
        this.dispatchEventToListeners("width changed");
    },

    columnWidthsMap: function()
    {
        var result = {};
        for (var i = 0; i < this._columnsArray.length; ++i) {
            var width = this._headerTableColumnGroup.children[i].style.width;
            result[this._columnsArray[i].columnIdentifier] = parseFloat(width);
        }
        return result;
    },

    applyColumnWidthsMap: function(columnWidthsMap)
    {
        for (var columnIdentifier in this.columns) {
            var column = this.columns[columnIdentifier];
            var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
            this._headerTableColumnGroup.children[column.ordinal].style.width = width;
            this._dataTableColumnGroup.children[column.ordinal].style.width = width;
        }

        // Normalize widths
        delete this._columnWidthsInitialized;
        this.updateWidths();
    },

    isColumnVisible: function(columnIdentifier)
    {
        var column = this.columns[columnIdentifier];
        var columnElement = column.element;
        return !columnElement.hidden;
    },

    showColumn: function(columnIdentifier)
    {
        var column = this.columns[columnIdentifier];
        var columnElement = column.element;
        if (!columnElement.hidden)
            return;

        columnElement.hidden = false;
        columnElement.removeStyleClass("hidden");

        var columnBodyElement = column.bodyElement;
        columnBodyElement.hidden = false;
        columnBodyElement.removeStyleClass("hidden");
    },

    hideColumn: function(columnIdentifier)
    {
        var column = this.columns[columnIdentifier];
        var columnElement = column.element;
        if (columnElement.hidden)
            return;

        var oldWidth = parseFloat(columnElement.style.width);

        columnElement.hidden = true;
        columnElement.addStyleClass("hidden");
        columnElement.style.width = 0;

        var columnBodyElement = column.bodyElement;
        columnBodyElement.hidden = true;
        columnBodyElement.addStyleClass("hidden");
        columnBodyElement.style.width = 0;

        this._columnWidthsInitialized = false;
    },

    _positionResizers: function()
    {
        var headerTableColumns = this._headerTableColumnGroup.children;
        var numColumns = headerTableColumns.length;
        var left = 0;
        var previousResizer = null;

        // Make n - 1 resizers for n columns. 
        for (var i = 0; i < numColumns - 1; i++) {
            var resizer = this.resizers[i];

            if (!resizer) {
                // This is the first call to updateWidth, so the resizers need
                // to be created.
                resizer = document.createElement("div");
                resizer.addStyleClass("data-grid-resizer");
                // This resizer is associated with the column to its right.
                resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
                this.element.appendChild(resizer);
                this.resizers[i] = resizer;
            }

            // Get the width of the cell in the first (and only) row of the
            // header table in order to determine the width of the column, since
            // it is not possible to query a column for its width.
            left += this.headerTableBody.rows[0].cells[i].offsetWidth;

            var columnIsVisible = !this._headerTableColumnGroup.children[i].hidden;
            if (columnIsVisible) {
                resizer.style.removeProperty("display");
                resizer.style.left = left + "px";
                resizer.leftNeighboringColumnID = i;
                if (previousResizer)
                    previousResizer.rightNeighboringColumnID = i;
                previousResizer = resizer;
            } else {
                resizer.style.setProperty("display", "none");
                resizer.leftNeighboringColumnID = 0;
                resizer.rightNeighboringColumnID = 0;
            }
        }
        if (previousResizer)
            previousResizer.rightNeighboringColumnID = numColumns - 1;
    },

    addCreationNode: function(hasChildren)
    {
        if (this.creationNode)
            this.creationNode.makeNormal();

        var emptyData = {};
        for (var column in this.columns)
            emptyData[column] = '';
        this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
        this.appendChild(this.creationNode);
    },

    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        if (!child)
            throw("insertChild: Node can't be undefined or null.");
        if (child.parent === this)
            throw("insertChild: Node is already a child of this node.");

        if (child.parent)
            child.parent.removeChild(child);

        this.children.splice(index, 0, child);
        this.hasChildren = true;

        child.parent = this;
        child.dataGrid = this.dataGrid;
        child._recalculateSiblings(index);

        delete child._depth;
        delete child._revealed;
        delete child._attached;
        child._shouldRefreshChildren = true;

        var current = child.children[0];
        while (current) {
            current.dataGrid = this.dataGrid;
            delete current._depth;
            delete current._revealed;
            delete current._attached;
            current._shouldRefreshChildren = true;
            current = current.traverseNextNode(false, child, true);
        }

        if (this.expanded)
            child._attach();
    },

    removeChild: function(child)
    {
        if (!child)
            throw("removeChild: Node can't be undefined or null.");
        if (child.parent !== this)
            throw("removeChild: Node is not a child of this node.");

        child.deselect();
        child._detach();

        this.children.remove(child, true);

        if (child.previousSibling)
            child.previousSibling.nextSibling = child.nextSibling;
        if (child.nextSibling)
            child.nextSibling.previousSibling = child.previousSibling;

        child.dataGrid = null;
        child.parent = null;
        child.nextSibling = null;
        child.previousSibling = null;

        if (this.children.length <= 0)
            this.hasChildren = false;
    },

    removeChildren: function()
    {
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            child.deselect();
            child._detach();

            child.dataGrid = null;
            child.parent = null;
            child.nextSibling = null;
            child.previousSibling = null;
        }

        this.children = [];
        this.hasChildren = false;
    },

    removeChildrenRecursive: function()
    {
        var childrenToRemove = this.children;

        var child = this.children[0];
        while (child) {
            if (child.children.length)
                childrenToRemove = childrenToRemove.concat(child.children);
            child = child.traverseNextNode(false, this, true);
        }

        for (var i = 0; i < childrenToRemove.length; ++i) {
            var child = childrenToRemove[i];
            child.deselect();
            child._detach();

            child.children = [];
            child.dataGrid = null;
            child.parent = null;
            child.nextSibling = null;
            child.previousSibling = null;
        }

        this.children = [];
    },

    sortNodes: function(comparator, reverseMode)
    {
        function comparatorWrapper(a, b)
        {
            if (a._dataGridNode._data.summaryRow)
                return 1;
            if (b._dataGridNode._data.summaryRow)
                return -1;

            var aDataGirdNode = a._dataGridNode;
            var bDataGirdNode = b._dataGridNode;
            return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
        }

        var tbody = this.dataTableBody;
        var tbodyParent = tbody.parentElement;
        tbodyParent.removeChild(tbody);

        var childNodes = tbody.childNodes;
        var fillerRow = childNodes[childNodes.length - 1];

        var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
        sortedRows.sort(comparatorWrapper.bind(this));
        var sortedRowsLength = sortedRows.length;

        tbody.removeChildren();
        var previousSiblingNode = null;
        for (var i = 0; i < sortedRowsLength; ++i) {
            var row = sortedRows[i];
            var node = row._dataGridNode;
            node.previousSibling = previousSiblingNode;
            if (previousSiblingNode)
                previousSiblingNode.nextSibling = node;
            tbody.appendChild(row);
            previousSiblingNode = node;
        }
        if (previousSiblingNode)
            previousSiblingNode.nextSibling = null;

        tbody.appendChild(fillerRow);
        tbodyParent.appendChild(tbody);
    },

    _keyDown: function(event)
    {
        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
            return;

        var handled = false;
        var nextSelectedNode;
        if (event.keyIdentifier === "Up" && !event.altKey) {
            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
            while (nextSelectedNode && !nextSelectedNode.selectable)
                nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
            handled = nextSelectedNode ? true : false;
        } else if (event.keyIdentifier === "Down" && !event.altKey) {
            nextSelectedNode = this.selectedNode.traverseNextNode(true);
            while (nextSelectedNode && !nextSelectedNode.selectable)
                nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
            handled = nextSelectedNode ? true : false;
        } else if (event.keyIdentifier === "Left") {
            if (this.selectedNode.expanded) {
                if (event.altKey)
                    this.selectedNode.collapseRecursively();
                else
                    this.selectedNode.collapse();
                handled = true;
            } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
                handled = true;
                if (this.selectedNode.parent.selectable) {
                    nextSelectedNode = this.selectedNode.parent;
                    handled = nextSelectedNode ? true : false;
                } else if (this.selectedNode.parent)
                    this.selectedNode.parent.collapse();
            }
        } else if (event.keyIdentifier === "Right") {
            if (!this.selectedNode.revealed) {
                this.selectedNode.reveal();
                handled = true;
            } else if (this.selectedNode.hasChildren) {
                handled = true;
                if (this.selectedNode.expanded) {
                    nextSelectedNode = this.selectedNode.children[0];
                    handled = nextSelectedNode ? true : false;
                } else {
                    if (event.altKey)
                        this.selectedNode.expandRecursively();
                    else
                        this.selectedNode.expand();
                }
            }
        } else if (event.keyCode === 8 || event.keyCode === 46) {
            if (this._deleteCallback) {
                handled = true;
                this._deleteCallback(this.selectedNode);
            }
        } else if (isEnterKey(event)) {
            if (this._editCallback) {
                handled = true;
                // The first child of the selected element is the <td class="0-column">,
                // and that's what we want to edit.
                this._startEditing(this.selectedNode._element.children[0]);
            }
        }

        if (nextSelectedNode) {
            nextSelectedNode.reveal();
            nextSelectedNode.select();
        }

        if (handled) {
            event.preventDefault();
            event.stopPropagation();
        }
    },

    expand: function()
    {
        // This is the root, do nothing.
    },

    collapse: function()
    {
        // This is the root, do nothing.
    },

    reveal: function()
    {
        // This is the root, do nothing.
    },

    dataGridNodeFromNode: function(target)
    {
        var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
        return rowElement._dataGridNode;
    },

    dataGridNodeFromPoint: function(x, y)
    {
        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
        var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
        return rowElement._dataGridNode;
    },

    _clickInHeaderCell: function(event)
    {
        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
        if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
            return;

        var sortOrder = this.sortOrder;

        if (this._sortColumnCell)
            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");

        if (cell == this._sortColumnCell) {
            if (sortOrder === "ascending")
                sortOrder = "descending";
            else
                sortOrder = "ascending";
        }

        this._sortColumnCell = cell;

        cell.addStyleClass("sort-" + sortOrder);

        this.dispatchEventToListeners("sorting changed");
    },

    markColumnAsSortedBy: function(columnIdentifier, sortOrder)
    {
        if (this._sortColumnCell)
            this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
        this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
        this._sortColumnCell.addStyleClass("sort-" + sortOrder);
    },

    headerTableHeader: function(columnIdentifier)
    {
        return this._headerTableHeaders[columnIdentifier];
    },

    _mouseDownInDataTable: function(event)
    {
        var gridNode = this.dataGridNodeFromNode(event.target);
        if (!gridNode || !gridNode.selectable)
            return;

        if (gridNode.isEventWithinDisclosureTriangle(event))
            return;

        if (event.metaKey) {
            if (gridNode.selected)
                gridNode.deselect();
            else
                gridNode.select();
        } else
            gridNode.select();
    },
    
    _contextMenuInDataTable: function(event)
    {
        var gridNode = this.dataGridNodeFromNode(event.target);
        if (!gridNode || !gridNode.selectable)
            return;
        
        if (gridNode.isEventWithinDisclosureTriangle(event))
            return;
      
        var contextMenu = new WebInspector.ContextMenu();
        
        // FIXME: Use the column names for Editing, instead of just "Edit".
        if (this.dataGrid._editCallback) {
            if (gridNode === this.creationNode)
                contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target));
            else
                contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target));
        }
        if (this.dataGrid._deleteCallback && gridNode !== this.creationNode)
            contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
        
        contextMenu.show(event);
    },

    _clickInDataTable: function(event)
    {
        var gridNode = this.dataGridNodeFromNode(event.target);
        if (!gridNode || !gridNode.hasChildren)
            return;

        if (!gridNode.isEventWithinDisclosureTriangle(event))
            return;

        if (gridNode.expanded) {
            if (event.altKey)
                gridNode.collapseRecursively();
            else
                gridNode.collapse();
        } else {
            if (event.altKey)
                gridNode.expandRecursively();
            else
                gridNode.expand();
        }
    },
    
    _startResizerDragging: function(event)
    {
        this.currentResizer = event.target;
        if (!this.currentResizer.rightNeighboringColumnID)
            return;
        WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
            this._endResizerDragging.bind(this), event, "col-resize");
    },
    
    _resizerDragging: function(event)
    {
        var resizer = this.currentResizer;
        if (!resizer)
            return;
        
        // Constrain the dragpoint to be within the containing div of the
        // datagrid.
        var dragPoint = event.clientX - this.element.totalOffsetLeft;
        // Constrain the dragpoint to be within the space made up by the
        // column directly to the left and the column directly to the right.
        var leftEdgeOfPreviousColumn = 0;
        var firstRowCells = this.headerTableBody.rows[0].cells;
        for (var i = 0; i < resizer.leftNeighboringColumnID; i++)
            leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
            
        var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.leftNeighboringColumnID].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth;

        // Give each column some padding so that they don't disappear.
        var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
        var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;

        dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);

        resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";

        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
        this._headerTableColumnGroup.children[resizer.leftNeighboringColumnID].style.width = percentLeftColumn;
        this._dataTableColumnGroup.children[resizer.leftNeighboringColumnID].style.width = percentLeftColumn;

        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width =  percentRightColumn;
        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn;

        this._positionResizers();
        event.preventDefault();
        this.dispatchEventToListeners("width changed");
    },
    
    _endResizerDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
        this.currentResizer = null;
        this.dispatchEventToListeners("width changed");
    },
    
    ColumnResizePadding: 10,
    
    CenterResizerOverBorderAdjustment: 3,
}

WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.DataGridNode = function(data, hasChildren)
{
    this._expanded = false;
    this._selected = false;
    this._shouldRefreshChildren = true;
    this._data = data || {};
    this.hasChildren = hasChildren || false;
    this.children = [];
    this.dataGrid = null;
    this.parent = null;
    this.previousSibling = null;
    this.nextSibling = null;
    this.disclosureToggleWidth = 10;
}

WebInspector.DataGridNode.prototype = {
    selectable: true,

    get element()
    {
        if (this._element)
            return this._element;

        if (!this.dataGrid)
            return null;

        this._element = document.createElement("tr");
        this._element._dataGridNode = this;

        if (this.hasChildren)
            this._element.addStyleClass("parent");
        if (this.expanded)
            this._element.addStyleClass("expanded");
        if (this.selected)
            this._element.addStyleClass("selected");
        if (this.revealed)
            this._element.addStyleClass("revealed");

        this.createCells();
        return this._element;
    },

    createCells: function()
    {
        for (var columnIdentifier in this.dataGrid.columns) {
            var cell = this.createCell(columnIdentifier);
            this._element.appendChild(cell);
        }
    },

    get data()
    {
        return this._data;
    },

    set data(x)
    {
        this._data = x || {};
        this.refresh();
    },

    get revealed()
    {
        if ("_revealed" in this)
            return this._revealed;

        var currentAncestor = this.parent;
        while (currentAncestor && !currentAncestor.root) {
            if (!currentAncestor.expanded) {
                this._revealed = false;
                return false;
            }

            currentAncestor = currentAncestor.parent;
        }

        this._revealed = true;
        return true;
    },

    set hasChildren(x)
    {
        if (this._hasChildren === x)
            return;

        this._hasChildren = x;

        if (!this._element)
            return;

        if (this._hasChildren)
        {
            this._element.addStyleClass("parent");
            if (this.expanded)
                this._element.addStyleClass("expanded");
        }
        else
        {
            this._element.removeStyleClass("parent");
            this._element.removeStyleClass("expanded");
        }
    },

    get hasChildren()
    {
        return this._hasChildren;
    },

    set revealed(x)
    {
        if (this._revealed === x)
            return;

        this._revealed = x;

        if (this._element) {
            if (this._revealed)
                this._element.addStyleClass("revealed");
            else
                this._element.removeStyleClass("revealed");
        }

        for (var i = 0; i < this.children.length; ++i)
            this.children[i].revealed = x && this.expanded;
    },

    get depth()
    {
        if ("_depth" in this)
            return this._depth;
        if (this.parent && !this.parent.root)
            this._depth = this.parent.depth + 1;
        else
            this._depth = 0;
        return this._depth;
    },

    get shouldRefreshChildren()
    {
        return this._shouldRefreshChildren;
    },

    set shouldRefreshChildren(x)
    {
        this._shouldRefreshChildren = x;
        if (x && this.expanded)
            this.expand();
    },

    get selected()
    {
        return this._selected;
    },

    set selected(x)
    {
        if (x)
            this.select();
        else
            this.deselect();
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    refresh: function()
    {
        if (!this._element || !this.dataGrid)
            return;

        this._element.removeChildren();
        this.createCells();
    },

    createCell: function(columnIdentifier)
    {
        var cell = document.createElement("td");
        cell.className = columnIdentifier + "-column";

        var alignment = this.dataGrid.aligned[columnIdentifier];
        if (alignment)
            cell.addStyleClass(alignment);

        var div = document.createElement("div");
        div.textContent = this.data[columnIdentifier];
        cell.appendChild(div);

        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
            cell.addStyleClass("disclosure");
            if (this.depth)
                cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
        }

        return cell;
    },

    // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
    appendChild: WebInspector.DataGrid.prototype.appendChild,
    insertChild: WebInspector.DataGrid.prototype.insertChild,
    removeChild: WebInspector.DataGrid.prototype.removeChild,
    removeChildren: WebInspector.DataGrid.prototype.removeChildren,
    removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,

    _recalculateSiblings: function(myIndex)
    {
        if (!this.parent)
            return;

        var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);

        if (previousChild) {
            previousChild.nextSibling = this;
            this.previousSibling = previousChild;
        } else
            this.previousSibling = null;

        var nextChild = this.parent.children[myIndex + 1];

        if (nextChild) {
            nextChild.previousSibling = this;
            this.nextSibling = nextChild;
        } else
            this.nextSibling = null;
    },

    collapse: function()
    {
        if (this._element)
            this._element.removeStyleClass("expanded");

        this._expanded = false;

        for (var i = 0; i < this.children.length; ++i)
            this.children[i].revealed = false;

        this.dispatchEventToListeners("collapsed");
    },

    collapseRecursively: function()
    {
        var item = this;
        while (item) {
            if (item.expanded)
                item.collapse();
            item = item.traverseNextNode(false, this, true);
        }
    },

    expand: function()
    {
        if (!this.hasChildren || this.expanded)
            return;

        if (this.revealed && !this._shouldRefreshChildren)
            for (var i = 0; i < this.children.length; ++i)
                this.children[i].revealed = true;

        if (this._shouldRefreshChildren) {
            for (var i = 0; i < this.children.length; ++i)
                this.children[i]._detach();

            this.dispatchEventToListeners("populate");

            if (this._attached) {
                for (var i = 0; i < this.children.length; ++i) {
                    var child = this.children[i];
                    if (this.revealed)
                        child.revealed = true;
                    child._attach();
                }
            }

            delete this._shouldRefreshChildren;
        }

        if (this._element)
            this._element.addStyleClass("expanded");

        this._expanded = true;

        this.dispatchEventToListeners("expanded");
    },

    expandRecursively: function()
    {
        var item = this;
        while (item) {
            item.expand();
            item = item.traverseNextNode(false, this);
        }
    },

    reveal: function()
    {
        var currentAncestor = this.parent;
        while (currentAncestor && !currentAncestor.root) {
            if (!currentAncestor.expanded)
                currentAncestor.expand();
            currentAncestor = currentAncestor.parent;
        }

        this.element.scrollIntoViewIfNeeded(false);

        this.dispatchEventToListeners("revealed");
    },

    select: function(supressSelectedEvent)
    {
        if (!this.dataGrid || !this.selectable || this.selected)
            return;

        if (this.dataGrid.selectedNode)
            this.dataGrid.selectedNode.deselect();

        this._selected = true;
        this.dataGrid.selectedNode = this;

        if (this._element)
            this._element.addStyleClass("selected");

        if (!supressSelectedEvent)
            this.dispatchEventToListeners("selected");
    },

    deselect: function(supressDeselectedEvent)
    {
        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
            return;

        this._selected = false;
        this.dataGrid.selectedNode = null;

        if (this._element)
            this._element.removeStyleClass("selected");

        if (!supressDeselectedEvent)
            this.dispatchEventToListeners("deselected");
    },

    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
    {
        if (!dontPopulate && this.hasChildren)
            this.dispatchEventToListeners("populate");

        if (info)
            info.depthChange = 0;

        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
        if (node && (!skipHidden || this.expanded)) {
            if (info)
                info.depthChange = 1;
            return node;
        }

        if (this === stayWithin)
            return null;

        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
        if (node)
            return node;

        node = this;
        while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
            if (info)
                info.depthChange -= 1;
            node = node.parent;
        }

        if (!node)
            return null;

        return (!skipHidden || node.revealed) ? node.nextSibling : null;
    },

    traversePreviousNode: function(skipHidden, dontPopulate)
    {
        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
        if (!dontPopulate && node && node.hasChildren)
            node.dispatchEventToListeners("populate");

        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
            if (!dontPopulate && node.hasChildren)
                node.dispatchEventToListeners("populate");
            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
        }

        if (node)
            return node;

        if (!this.parent || this.parent.root)
            return null;

        return this.parent;
    },

    isEventWithinDisclosureTriangle: function(event)
    {
        if (!this.hasChildren)
            return false;
        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell.hasStyleClass("disclosure"))
            return false;
        var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
        var left = cell.totalOffsetLeft + computedLeftPadding;
        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
    },

    _attach: function()
    {
        if (!this.dataGrid || this._attached)
            return;

        this._attached = true;

        var nextNode = null;
        var previousNode = this.traversePreviousNode(true, true);
        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
            var nextNode = previousNode.element.nextSibling;
        if (!nextNode)
            nextNode = this.dataGrid.dataTableBody.lastChild;
        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);

        if (this.expanded)
            for (var i = 0; i < this.children.length; ++i)
                this.children[i]._attach();
    },

    _detach: function()
    {
        if (!this._attached)
            return;

        this._attached = false;

        if (this._element && this._element.parentNode)
            this._element.parentNode.removeChild(this._element);

        for (var i = 0; i < this.children.length; ++i)
            this.children[i]._detach();
    },

    savePosition: function()
    {
        if (this._savedPosition)
            return;

        if (!this.parent)
            throw("savePosition: Node must have a parent.");
        this._savedPosition = {
            parent: this.parent,
            index: this.parent.children.indexOf(this)
        };
    },

    restorePosition: function()
    {
        if (!this._savedPosition)
            return;

        if (this.parent !== this._savedPosition.parent)
            this._savedPosition.parent.insertChild(this, this._savedPosition.index);

        delete this._savedPosition;
    }
}

WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.CreationDataGridNode = function(data, hasChildren)
{
    WebInspector.DataGridNode.call(this, data, hasChildren);
    this.isCreationNode = true;
}

WebInspector.CreationDataGridNode.prototype = {
    makeNormal: function()
    {
        delete this.isCreationNode;
        delete this.makeNormal;
    }
}

WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

/* CookieItemsView.js */

/*
 * Copyright (C) 2009 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.CookieItemsView = function(treeElement, cookieDomain)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
    this.deleteButton.visible = false;
    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
    
    this._treeElement = treeElement;
    this._cookieDomain = cookieDomain;

    this._emptyMsgElement = document.createElement("div");
    this._emptyMsgElement.className = "storage-table-empty";
    this._emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies.");
    this.element.appendChild(this._emptyMsgElement);
}

WebInspector.CookieItemsView.prototype = {
    get statusBarItems()
    {
        return [this.refreshButton.element, this.deleteButton.element];
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this._update();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this.deleteButton.visible = false;
    },

    _update: function()
    {
        WebInspector.Cookies.getCookiesAsync(this._updateWithCookies.bind(this));
    },

    _updateWithCookies: function(allCookies, isAdvanced)
    {
        if (isAdvanced)
            this._filterCookiesForDomain(allCookies);
        else
            this._cookies = allCookies;

        if (!this._cookies.length) {
            // Nothing to show.
            this._emptyMsgElement.removeStyleClass("hidden");
            this.deleteButton.visible = false;
            if (this._dataGrid)
                this._dataGrid.element.addStyleClass("hidden");
            return;
        }

        if (!this._dataGrid) {
            if (isAdvanced) {
                this._createDataGrid();
                this._populateDataGrid();
                this._dataGrid.autoSizeColumns(6, 33);
                this._treeElement.subtitle = String.sprintf(WebInspector.UIString("%d cookies (%s)"), this._cookies.length,
                    Number.bytesToString(this._totalSize, WebInspector.UIString));
            } else {
                this._createSimpleDataGrid();
                this._populateSimpleDataGrid();
                this._dataGrid.autoSizeColumns(20, 80);
            }
        } else {
            if (isAdvanced)
                this._populateDataGrid();
            else
                this._populateSimpleDataGrid();
        }

        this._dataGrid.element.removeStyleClass("hidden");
        this._emptyMsgElement.addStyleClass("hidden");
        if (isAdvanced)
            this.deleteButton.visible = true;
    },

    _filterCookiesForDomain: function(allCookies)
    {
        this._cookies = [];
        var resourceURLsForDocumentURL = [];
        this._totalSize = 0;

        for (var id in WebInspector.resources) {
            var resource = WebInspector.resources[id];
            var match = resource.documentURL.match(WebInspector.GenericURLRegExp);
            if (match && match[2] === this._cookieDomain)
                resourceURLsForDocumentURL.push(resource.url);
        }

        for (var i = 0; i < allCookies.length; ++i) {
            var pushed = false;
            var size = allCookies[i].size;
            for (var j = 0; j < resourceURLsForDocumentURL.length; ++j) {
                var resourceURL = resourceURLsForDocumentURL[j];
                if (WebInspector.Cookies.cookieMatchesResourceURL(allCookies[i], resourceURL)) {
                    this._totalSize += size;
                    if (!pushed) {
                        pushed = true;
                        this._cookies.push(allCookies[i]);
                    }
                }
            }
        }
    },

    _createDataGrid: function()
    {
        var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} };
        columns[0].title = WebInspector.UIString("Name");
        columns[0].sortable = true;
        columns[1].title = WebInspector.UIString("Value");
        columns[1].sortable = true;
        columns[2].title = WebInspector.UIString("Domain");
        columns[2].sortable = true;
        columns[3].title = WebInspector.UIString("Path");
        columns[3].sortable = true;
        columns[4].title = WebInspector.UIString("Expires");
        columns[4].sortable = true;
        columns[5].title = WebInspector.UIString("Size");
        columns[5].aligned = "right";
        columns[5].sortable = true;
        columns[6].title = WebInspector.UIString("HTTP");
        columns[6].aligned = "centered";
        columns[6].sortable = true;
        columns[7].title = WebInspector.UIString("Secure");
        columns[7].aligned = "centered";
        columns[7].sortable = true;

        this._dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCookieCallback.bind(this));
        this._dataGrid.addEventListener("sorting changed", this._populateDataGrid, this);
        this.element.appendChild(this._dataGrid.element);
        this._dataGrid.updateWidths();
    },

    _populateDataGrid: function()
    {
        var selectedCookie = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.cookie : null;
        var sortDirection = this._dataGrid.sortOrder === "ascending" ? 1 : -1;

        function localeCompare(field, cookie1, cookie2)
        {
            return sortDirection * (cookie1[field] + "").localeCompare(cookie2[field] + "")
        }

        function numberCompare(field, cookie1, cookie2)
        {
            return sortDirection * (cookie1[field] - cookie2[field]);
        }

        function expiresCompare(cookie1, cookie2)
        {
            if (cookie1.session !== cookie2.session)
                return sortDirection * (cookie1.session ? 1 : -1);

            if (cookie1.session)
                return 0;

            return sortDirection * (cookie1.expires - cookie2.expires);
        }

        var comparator;
        switch (parseInt(this._dataGrid.sortColumnIdentifier)) {
            case 0: comparator = localeCompare.bind(this, "name"); break;
            case 1: comparator = localeCompare.bind(this, "value"); break;
            case 2: comparator = localeCompare.bind(this, "domain"); break;
            case 3: comparator = localeCompare.bind(this, "path"); break;
            case 4: comparator = expiresCompare; break;
            case 5: comparator = numberCompare.bind(this, "size"); break;
            case 6: comparator = localeCompare.bind(this, "httpOnly"); break;
            case 7: comparator = localeCompare.bind(this, "secure"); break;
            default: localeCompare.bind(this, "name");
        }

        this._cookies.sort(comparator);

        this._dataGrid.removeChildren();
        var nodeToSelect;
        for (var i = 0; i < this._cookies.length; ++i) {
            var data = {};
            var cookie = this._cookies[i];
            data[0] = cookie.name;
            data[1] = cookie.value;
            data[2] = cookie.domain;
            data[3] = cookie.path;
            data[4] = (cookie.session ? WebInspector.UIString("Session") : new Date(cookie.expires).toGMTString());
            data[5] = Number.bytesToString(cookie.size, WebInspector.UIString);
            data[6] = (cookie.httpOnly ? "\u2713" : ""); // Checkmark
            data[7] = (cookie.secure ? "\u2713" : ""); // Checkmark

            var node = new WebInspector.DataGridNode(data);
            node.cookie = cookie;
            node.selectable = true;
            this._dataGrid.appendChild(node);
            if (cookie === selectedCookie)
                nodeToSelect = node;
        }
        if (nodeToSelect)
            nodeToSelect.selected = true;
        else
            this._dataGrid.children[0].selected = true;
    },

    _createSimpleDataGrid: function()
    {
        var columns = {};
        columns[0] = {};
        columns[1] = {};
        columns[0].title = WebInspector.UIString("Name");
        columns[1].title = WebInspector.UIString("Value");

        this._dataGrid = new WebInspector.DataGrid(columns);
        this.element.appendChild(this._dataGrid.element);
        this._dataGrid.updateWidths();
    },

    _populateSimpleDataGrid: function()
    {
        var cookies = this._cookies;
        this._dataGrid.removeChildren();
        var addedCookies = {};
        for (var i = 0; i < cookies.length; ++i) {
            if (addedCookies[cookies[i].name])
                continue;
            addedCookies[cookies[i].name] = true;
            var data = {};
            data[0] = cookies[i].name;
            data[1] = cookies[i].value;

            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = true;
            this._dataGrid.appendChild(node);
        }
        this._dataGrid.children[0].selected = true;
    },

    resize: function()
    {
        if (this._dataGrid)
            this._dataGrid.updateWidths();
    },

    _deleteButtonClicked: function(event)
    {
        if (!this._dataGrid || !this._dataGrid.selectedNode)
            return;

        this._deleteCookieCallback(this._dataGrid.selectedNode);
    },

    _deleteCookieCallback: function(node)
    {
        var cookie = node.cookie;
        InspectorBackend.deleteCookie(cookie.name, this._cookieDomain);
        this._update();
    },

    _refreshButtonClicked: function(event)
    {
        this._update();
    }
}

WebInspector.CookieItemsView.prototype.__proto__ = WebInspector.View.prototype;

/* ApplicationCacheItemsView.js */

/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ApplicationCacheItemsView = function(treeElement, appcacheDomain)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    // FIXME: Delete Button semantics are not yet defined.
    // FIXME: Needs better tooltip. (Localized)
    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
    this.deleteButton.visible = false;
    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

    // FIXME: Refresh Button semantics are not yet defined.
    // FIXME: Needs better tooltip. (Localized)
    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);

    if (Preferences.onlineDetectionEnabled) {
        this.connectivityIcon = document.createElement("img");
        this.connectivityIcon.className = "storage-application-cache-connectivity-icon";
        this.connectivityIcon.src = "";
        this.connectivityMessage = document.createElement("span");
        this.connectivityMessage.className = "storage-application-cache-connectivity";
        this.connectivityMessage.textContent = "";
    }

    this.divider = document.createElement("span");
    this.divider.className = "status-bar-item status-bar-divider";

    this.statusIcon = document.createElement("img");
    this.statusIcon.className = "storage-application-cache-status-icon";
    this.statusIcon.src = "";
    this.statusMessage = document.createElement("span");
    this.statusMessage.className = "storage-application-cache-status";
    this.statusMessage.textContent = "";

    this._treeElement = treeElement;
    this._appcacheDomain = appcacheDomain;

    this._emptyMsgElement = document.createElement("div");
    this._emptyMsgElement.className = "storage-table-empty";
    this._emptyMsgElement.textContent = WebInspector.UIString("No Application Cache information available.");
    this.element.appendChild(this._emptyMsgElement);

    this.updateStatus(applicationCache.UNCACHED);
}

WebInspector.ApplicationCacheItemsView.prototype = {
    get statusBarItems()
    {
        if (Preferences.onlineDetectionEnabled) {
            return [
                this.refreshButton.element, this.deleteButton.element,
                this.connectivityIcon, this.connectivityMessage, this.divider,
                this.statusIcon, this.statusMessage
            ];
        } else {
            return [
                this.refreshButton.element, this.deleteButton.element, this.divider,
                this.statusIcon, this.statusMessage
            ];
        }
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.updateNetworkState(navigator.onLine);
        this._update();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this.deleteButton.visible = false;
    },

    updateStatus: function(status)
    {
        var statusInformation = {};
        statusInformation[applicationCache.UNCACHED]    = { src: "Images/warningOrangeDot.png", text: "UNCACHED"    };
        statusInformation[applicationCache.IDLE]        = { src: "Images/warningOrangeDot.png", text: "IDLE"        };
        statusInformation[applicationCache.CHECKING]    = { src: "Images/successGreenDot.png",  text: "CHECKING"    };
        statusInformation[applicationCache.DOWNLOADING] = { src: "Images/successGreenDot.png",  text: "DOWNLOADING" };
        statusInformation[applicationCache.UPDATEREADY] = { src: "Images/successGreenDot.png",  text: "UPDATEREADY" };
        statusInformation[applicationCache.OBSOLETE]    = { src: "Images/errorRedDot.png",      text: "OBSOLETE"    };

        var info = statusInformation[status];
        if (!info) {
            console.error("Unknown Application Cache Status was Not Handled: %d", status);
            return;
        }

        this.statusIcon.src = info.src;
        this.statusMessage.textContent = info.text;
    },

    updateNetworkState: function(isNowOnline)
    {
        if (Preferences.onlineDetectionEnabled) {
            if (isNowOnline) {
                this.connectivityIcon.src = "Images/successGreenDot.png";
                this.connectivityMessage.textContent = WebInspector.UIString("Online");
            } else {
                this.connectivityIcon.src = "Images/errorRedDot.png";
                this.connectivityMessage.textContent = WebInspector.UIString("Offline");
            }
        }
    },

    _update: function()
    {
        WebInspector.ApplicationCache.getApplicationCachesAsync(this._updateCallback.bind(this));
    },

    _updateCallback: function(applicationCaches)
    {
        // FIXME: applicationCaches is just one cache.
        // FIXME: are these variables needed anywhere else?
        this._manifest = applicationCaches.manifest;
        this._creationTime = applicationCaches.creationTime;
        this._updateTime = applicationCaches.updateTime;
        this._size = applicationCaches.size;
        this._resources = applicationCaches.resources;
        var lastPathComponent = applicationCaches.lastPathComponent;

        if (!this._manifest) {
            this._emptyMsgElement.removeStyleClass("hidden");
            this.deleteButton.visible = false;
            if (this._dataGrid)
                this._dataGrid.element.addStyleClass("hidden");
            return;
        }

        if (!this._dataGrid)
            this._createDataGrid();

        this._populateDataGrid();
        this._dataGrid.autoSizeColumns(20, 80);
        this._dataGrid.element.removeStyleClass("hidden");
        this._emptyMsgElement.addStyleClass("hidden");
        this.deleteButton.visible = true;

        var totalSizeString = Number.bytesToString(this._size, WebInspector.UIString);
        this._treeElement.subtitle = WebInspector.UIString("%s (%s)", lastPathComponent, totalSizeString);

        // FIXME: For Chrome, put creationTime and updateTime somewhere.
        // NOTE: localizedString has not yet been added.
        // WebInspector.UIString("(%s) Created: %s Updated: %s", this._size, this._creationTime, this._updateTime);
    },

    _createDataGrid: function()
    {
        var columns = { 0: {}, 1: {}, 2: {} };
        columns[0].title = WebInspector.UIString("Resource");
        columns[0].sort = "ascending";
        columns[0].sortable = true;
        columns[1].title = WebInspector.UIString("Type");
        columns[1].sortable = true;
        columns[2].title = WebInspector.UIString("Size");
        columns[2].aligned = "right";
        columns[2].sortable = true;
        this._dataGrid = new WebInspector.DataGrid(columns);
        this.element.appendChild(this._dataGrid.element);
        this._dataGrid.addEventListener("sorting changed", this._populateDataGrid, this);
        this._dataGrid.updateWidths();
    },

    _populateDataGrid: function()
    {
        var selectedResource = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.resource : null;
        var sortDirection = this._dataGrid.sortOrder === "ascending" ? 1 : -1;

        function numberCompare(field, resource1, resource2)
        {
            return sortDirection * (resource1[field] - resource2[field]);
        }
        function localeCompare(field, resource1, resource2)
        {
             return sortDirection * (resource1[field] + "").localeCompare(resource2[field] + "")
        }

        var comparator;
        switch (parseInt(this._dataGrid.sortColumnIdentifier)) {
            case 0: comparator = localeCompare.bind(this, "name"); break;
            case 1: comparator = localeCompare.bind(this, "type"); break;
            case 2: comparator = numberCompare.bind(this, "size"); break;
            default: localeCompare.bind(this, "resource"); // FIXME: comparator = ?
        }

        this._resources.sort(comparator);
        this._dataGrid.removeChildren();

        var nodeToSelect;
        for (var i = 0; i < this._resources.length; ++i) {
            var data = {};
            var resource = this._resources[i];
            data[0] = resource.name;
            data[1] = resource.type;
            data[2] = Number.bytesToString(resource.size, WebInspector.UIString);
            var node = new WebInspector.DataGridNode(data);
            node.resource = resource;
            node.selectable = true;
            this._dataGrid.appendChild(node);
            if (resource === selectedResource) {
                nodeToSelect = node;
                nodeToSelect.selected = true;
            }
        }

        if (!nodeToSelect)
            this._dataGrid.children[0].selected = true;
    },

    resize: function()
    {
        if (this._dataGrid)
            this._dataGrid.updateWidths();
    },

    _deleteButtonClicked: function(event)
    {
        if (!this._dataGrid || !this._dataGrid.selectedNode)
            return;

        // FIXME: Delete Button semantics are not yet defined. (Delete a single, or all?)
        this._deleteCallback(this._dataGrid.selectedNode);
    },

    _deleteCallback: function(node)
    {
        // FIXME: Should we delete a single (selected) resource or all resources?
        // InspectorBackend.deleteCachedResource(...)
        // this._update();
    },

    _refreshButtonClicked: function(event)
    {
        // FIXME: Is this a refresh button or a re-fetch manifest button?
        // this._update();
    }
}

WebInspector.ApplicationCacheItemsView.prototype.__proto__ = WebInspector.View.prototype;

/* Script.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Script = function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage, worldType)
{
    this.sourceID = sourceID;
    this.sourceURL = sourceURL;
    this.source = source;
    this.startingLine = startingLine;
    this.errorLine = errorLine;
    this.errorMessage = errorMessage;
    this.worldType = worldType;

    // if no URL, look for "//@ sourceURL=" decorator
    // note that this sourceURL comment decorator is behavior that FireBug added
    // in it's 1.1 release as noted in the release notes:
    // http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt
    if (!sourceURL) {
        // use of [ \t] rather than \s is to prevent \n from matching
        var pattern = /^\s*\/\/[ \t]*@[ \t]*sourceURL[ \t]*=[ \t]*(\S+).*$/m;
        var match = pattern.exec(source);

        if (match)
            this.sourceURL = match[1];
    }
}

WebInspector.Script.WorldType = {
    MAIN_WORLD: 0,
    EXTENSIONS_WORLD: 1
}

WebInspector.Script.WorldType = {
    MAIN_WORLD: 0,
    EXTENSIONS_WORLD: 1
}

WebInspector.Script.prototype = {
    get linesCount()
    {
        if (!this.source)
            return 0;
        if (this._linesCount)
            return this._linesCount;
        this._linesCount = 0;
        var lastIndex = this.source.indexOf("\n");
        while (lastIndex !== -1) {
            lastIndex = this.source.indexOf("\n", lastIndex + 1)
            this._linesCount++;
        }
        return this._linesCount;
    }
}

/* BreakpointManager.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.BreakpointManager = function()
{
    this._breakpoints = {};
    this._xhrBreakpoints = {};
}

WebInspector.BreakpointManager.DebuggerEventType = {
    JavaScriptPause: 0,
    DOMBreakpoint: 1,
    NativeBreakpoint: 2
};

WebInspector.BreakpointManager.prototype = {
    setOneTimeBreakpoint: function(sourceID, line)
    {
        var breakpoint = new WebInspector.Breakpoint(this, sourceID, undefined, line, true, undefined);
        if (this._breakpoints[breakpoint.id])
            return;
        if (this._oneTimeBreakpoint)
            InspectorBackend.removeBreakpoint(this._oneTimeBreakpoint.sourceID, this._oneTimeBreakpoint.line);
        this._oneTimeBreakpoint = breakpoint;
        // FIXME(40669): one time breakpoint will be persisted in inspector settings if not hit.
        this._setBreakpointOnBackend(breakpoint, true);
    },

    removeOneTimeBreakpoint: function()
    {
        if (this._oneTimeBreakpoint) {
            InspectorBackend.removeBreakpoint(this._oneTimeBreakpoint.sourceID, this._oneTimeBreakpoint.line);
            delete this._oneTimeBreakpoint;
        }
    },

    setBreakpoint: function(sourceID, url, line, enabled, condition)
    {
        var breakpoint = this._setBreakpoint(sourceID, url, line, enabled, condition);
        if (breakpoint)
            this._setBreakpointOnBackend(breakpoint);
    },

    restoredBreakpoint: function(sourceID, url, line, enabled, condition)
    {
        this._setBreakpoint(sourceID, url, line, enabled, condition);
    },

    breakpointsForSourceID: function(sourceID)
    {
        var breakpoints = [];
        for (var id in this._breakpoints) {
            if (this._breakpoints[id].sourceID === sourceID)
                breakpoints.push(this._breakpoints[id]);
        }
        return breakpoints;
    },

    breakpointsForURL: function(url)
    {
        var breakpoints = [];
        for (var id in this._breakpoints) {
            if (this._breakpoints[id].url === url)
                breakpoints.push(this._breakpoints[id]);
        }
        return breakpoints;
    },

    reset: function()
    {
        this._breakpoints = {};
        delete this._oneTimeBreakpoint;
        this._xhrBreakpoints = {};
    },

    _setBreakpoint: function(sourceID, url, line, enabled, condition)
    {
        var breakpoint = new WebInspector.Breakpoint(this, sourceID, url, line, enabled, condition);
        if (this._breakpoints[breakpoint.id])
            return;
        if (this._oneTimeBreakpoint && (this._oneTimeBreakpoint.id == breakpoint.id))
            delete this._oneTimeBreakpoint;
        this._breakpoints[breakpoint.id] = breakpoint;
        breakpoint.addEventListener("removed", this._breakpointRemoved, this);
        this.dispatchEventToListeners("breakpoint-added", breakpoint);
        return breakpoint;
    },

    _breakpointRemoved: function(event)
    {
        delete this._breakpoints[event.target.id];
    },

    _setBreakpointOnBackend: function(breakpoint, isOneTime)
    {
        function didSetBreakpoint(success, line)
        {
            if (success && line == breakpoint.line)
                return;
            if (isOneTime) {
                if (success)
                    this._oneTimeBreakpoint.line = line;
                else
                    delete this._oneTimeBreakpoint;
            } else {
                breakpoint.remove();
                if (success)
                    this._setBreakpoint(breakpoint.sourceID, breakpoint.url, line, breakpoint.enabled, breakpoint.condition);
            }
        }
        InspectorBackend.setBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.enabled, breakpoint.condition, didSetBreakpoint.bind(this));
    },

    createEventListenerBreakpoint: function(eventName)
    {
        var data = { type: "EventListener", condition: { eventName: eventName } };
        var breakpoint = new WebInspector.NativeBreakpoint(data, this);
        breakpoint.label = eventName;
        return breakpoint;
    },

    createXHRBreakpoint: function(url)
    {
        if (url in this._xhrBreakpoints)
            return;
        this._xhrBreakpoints[url] = true;

        var data = { type: "XHR", condition: { url: url } };
        var breakpoint = new WebInspector.NativeBreakpoint(data, this);
        if (!url.length)
            breakpoint.label = WebInspector.UIString("Any XHR");
        else
            breakpoint.label = WebInspector.UIString("URL contains \"%s\"", url);
        breakpoint.compareTo = function(other)
        {
            var urlOther = other._data.condition.url;
            if (url !== urlOther)
                return url < urlOther ? -1 : 1;
            return 0;
        };

        breakpoint.addEventListener("removed", this._xhrBreakpointRemoved.bind(this, url));
        this.dispatchEventToListeners("xhr-breakpoint-added", breakpoint);

        breakpoint.enabled = true;
    },

    _xhrBreakpointRemoved: function(url)
    {
        delete this._xhrBreakpoints[url];
    },

    _removeNativeBreakpoint: function(breakpoint)
    {
        if (breakpoint._beingSetOnBackend)
            return;
        if (breakpoint.enabled)
            this._removeNativeBreakpointFromBackend(breakpoint);
        breakpoint.dispatchEventToListeners("removed");
    },

    _setNativeBreakpointEnabled: function(breakpoint, enabled)
    {
        if (breakpoint._beingSetOnBackend)
            return;
        if (breakpoint.enabled === enabled)
            return;
        if (enabled)
            this._setNativeBreakpointOnBackend(breakpoint);
        else
            this._removeNativeBreakpointFromBackend(breakpoint);
    },

    _setNativeBreakpointOnBackend: function(breakpoint)
    {
        breakpoint._beingSetOnBackend = true;
        InspectorBackend.setNativeBreakpoint(breakpoint._data, didSetNativeBreakpoint.bind(this));

        function didSetNativeBreakpoint(backendBreakpointId)
        {
            breakpoint._beingSetOnBackend = false;
            if (backendBreakpointId !== 0) {
                breakpoint._backendId = backendBreakpointId;
                this._breakpoints[backendBreakpointId] = breakpoint;
            }
            breakpoint.dispatchEventToListeners("enable-changed");
        }
    },

    _removeNativeBreakpointFromBackend: function(breakpoint)
    {
        InspectorBackend.removeNativeBreakpoint(breakpoint._backendId);
        delete this._breakpoints[breakpoint._backendId]
        delete breakpoint._backendId;
        breakpoint.dispatchEventToListeners("enable-changed");
    },

    debuggerPaused: function(details)
    {
        if (details.eventType !== WebInspector.BreakpointManager.DebuggerEventType.NativeBreakpoint)
            return;
        var breakpoint = this._breakpoints[details.eventData.breakpointId];
        if (breakpoint) {
            breakpoint.hit = true;
            breakpoint.dispatchEventToListeners("hit-state-changed");
            this._lastHitBreakpoint = breakpoint;
        }
    },

    debuggerResumed: function()
    {
        if (!this._lastHitBreakpoint)
            return;
        this._lastHitBreakpoint.hit = false;
        this._lastHitBreakpoint.dispatchEventToListeners("hit-state-changed");
        delete this._lastHitBreakpoint;
    }
}

WebInspector.BreakpointManager.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.Breakpoint = function(breakpointManager, sourceID, url, line, enabled, condition)
{
    this.url = url;
    this.line = line;
    this.sourceID = sourceID;
    this._enabled = enabled;
    this._condition = condition || "";
    this._sourceText = "";
    this._breakpointManager = breakpointManager;
}

WebInspector.Breakpoint.prototype = {
    get enabled()
    {
        return this._enabled;
    },

    set enabled(x)
    {
        if (this._enabled === x)
            return;

        this._enabled = x;
        this._breakpointManager._setBreakpointOnBackend(this);
        this.dispatchEventToListeners("enable-changed");
    },

    get sourceText()
    {
        return this._sourceText;
    },

    set sourceText(text)
    {
        this._sourceText = text;
        this.dispatchEventToListeners("text-changed");
    },

    get id()
    {
        return this.sourceID + ":" + this.line;
    },

    get condition()
    {
        return this._condition;
    },

    set condition(c)
    {
        c = c || "";
        if (this._condition === c)
            return;

        this._condition = c;
        if (this.enabled)
            this._breakpointManager._setBreakpointOnBackend(this);
        this.dispatchEventToListeners("condition-changed");
    },

    compareTo: function(other)
    {
        if (this.url != other.url)
            return this.url < other.url ? -1 : 1;
        if (this.line != other.line)
            return this.line < other.line ? -1 : 1;
        return 0;
    },

    remove: function()
    {
        InspectorBackend.removeBreakpoint(this.sourceID, this.line);
        this.dispatchEventToListeners("removed");
        this.removeAllListeners();
        delete this._breakpointManager;
    }
}

WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.NativeBreakpoint = function(data, manager)
{
    this._data = data;
    this._manager = manager;
}

WebInspector.NativeBreakpoint.prototype = {
    get enabled()
    {
        return "_backendId" in this;
    },

    set enabled(enabled)
    {
        this._manager._setNativeBreakpointEnabled(this, enabled);
    },

    remove: function()
    {
        this._manager._removeNativeBreakpoint(this);
    }
}

WebInspector.NativeBreakpoint.prototype.__proto__ = WebInspector.Object.prototype;

/* SidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SidebarPane = function(title)
{
    this.element = document.createElement("div");
    this.element.className = "pane";

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";
    this.titleElement.tabIndex = 0;
    this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false);
    this.titleElement.addEventListener("keydown", this._onTitleKeyDown.bind(this), false);

    this.bodyElement = document.createElement("div");
    this.bodyElement.className = "body";

    this.element.appendChild(this.titleElement);
    this.element.appendChild(this.bodyElement);

    this.title = title;
    this.growbarVisible = false;
    this.expanded = false;
}

WebInspector.SidebarPane.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.titleElement.textContent = x;
    },

    get growbarVisible()
    {
        return this._growbarVisible;
    },

    set growbarVisible(x)
    {
        if (this._growbarVisible === x)
            return;

        this._growbarVisible = x;

        if (x && !this._growbarElement) {
            this._growbarElement = document.createElement("div");
            this._growbarElement.className = "growbar";
            this.element.appendChild(this._growbarElement);
        } else if (!x && this._growbarElement) {
            if (this._growbarElement.parentNode)
                this._growbarElement.parentNode(this._growbarElement);
            delete this._growbarElement;
        }
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    expand: function()
    {
        if (this._expanded)
            return;
        this._expanded = true;
        this.element.addStyleClass("expanded");
        if (this.onexpand)
            this.onexpand(this);
    },

    collapse: function()
    {
        if (!this._expanded)
            return;
        this._expanded = false;
        this.element.removeStyleClass("expanded");
        if (this.oncollapse)
            this.oncollapse(this);
    },

    toggleExpanded: function()
    {
        this.expanded = !this.expanded;
    },

    _onTitleKeyDown: function(event)
    {
        if (isEnterKey(event) || event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code)
            this.toggleExpanded();
    }
}

WebInspector.SidebarPane.prototype.__proto__ = WebInspector.Object.prototype;

/* ElementsTreeOutline.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ElementsTreeOutline = function() {
    this.element = document.createElement("ol");
    this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
    this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
    this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);

    TreeOutline.call(this, this.element);

    this.includeRootDOMNode = true;
    this.selectEnabled = false;
    this.showInElementsPanelEnabled = false;
    this.rootDOMNode = null;
    this.focusedDOMNode = null;

    this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
}

WebInspector.ElementsTreeOutline.prototype = {
    get rootDOMNode()
    {
        return this._rootDOMNode;
    },

    set rootDOMNode(x)
    {
        if (this._rootDOMNode === x)
            return;

        this._rootDOMNode = x;

        this._isXMLMimeType = !!(WebInspector.mainResource && WebInspector.mainResource.mimeType && WebInspector.mainResource.mimeType.match(/x(?:ht)?ml/i));

        this.update();
    },

    get isXMLMimeType()
    {
        return this._isXMLMimeType;
    },

    nodeNameToCorrectCase: function(nodeName)
    {
        return this.isXMLMimeType ? nodeName : nodeName.toLowerCase();
    },

    get focusedDOMNode()
    {
        return this._focusedDOMNode;
    },

    set focusedDOMNode(x)
    {
        if (this._focusedDOMNode === x) {
            this.revealAndSelectNode(x);
            return;
        }

        this._focusedDOMNode = x;

        this.revealAndSelectNode(x);

        // The revealAndSelectNode() method might find a different element if there is inlined text,
        // and the select() call would change the focusedDOMNode and reenter this setter. So to
        // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same
        // node as the one passed in.
        if (this._focusedDOMNode === x)
            this.focusedNodeChanged();
    },

    get editing()
    {
        return this._editing;
    },

    update: function()
    {
        var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;

        this.removeChildren();

        if (!this.rootDOMNode)
            return;

        var treeElement;
        if (this.includeRootDOMNode) {
            treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
            treeElement.selectable = this.selectEnabled;
            this.appendChild(treeElement);
        } else {
            // FIXME: this could use findTreeElement to reuse a tree element if it already exists
            var node = this.rootDOMNode.firstChild;
            while (node) {
                treeElement = new WebInspector.ElementsTreeElement(node);
                treeElement.selectable = this.selectEnabled;
                this.appendChild(treeElement);
                node = node.nextSibling;
            }
        }

        if (selectedNode)
            this.revealAndSelectNode(selectedNode);
    },

    updateSelection: function()
    {
        if (!this.selectedTreeElement)
            return;
        var element = this.treeOutline.selectedTreeElement;
        element.updateSelection();
    },

    focusedNodeChanged: function(forceUpdate) {},

    findTreeElement: function(node)
    {
        var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
        if (!treeElement && node.nodeType === Node.TEXT_NODE) {
            // The text node might have been inlined if it was short, so try to find the parent element.
            treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
        }

        return treeElement;
    },

    createTreeElementFor: function(node)
    {
        var treeElement = this.findTreeElement(node);
        if (treeElement)
            return treeElement;
        if (!node.parentNode)
            return null;

        var treeElement = this.createTreeElementFor(node.parentNode);
        if (treeElement && treeElement.showChild(node.index))
            return treeElement.children[node.index];

        return null;
    },

    set suppressRevealAndSelect(x)
    {
        if (this._suppressRevealAndSelect === x)
            return;
        this._suppressRevealAndSelect = x;
    },

    revealAndSelectNode: function(node)
    {
        if (!node || this._suppressRevealAndSelect)
            return;

        var treeElement = this.createTreeElementFor(node);
        if (!treeElement)
            return;

        treeElement.reveal();
        treeElement.select();
    },

    _treeElementFromEvent: function(event)
    {
        var root = this.element;

        // We choose this X coordinate based on the knowledge that our list
        // items extend nearly to the right edge of the outer <ol>.
        var x = root.totalOffsetLeft + root.offsetWidth - 20;

        var y = event.pageY;

        // Our list items have 1-pixel cracks between them vertically. We avoid
        // the cracks by checking slightly above and slightly below the mouse
        // and seeing if we hit the same element each time.
        var elementUnderMouse = this.treeElementFromPoint(x, y);
        var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
        var element;
        if (elementUnderMouse === elementAboveMouse)
            element = elementUnderMouse;
        else
            element = this.treeElementFromPoint(x, y + 2);

        return element;
    },

    _onmousedown: function(event)
    {
        var element = this._treeElementFromEvent(event);

        if (!element || element.isEventWithinDisclosureTriangle(event))
            return;

        element.select();
    },

    _onmousemove: function(event)
    {
        var element = this._treeElementFromEvent(event);
        if (element && this._previousHoveredElement === element)
            return;

        if (this._previousHoveredElement) {
            this._previousHoveredElement.hovered = false;
            delete this._previousHoveredElement;
        }

        if (element) {
            element.hovered = true;
            this._previousHoveredElement = element;

            // Lazily compute tag-specific tooltips.
            if (element.representedObject && !element.tooltip)
                element._createTooltipForNode();
        }

        WebInspector.highlightDOMNode(element ? element.representedObject.id : 0);
    },

    _onmouseout: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
            return;

        if (this._previousHoveredElement) {
            this._previousHoveredElement.hovered = false;
            delete this._previousHoveredElement;
        }

        WebInspector.highlightDOMNode(0);
    },

    _contextMenuEventFired: function(event)
    {
        var listItem = event.target.enclosingNodeOrSelfWithNodeName("LI");
        if (!listItem || !listItem.treeElement)
            return;

        var contextMenu = new WebInspector.ContextMenu();

        var href = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link");
        var tag = event.target.enclosingNodeOrSelfWithClass("webkit-html-tag");
        var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
        var needSeparator;
        if (href)
            needSeparator = WebInspector.panels.elements.populateHrefContextMenu(contextMenu, event, href);
        if (tag && listItem.treeElement._populateTagContextMenu) {
            if (needSeparator)
                contextMenu.appendSeparator();
            listItem.treeElement._populateTagContextMenu(contextMenu, event);
        } else if (textNode && listItem.treeElement._populateTextContextMenu) {
            if (needSeparator)
                contextMenu.appendSeparator();
            listItem.treeElement._populateTextContextMenu(contextMenu, textNode);
        }
        contextMenu.show(event);
    }
}

WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;

WebInspector.ElementsTreeElement = function(node, elementCloseTag)
{
    this._elementCloseTag = elementCloseTag;
    var hasChildrenOverride = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node);

    // The title will be updated in onattach.
    TreeElement.call(this, "", node, hasChildrenOverride);

    if (this.representedObject.nodeType == Node.ELEMENT_NODE && !elementCloseTag)
        this._canAddAttributes = true;
    this._searchQuery = null;
    this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit;
}

WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;

// A union of HTML4 and HTML5-Draft elements that explicitly
// or implicitly (for HTML5) forbid the closing tag.
// FIXME: Revise once HTML5 Final is published.
WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
    "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
    "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source"
].keySet();

// These tags we do not allow editing their tag name.
WebInspector.ElementsTreeElement.EditTagBlacklist = [
    "html", "head", "body"
].keySet();

WebInspector.ElementsTreeElement.prototype = {
    highlightSearchResults: function(searchQuery)
    {
        if (this._searchQuery === searchQuery)
            return;

        this._searchQuery = searchQuery;
        this.updateTitle();
    },

    get hovered()
    {
        return this._hovered;
    },

    set hovered(x)
    {
        if (this._hovered === x)
            return;

        this._hovered = x;

        if (this.listItemElement) {
            if (x) {
                this.updateSelection();
                this.listItemElement.addStyleClass("hovered");
            } else {
                this.listItemElement.removeStyleClass("hovered");
            }
        }
    },

    get expandedChildrenLimit()
    {
        return this._expandedChildrenLimit;
    },

    set expandedChildrenLimit(x)
    {
        if (this._expandedChildrenLimit === x)
            return;

        this._expandedChildrenLimit = x;
        if (this.treeOutline && !this._updateChildrenInProgress)
            this._updateChildren(true);
    },

    get expandedChildCount()
    {
        var count = this.children.length;
        if (count && this.children[count - 1]._elementCloseTag)
            count--;
        if (count && this.children[count - 1].expandAllButton)
            count--;
        return count;
    },

    showChild: function(index)
    {
        if (this._elementCloseTag)
            return;

        if (index >= this.expandedChildrenLimit) {
            this._expandedChildrenLimit = index + 1;
            this._updateChildren(true);
        }

        // Whether index-th child is visible in the children tree
        return this.expandedChildCount > index;
    },

    _createTooltipForNode: function()
    {
        var node = this.representedObject;
        if (!node.nodeName || node.nodeName.toLowerCase() !== "img")
            return;

        function setTooltip(properties)
        {
            if (!properties)
                return;

            if (properties.offsetHeight === properties.naturalHeight && properties.offsetWidth === properties.naturalWidth)
                this.tooltip = WebInspector.UIString("%d × %d pixels", properties.offsetWidth, properties.offsetHeight);
            else
                this.tooltip = WebInspector.UIString("%d × %d pixels (Natural: %d × %d pixels)", properties.offsetWidth, properties.offsetHeight, properties.naturalWidth, properties.naturalHeight);
        }

        InjectedScriptAccess.getForNode(node).getNodeProperties(node.id, ["naturalHeight", "naturalWidth", "offsetHeight", "offsetWidth"], setTooltip.bind(this));
    },

    updateSelection: function()
    {
        var listItemElement = this.listItemElement;
        if (!listItemElement)
            return;

        if (document.body.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        if (!this.selectionElement) {
            this.selectionElement = document.createElement("div");
            this.selectionElement.className = "selection selected";
            listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
        }

        this.selectionElement.style.height = listItemElement.offsetHeight + "px";
    },

    onattach: function()
    {
        if (this._hovered) {
            this.updateSelection();
            this.listItemElement.addStyleClass("hovered");
        }

        this.updateTitle();

        this._preventFollowingLinksOnDoubleClick();
    },

    _preventFollowingLinksOnDoubleClick: function()
    {
        var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
        if (!links)
            return;

        for (var i = 0; i < links.length; ++i)
            links[i].preventFollowOnDoubleClick = true;
    },

    onpopulate: function()
    {
        if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag)
            return;

        this.updateChildren();
    },

    updateChildren: function(fullRefresh)
    {
        if (this._elementCloseTag)
            return;

        WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh));
    },

    insertChildElement: function(child, index, closingTag)
    {
        var newElement = new WebInspector.ElementsTreeElement(child, closingTag);
        newElement.selectable = this.treeOutline.selectEnabled;
        this.insertChild(newElement, index);
        return newElement;
    },

    moveChild: function(child, targetIndex)
    {
        var wasSelected = child.selected;
        this.removeChild(child);
        this.insertChild(child, targetIndex);
        if (wasSelected)
            child.select();
    },

    _updateChildren: function(fullRefresh)
    {
        if (this._updateChildrenInProgress)
            return;

        this._updateChildrenInProgress = true;
        var focusedNode = this.treeOutline.focusedDOMNode;
        var originalScrollTop;
        if (fullRefresh) {
            var treeOutlineContainerElement = this.treeOutline.element.parentNode;
            originalScrollTop = treeOutlineContainerElement.scrollTop;
            var selectedTreeElement = this.treeOutline.selectedTreeElement;
            if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
                this.select();
            this.removeChildren();
        }

        var treeElement = this;
        var treeChildIndex = 0;
        var elementToSelect;

        function updateChildrenOfNode(node)
        {
            var treeOutline = treeElement.treeOutline;
            var child = node.firstChild;
            while (child) {
                var currentTreeElement = treeElement.children[treeChildIndex];
                if (!currentTreeElement || currentTreeElement.representedObject !== child) {
                    // Find any existing element that is later in the children list.
                    var existingTreeElement = null;
                    for (var i = (treeChildIndex + 1), size = treeElement.expandedChildCount; i < size; ++i) {
                        if (treeElement.children[i].representedObject === child) {
                            existingTreeElement = treeElement.children[i];
                            break;
                        }
                    }

                    if (existingTreeElement && existingTreeElement.parent === treeElement) {
                        // If an existing element was found and it has the same parent, just move it.
                        treeElement.moveChild(existingTreeElement, treeChildIndex);
                    } else {
                        // No existing element found, insert a new element.
                        if (treeChildIndex < treeElement.expandedChildrenLimit) {
                            var newElement = treeElement.insertChildElement(child, treeChildIndex);
                            if (child === focusedNode)
                                elementToSelect = newElement;
                            if (treeElement.expandedChildCount > treeElement.expandedChildrenLimit)
                                treeElement.expandedChildrenLimit++;
                        }
                    }
                }

                child = child.nextSibling;
                ++treeChildIndex;
            }
        }

        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
        for (var i = (this.children.length - 1); i >= 0; --i) {
            var currentChild = this.children[i];
            var currentNode = currentChild.representedObject;
            var currentParentNode = currentNode.parentNode;

            if (currentParentNode === this.representedObject)
                continue;

            var selectedTreeElement = this.treeOutline.selectedTreeElement;
            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
                this.select();

            this.removeChildAtIndex(i);
        }

        updateChildrenOfNode(this.representedObject);
        this.adjustCollapsedRange(false);

        var lastChild = this.children[this.children.length - 1];
        if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
            this.insertChildElement(this.representedObject, this.children.length, true);

        // We want to restore the original selection and tree scroll position after a full refresh, if possible.
        if (fullRefresh && elementToSelect) {
            elementToSelect.select();
            if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight)
                treeOutlineContainerElement.scrollTop = originalScrollTop;
        }

        delete this._updateChildrenInProgress;
    },

    adjustCollapsedRange: function()
    {
        // Ensure precondition: only the tree elements for node children are found in the tree
        // (not the Expand All button or the closing tag).
        if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent)
            this.removeChild(this.expandAllButtonElement.__treeElement);

        const node = this.representedObject;
        if (!node.children)
            return;
        const childNodeCount = node.children.length;

        // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
        for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i)
            this.insertChildElement(node.children[i], i);

        const expandedChildCount = this.expandedChildCount;
        if (childNodeCount > this.expandedChildCount) {
            var targetButtonIndex = expandedChildCount;
            if (!this.expandAllButtonElement) {
                var title = "<button class=\"show-all-nodes\" value=\"\" />";
                var item = new TreeElement(title, null, false);
                item.selectable = false;
                item.expandAllButton = true;
                this.insertChild(item, targetButtonIndex);
                this.expandAllButtonElement = item.listItemElement.firstChild;
                this.expandAllButtonElement.__treeElement = item;
                this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
            } else if (!this.expandAllButtonElement.__treeElement.parent)
                this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
            this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount);
        } else if (this.expandAllButtonElement)
            delete this.expandAllButtonElement;
    },

    handleLoadAllChildren: function()
    {
        this.expandedChildrenLimit = Math.max(this.representedObject._childNodeCount, this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit);
    },

    onexpand: function()
    {
        if (this._elementCloseTag)
            return;

        this.updateTitle();
        this.treeOutline.updateSelection();
    },

    oncollapse: function()
    {
        if (this._elementCloseTag)
            return;

        this.updateTitle();
        this.treeOutline.updateSelection();
    },

    onreveal: function()
    {
        if (this.listItemElement)
            this.listItemElement.scrollIntoViewIfNeeded(false);
    },

    onselect: function(treeElement, selectedByUser)
    {
        this.treeOutline.suppressRevealAndSelect = true;
        this.treeOutline.focusedDOMNode = this.representedObject;
        if (selectedByUser)
            WebInspector.highlightDOMNode(this.representedObject.id);
        this.updateSelection();
        this.treeOutline.suppressRevealAndSelect = false;
    },

    ondelete: function()
    {
        var startTagTreeElement = this.treeOutline.findTreeElement(this.representedObject);
        startTagTreeElement ? startTagTreeElement.remove() : this.remove();
        return true;
    },

    onenter: function()
    {
        // On Enter or Return start editing the first attribute
        // or create a new attribute on the selected element.
        if (this.treeOutline.editing)
            return false;

        this._startEditing();

        // prevent a newline from being immediately inserted
        return true;
    },

    selectOnMouseDown: function(event)
    {
        TreeElement.prototype.selectOnMouseDown.call(this, event);

        if (this._editing)
            return;

        if (this.treeOutline.showInElementsPanelEnabled) {
            WebInspector.showPanel("elements");
            WebInspector.panels.elements.focusedDOMNode = this.representedObject;
        }

        // Prevent selecting the nearest word on double click.
        if (event.detail >= 2)
            event.preventDefault();
    },

    ondblclick: function(event)
    {
        if (this._editing || this._elementCloseTag)
            return;

        if (this._startEditingTarget(event.target))
            return;

        if (this.hasChildren && !this.expanded)
            this.expand();
    },

    _insertInLastAttributePosition: function(tag, node)
    {
        if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
            tag.insertBefore(node, tag.lastChild);
        else {
            var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
            tag.textContent = '';
            tag.appendChild(document.createTextNode('<'+nodeName));
            tag.appendChild(node);
            tag.appendChild(document.createTextNode('>'));
        }

        this.updateSelection();
    },

    _startEditingTarget: function(eventTarget)
    {
        if (this.treeOutline.focusedDOMNode != this.representedObject)
            return;

        if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
            return false;

        var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node");
        if (textNode)
            return this._startEditingTextNode(textNode);

        var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute");
        if (attribute)
            return this._startEditingAttribute(attribute, eventTarget);

        var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
        if (tagName)
            return this._startEditingTagName(tagName);

        var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute");
        if (newAttribute)
            return this._addNewAttribute();

        return false;
    },

    _populateTagContextMenu: function(contextMenu, event)
    {
        var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
        var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");

        // Add attribute-related actions.
        contextMenu.appendItem(WebInspector.UIString("Add Attribute"), this._addNewAttribute.bind(this));
        if (attribute && !newAttribute)
            contextMenu.appendItem(WebInspector.UIString("Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
        contextMenu.appendSeparator();

        // Add free-form node-related actions.
        contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
        contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
        contextMenu.appendItem(WebInspector.UIString("Delete Node"), this.remove.bind(this));

        if (Preferences.nativeInstrumentationEnabled) {
            // Add debbuging-related actions
            contextMenu.appendSeparator();

            var node = this.representedObject;
            for (var key in WebInspector.DOMBreakpoint.Types) {
                var type = WebInspector.DOMBreakpoint.Types[key];
                var label = WebInspector.DOMBreakpoint.contextMenuLabelForType(type);
                var hasBreakpoint = node.hasBreakpoint(type);
                if (!hasBreakpoint)
                    var handler = node.setBreakpoint.bind(node, type);
                else
                    var handler = node.removeBreakpoint.bind(node, type);
                contextMenu.appendCheckboxItem(label, handler, hasBreakpoint);
            }
        }
    },

    _populateTextContextMenu: function(contextMenu, textNode)
    {
        contextMenu.appendItem(WebInspector.UIString("Edit Text"), this._startEditingTextNode.bind(this, textNode));
    },

    _startEditing: function()
    {
        if (this.treeOutline.focusedDOMNode !== this.representedObject)
            return;

        var listItem = this._listItemNode;

        if (this._canAddAttributes) {
            var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
            if (attribute)
                return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);

            return this._addNewAttribute();
        }

        if (this.representedObject.nodeType === Node.TEXT_NODE) {
            var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
            if (textNode)
                return this._startEditingTextNode(textNode);
            return;
        }
    },

    _addNewAttribute: function()
    {
        // Cannot just convert the textual html into an element without
        // a parent node. Use a temporary span container for the HTML.
        var container = document.createElement("span");
        container.innerHTML = this._attributeHTML(" ", "");
        var attr = container.firstChild;
        attr.style.marginLeft = "2px"; // overrides the .editing margin rule
        attr.style.marginRight = "2px"; // overrides the .editing margin rule

        var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
        this._insertInLastAttributePosition(tag, attr);
        return this._startEditingAttribute(attr, attr);
    },

    _triggerEditAttribute: function(attributeName)
    {
        var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
        for (var i = 0, len = attributeElements.length; i < len; ++i) {
            if (attributeElements[i].textContent === attributeName) {
                for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
                    if (elem.nodeType !== Node.ELEMENT_NODE)
                        continue;

                    if (elem.hasStyleClass("webkit-html-attribute-value"))
                        return this._startEditingAttribute(elem.parentNode, elem);
                }
            }
        }
    },

    _startEditingAttribute: function(attribute, elementForSelection)
    {
        if (WebInspector.isBeingEdited(attribute))
            return true;

        var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
        if (!attributeNameElement)
            return false;

        var attributeName = attributeNameElement.innerText;

        function removeZeroWidthSpaceRecursive(node)
        {
            if (node.nodeType === Node.TEXT_NODE) {
                node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
                return;
            }

            if (node.nodeType !== Node.ELEMENT_NODE)
                return;

            for (var child = node.firstChild; child; child = child.nextSibling)
                removeZeroWidthSpaceRecursive(child);
        }

        // Remove zero-width spaces that were added by nodeTitleInfo.
        removeZeroWidthSpaceRecursive(attribute);

        this._editing = WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
        window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);

        return true;
    },

    _startEditingTextNode: function(textNode)
    {
        if (WebInspector.isBeingEdited(textNode))
            return true;

        this._editing = WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
        window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);

        return true;
    },

    _startEditingTagName: function(tagNameElement)
    {
        if (!tagNameElement) {
            tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0];
            if (!tagNameElement)
                return false;
        }

        var tagName = tagNameElement.textContent;
        if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()])
            return false;

        if (WebInspector.isBeingEdited(tagNameElement))
            return true;

        var closingTagElement = this._distinctClosingTagElement();

        function keyupListener(event)
        {
            if (closingTagElement)
                closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
        }

        function editingComitted(element, newTagName)
        {
            tagNameElement.removeEventListener('keyup', keyupListener, false);
            this._tagNameEditingCommitted.apply(this, arguments);
        }

        function editingCancelled()
        {
            tagNameElement.removeEventListener('keyup', keyupListener, false);
            this._editingCancelled.apply(this, arguments);
        }

        tagNameElement.addEventListener('keyup', keyupListener, false);

        this._editing = WebInspector.startEditing(tagNameElement, editingComitted.bind(this), editingCancelled.bind(this), tagName);
        window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
        return true;
    },

    _startEditingAsHTML: function(commitCallback, initialValue)
    {
        if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement))
            return true;

        this._htmlEditElement = document.createElement("div");
        this._htmlEditElement.className = "source-code elements-tree-editor";
        this._htmlEditElement.textContent = initialValue;

        // Hide header items.
        var child = this.listItemElement.firstChild;
        while (child) {
            child.style.display = "none";
            child = child.nextSibling;
        }
        // Hide children item.
        if (this._childrenListNode)
            this._childrenListNode.style.display = "none";
        // Append editor.
        this.listItemElement.appendChild(this._htmlEditElement);

        this.updateSelection();

        function commit()
        {
            commitCallback(this._htmlEditElement.textContent);
            dispose.call(this);
        }

        function dispose()
        {
            delete this._editing;

            // Remove editor.
            this.listItemElement.removeChild(this._htmlEditElement);
            delete this._htmlEditElement;
            // Unhide children item.
            if (this._childrenListNode)
                this._childrenListNode.style.removeProperty("display");
            // Unhide header items.
            var child = this.listItemElement.firstChild;
            while (child) {
                child.style.removeProperty("display");
                child = child.nextSibling;
            }

            this.updateSelection();
        }

        this._editing = WebInspector.startEditing(this._htmlEditElement, commit.bind(this), dispose.bind(this), null, true);
    },

    _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
    {
        delete this._editing;

        // Before we do anything, determine where we should move
        // next based on the current element's settings
        var moveToAttribute, moveToTagName, moveToNewAttribute;
        if (moveDirection) {
            var found = false;

            // Search for the attribute's position, and then decide where to move to.
            var attributes = this.representedObject.attributes;
            for (var i = 0; i < attributes.length; ++i) {
                if (attributes[i].name === attributeName) {
                    found = true;
                    if (moveDirection === "backward") {
                        if (i === 0)
                            moveToTagName = true;
                        else
                            moveToAttribute = attributes[i - 1].name;
                    } else if (moveDirection === "forward") {
                        if (i === attributes.length - 1)
                            moveToNewAttribute = true;
                        else
                            moveToAttribute = attributes[i + 1].name;
                    }
                }
            }

            // Moving From the "New Attribute" position.
            if (!found) {
                if (moveDirection === "backward" && attributes.length > 0)
                    moveToAttribute = attributes[attributes.length - 1].name;
                else if (moveDirection === "forward" && !/^\s*$/.test(newText))
                    moveToNewAttribute = true;
            }
        }

        function moveToNextAttributeIfNeeded()
        {
            // Cleanup empty new attribute sections.
            if (element.textContent.trim().length === 0)
                element.parentNode.removeChild(element);

            // Make the move.
            if (moveToAttribute)
                this._triggerEditAttribute(moveToAttribute);
            else if (moveToNewAttribute)
                this._addNewAttribute();
            else if (moveToTagName)
                this._startEditingTagName();
        }

        function regenerateStyledAttribute(name, value)
        {
            var previous = element.previousSibling;
            if (!previous || previous.nodeType !== Node.TEXT_NODE)
                element.parentNode.insertBefore(document.createTextNode(" "), element);
            element.outerHTML = this._attributeHTML(name, value);
        }

        var parseContainerElement = document.createElement("span");
        parseContainerElement.innerHTML = "<span " + newText + "></span>";
        var parseElement = parseContainerElement.firstChild;

        if (!parseElement) {
            this._editingCancelled(element, attributeName);
            moveToNextAttributeIfNeeded.call(this);
            return;
        }

        if (!parseElement.hasAttributes()) {
            this.representedObject.removeAttribute(attributeName);
            moveToNextAttributeIfNeeded.call(this);
            return;
        }

        var foundOriginalAttribute = false;
        for (var i = 0; i < parseElement.attributes.length; ++i) {
            var attr = parseElement.attributes[i];
            foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName;
            try {
                this.representedObject.setAttribute(attr.name, attr.value);
                regenerateStyledAttribute.call(this, attr.name, attr.value);
            } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can)
        }

        if (!foundOriginalAttribute)
            this.representedObject.removeAttribute(attributeName);

        this.treeOutline.focusedNodeChanged(true);

        moveToNextAttributeIfNeeded.call(this);
    },

    _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection)
    {
        delete this._editing;
        var self = this;

        function cancel()
        {
            var closingTagElement = self._distinctClosingTagElement();
            if (closingTagElement)
                closingTagElement.textContent = "</" + tagName + ">";

            self._editingCancelled(element, tagName);
            moveToNextAttributeIfNeeded.call(self);
        }

        function moveToNextAttributeIfNeeded()
        {
            if (moveDirection !== "forward")
                return;

            var attributes = this.representedObject.attributes;
            if (attributes.length > 0)
                this._triggerEditAttribute(attributes[0].name);
            else
                this._addNewAttribute();
        }

        newText = newText.trim();
        if (newText === oldText) {
            cancel();
            return;
        }

        var treeOutline = this.treeOutline;
        var wasExpanded = this.expanded;

        function changeTagNameCallback(nodeId)
        {
            if (!nodeId) {
                cancel();
                return;
            }

            // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
            WebInspector.panels.elements.updateModifiedNodes();

            WebInspector.updateFocusedNode(nodeId);
            var newTreeItem = treeOutline.findTreeElement(WebInspector.domAgent.nodeForId(nodeId));
            if (wasExpanded)
                newTreeItem.expand();

            moveToNextAttributeIfNeeded.call(newTreeItem);
        }

        InspectorBackend.changeTagName(this.representedObject.id, newText, changeTagNameCallback);
    },

    _textNodeEditingCommitted: function(element, newText)
    {
        delete this._editing;

        var textNode;
        if (this.representedObject.nodeType === Node.ELEMENT_NODE) {
            // We only show text nodes inline in elements if the element only
            // has a single child, and that child is a text node.
            textNode = this.representedObject.firstChild;
        } else if (this.representedObject.nodeType == Node.TEXT_NODE)
            textNode = this.representedObject;

        textNode.nodeValue = newText;
    },

    _editingCancelled: function(element, context)
    {
        delete this._editing;

        // Need to restore attributes structure.
        this.updateTitle();
    },

    _distinctClosingTagElement: function()
    {
        // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM

        // For an expanded element, it will be the last element with class "close"
        // in the child element list.
        if (this.expanded) {
            var closers = this._childrenListNode.querySelectorAll(".close");
            return closers[closers.length-1];
        }

        // Remaining cases are single line non-expanded elements with a closing
        // tag, or HTML elements without a closing tag (such as <br>). Return
        // null in the case where there isn't a closing tag.
        var tags = this.listItemElement.getElementsByClassName("webkit-html-tag");
        return (tags.length === 1 ? null : tags[tags.length-1]);
    },

    updateTitle: function()
    {
        // If we are editing, return early to prevent canceling the edit.
        // After editing is committed updateTitle will be called.
        if (this._editing)
            return;

        var title = this._nodeTitleInfo(WebInspector.linkifyURL).title;
        this.title = "<span class=\"highlight\">" + title + "</span>";
        delete this.selectionElement;
        this.updateSelection();
        this._preventFollowingLinksOnDoubleClick();
        this._highlightSearchResults();
    },

    _attributeHTML: function(name, value, node, linkify)
    {
        var hasText = (value.length > 0);
        var html = "<span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + name.escapeHTML() + "</span>";

        if (hasText)
            html += "=&#8203;\"";

        if (linkify && (name === "src" || name === "href")) {
            var rewrittenHref = WebInspector.resourceURLForRelatedNode(node, value);
            value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
            html += linkify(rewrittenHref, value, "webkit-html-attribute-value", node.nodeName.toLowerCase() === "a");
        } else {
            value = value.escapeHTML().replace(/([\/;:\)\]\}])/g, "$1&#8203;");
            html += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
        }

        if (hasText)
            html += "\"";

        html += "</span>";
        return html;
    },

    _tagHTML: function(tagName, isClosingTag, isDistinctTreeElement, linkify)
    {
        var node = this.representedObject;
        var result = "<span class=\"webkit-html-tag" + (isClosingTag && isDistinctTreeElement ? " close" : "")  + "\">&lt;";
        result += "<span " + (isClosingTag ? "" : "class=\"webkit-html-tag-name\"") + ">" + (isClosingTag ? "/" : "") + tagName + "</span>";
        if (!isClosingTag && node.hasAttributes()) {
            for (var i = 0; i < node.attributes.length; ++i) {
                var attr = node.attributes[i];
                result += " " + this._attributeHTML(attr.name, attr.value, node, linkify);
            }
        }
        result += "&gt;</span>&#8203;";

        return result;
    },

    _nodeTitleInfo: function(linkify)
    {
        var node = this.representedObject;
        var info = {title: "", hasChildren: this.hasChildren};

        switch (node.nodeType) {
            case Node.DOCUMENT_NODE:
                info.title = "Document";
                break;

            case Node.DOCUMENT_FRAGMENT_NODE:
                info.title = "Document Fragment";
                break;

            case Node.ATTRIBUTE_NODE:
                var value = node.value || "\u200B"; // Zero width space to force showing an empty value.
                info.title = this._attributeHTML(node.name, value);
                break;

            case Node.ELEMENT_NODE:
                var tagName = this.treeOutline.nodeNameToCorrectCase(node.nodeName).escapeHTML();
                if (this._elementCloseTag) {
                    info.title = this._tagHTML(tagName, true, true);
                    info.hasChildren = false;
                    break;
                }

                info.title = this._tagHTML(tagName, false, false, linkify);

                var textChild = onlyTextChild.call(node);
                var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;

                if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName]))) {
                    if (this.hasChildren)
                        info.title += "<span class=\"webkit-html-text-node\">&#8230;</span>&#8203;";
                    info.title += this._tagHTML(tagName, true, false);
                }

                // If this element only has a single child that is a text node,
                // just show that text and the closing tag inline rather than
                // create a subtree for them
                if (showInlineText) {
                    info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>&#8203;" + this._tagHTML(tagName, true, false);
                    info.hasChildren = false;
                }
                break;

            case Node.TEXT_NODE:
                if (isNodeWhitespace.call(node))
                    info.title = "(whitespace)";
                else {
                    if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "script") {
                        var newNode = document.createElement("span");
                        newNode.textContent = node.textContent;

                        var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript");
                        javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);

                        info.title = "<span class=\"webkit-html-text-node webkit-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
                    } else if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "style") {
                        var newNode = document.createElement("span");
                        newNode.textContent = node.textContent;

                        var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css");
                        cssSyntaxHighlighter.syntaxHighlightNode(newNode);

                        info.title = "<span class=\"webkit-html-text-node webkit-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
                    } else {
                        info.title = "\"<span class=\"webkit-html-text-node\">" + node.nodeValue.escapeHTML() + "</span>\"";
                    }
                }
                break;

            case Node.COMMENT_NODE:
                info.title = "<span class=\"webkit-html-comment\">&lt;!--" + node.nodeValue.escapeHTML() + "--&gt;</span>";
                break;

            case Node.DOCUMENT_TYPE_NODE:
                info.title = "<span class=\"webkit-html-doctype\">&lt;!DOCTYPE " + node.nodeName;
                if (node.publicId) {
                    info.title += " PUBLIC \"" + node.publicId + "\"";
                    if (node.systemId)
                        info.title += " \"" + node.systemId + "\"";
                } else if (node.systemId)
                    info.title += " SYSTEM \"" + node.systemId + "\"";
                if (node.internalSubset)
                    info.title += " [" + node.internalSubset + "]";
                info.title += "&gt;</span>";
                break;

            case Node.CDATA_SECTION_NODE:
                info.title = "<span class=\"webkit-html-text-node\">&lt;![CDATA[" + node.nodeValue.escapeHTML() + "]]&gt;</span>";
                break;
            default:
                info.title = this.treeOutline.nodeNameToCorrectCase(node.nodeName).collapseWhitespace().escapeHTML();
        }

        return info;
    },

    _showInlineText: function(node)
    {
        if (node.nodeType === Node.ELEMENT_NODE) {
            var textChild = onlyTextChild.call(node);
            if (textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength)
                return true;
        }
        return false;
    },

    remove: function()
    {
        var parentElement = this.parent;
        if (!parentElement)
            return;

        var self = this;
        function removeNodeCallback(removedNodeId)
        {
            // -1 is an error code, which means removing the node from the DOM failed,
            // so we shouldn't remove it from the tree.
            if (removedNodeId === -1)
                return;

            parentElement.removeChild(self);
            parentElement.adjustCollapsedRange(true);
        }

        InspectorBackend.removeNode(this.representedObject.id, removeNodeCallback);
    },

    _editAsHTML: function()
    {
        var treeOutline = this.treeOutline;
        var node = this.representedObject;
        var wasExpanded = this.expanded;

        function selectNode(nodeId)
        {
            if (!nodeId)
                return;

            // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
            WebInspector.panels.elements.updateModifiedNodes();

            WebInspector.updateFocusedNode(nodeId);
            if (wasExpanded) {
                var newTreeItem = treeOutline.findTreeElement(WebInspector.domAgent.nodeForId(nodeId));
                if (newTreeItem)
                    newTreeItem.expand();
            }
        }

        function commitChange(value)
        {
            InspectorBackend.setOuterHTML(node.id, value, selectNode);
        }

        InspectorBackend.getOuterHTML(node.id, this._startEditingAsHTML.bind(this, commitChange));
    },

    _copyHTML: function()
    {
        InspectorBackend.copyNode(this.representedObject.id);
    },

    _highlightSearchResults: function()
    {
        if (!this._searchQuery)
            return;
        var text = this.listItemElement.textContent;
        var regexObject = createSearchRegex(this._searchQuery);

        var offset = 0;
        var match = regexObject.exec(text);
        while (match) {
            highlightSearchResult(this.listItemElement, offset + match.index, match[0].length);
            offset += match.index + 1;
            text = text.substring(match.index + 1);
            match = regexObject.exec(text);
        }
    }
}

WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;

/* SidebarTreeElement.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren)
{
    TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren);
}

WebInspector.SidebarSectionTreeElement.prototype = {
    selectable: false,

    get smallChildren()
    {
        return this._smallChildren;
    },

    set smallChildren(x)
    {
        if (this._smallChildren === x)
            return;

        this._smallChildren = x;

        if (this._smallChildren)
            this._childrenListNode.addStyleClass("small");
        else
            this._childrenListNode.removeStyleClass("small");
    },

    onattach: function()
    {
        this._listItemNode.addStyleClass("sidebar-tree-section");
    },

    onreveal: function()
    {
        if (this.listItemElement)
            this.listItemElement.scrollIntoViewIfNeeded(false);
    }
}

WebInspector.SidebarSectionTreeElement.prototype.__proto__ = TreeElement.prototype;

WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren)
{
    TreeElement.call(this, "", representedObject || {}, hasChildren);

    if (hasChildren) {
        this.disclosureButton = document.createElement("button");
        this.disclosureButton.className = "disclosure-button";
    }

    if (!this.iconElement) {
        this.iconElement = document.createElement("img");
        this.iconElement.className = "icon";
    }

    this.statusElement = document.createElement("div");
    this.statusElement.className = "status";

    this.titlesElement = document.createElement("div");
    this.titlesElement.className = "titles";

    this.titleElement = document.createElement("span");
    this.titleElement.className = "title";
    this.titlesElement.appendChild(this.titleElement);

    this.subtitleElement = document.createElement("span");
    this.subtitleElement.className = "subtitle";
    this.titlesElement.appendChild(this.subtitleElement);

    this.className = className;
    this.mainTitle = title;
    this.subtitle = subtitle;
}

WebInspector.SidebarTreeElement.prototype = {
    get small()
    {
        return this._small;
    },

    set small(x)
    {
        this._small = x;

        if (this._listItemNode) {
            if (this._small)
                this._listItemNode.addStyleClass("small");
            else
                this._listItemNode.removeStyleClass("small");
        }
    },

    get mainTitle()
    {
        return this._mainTitle;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        this._subtitle = x;
        this.refreshTitles();
    },

    get bubbleText()
    {
        return this._bubbleText;
    },

    set bubbleText(x)
    {
        if (!this.bubbleElement) {
            this.bubbleElement = document.createElement("div");
            this.bubbleElement.className = "bubble";
            this.statusElement.appendChild(this.bubbleElement);
        }

        this._bubbleText = x;
        this.bubbleElement.textContent = x;
    },

    refreshTitles: function()
    {
        var mainTitle = this.mainTitle;
        if (this.titleElement.textContent !== mainTitle)
            this.titleElement.textContent = mainTitle;

        var subtitle = this.subtitle;
        if (subtitle) {
            if (this.subtitleElement.textContent !== subtitle)
                this.subtitleElement.textContent = subtitle;
            this.titlesElement.removeStyleClass("no-subtitle");
        } else
            this.titlesElement.addStyleClass("no-subtitle");
    },

    isEventWithinDisclosureTriangle: function(event)
    {
        return event.target === this.disclosureButton;
    },

    onattach: function()
    {
        this._listItemNode.addStyleClass("sidebar-tree-item");

        if (this.className)
            this._listItemNode.addStyleClass(this.className);

        if (this.small)
            this._listItemNode.addStyleClass("small");

        if (this.hasChildren && this.disclosureButton)
            this._listItemNode.appendChild(this.disclosureButton);

        this._listItemNode.appendChild(this.iconElement);
        this._listItemNode.appendChild(this.statusElement);
        this._listItemNode.appendChild(this.titlesElement);
    },

    onreveal: function()
    {
        if (this._listItemNode)
            this._listItemNode.scrollIntoViewIfNeeded(false);
    }
}

WebInspector.SidebarTreeElement.prototype.__proto__ = TreeElement.prototype;

/* Section.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Google Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Section = function(title, subtitle)
{
    this.element = document.createElement("div");
    this.element.className = "section";
    this.element.sectionForTest = this;

    this.headerElement = document.createElement("div");
    this.headerElement.className = "header";

    this.titleElement = document.createElement("div");
    this.titleElement.className = "title";

    this.subtitleElement = document.createElement("div");
    this.subtitleElement.className = "subtitle";

    this.headerElement.appendChild(this.subtitleElement);
    this.headerElement.appendChild(this.titleElement);

    this.headerElement.addEventListener("click", this.toggleExpanded.bind(this), false);
    this.element.appendChild(this.headerElement);

    this.title = title;
    this.subtitle = subtitle;
    this._expanded = false;
}

WebInspector.Section.prototype = {
    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;

        if (x instanceof Node) {
            this.titleElement.removeChildren();
            this.titleElement.appendChild(x);
        } else
          this.titleElement.textContent = x;
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        if (this._subtitle === x)
            return;
        this._subtitle = x;
        this.subtitleElement.textContent = x;
    },

    get subtitleAsTextForTest()
    {
        var result = this.subtitleElement.textContent;
        var child = this.subtitleElement.querySelector("[data-uncopyable]");
        if (child) {
            var linkData = child.getAttribute("data-uncopyable");
            if (linkData)
                result += linkData;
        }
        return result;
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(x)
    {
        if (x)
            this.expand();
        else
            this.collapse();
    },

    get populated()
    {
        return this._populated;
    },

    set populated(x)
    {
        this._populated = x;
        if (!x && this.onpopulate && this._expanded) {
            this.onpopulate(this);
            this._populated = true;
        }
    },

    expand: function()
    {
        if (this._expanded)
            return;
        this._expanded = true;
        this.element.addStyleClass("expanded");

        if (!this._populated && this.onpopulate) {
            this.onpopulate(this);
            this._populated = true;
        }
    },

    collapse: function()
    {
        if (!this._expanded)
            return;
        this._expanded = false;
        this.element.removeStyleClass("expanded");
    },

    toggleExpanded: function()
    {
        this.expanded = !this.expanded;
    }
}

/* PropertiesSection.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Google Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PropertiesSection = function(title, subtitle)
{
    WebInspector.Section.call(this, title, subtitle);

    this.propertiesElement = document.createElement("ol");
    this.propertiesElement.className = "properties properties-tree source-code";
    this.propertiesElement.tabIndex = 0;
    this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
    this.propertiesTreeOutline.section = this;

    this.element.appendChild(this.propertiesElement);
}

WebInspector.PropertiesSection.prototype.__proto__ = WebInspector.Section.prototype;

/* RemoteObject.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.RemoteObject = function(objectId, type, description, hasChildren)
{
    this._objectId = objectId;
    this._type = type;
    this._description = description;
    this._hasChildren = hasChildren;
}

WebInspector.RemoteObject.fromPrimitiveValue = function(value)
{
    return new WebInspector.RemoteObject(null, typeof value, value);
}

WebInspector.RemoteObject.resolveNode = function(node, callback)
{
    function mycallback(object)
    {
        callback(object ? WebInspector.RemoteObject.fromPayload(object) : null);
    }
    InjectedScriptAccess.getForNode(node).resolveNode(node.id, mycallback);
}

WebInspector.RemoteObject.fromPayload = function(payload)
{
    if (typeof payload === "object")
        return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.description, payload.hasChildren);
    // FIXME: make sure we only get here with real payloads in the new DebuggerAgent.js.
    return payload;
}

WebInspector.RemoteObject.type = function(remoteObject)
{
    if (remoteObject === null)
        return "null";

    var type = typeof remoteObject;
    if (type !== "object" && type !== "function")
        return type;

    return remoteObject.type;
}

WebInspector.RemoteObject.prototype = {
    get objectId()
    {
        return this._objectId;
    },

    get type()
    {
        return this._type;
    },

    get description()
    {
        return this._description;
    },

    get hasChildren()
    {
        return this._hasChildren;
    },

    isError: function()
    {
        return this._type === "error";
    },

    getOwnProperties: function(abbreviate, callback)
    {
        this.getProperties(false, abbreviate, callback);
    },

    getProperties: function(ignoreHasOwnProperty, abbreviate, callback)
    {
        if (!this._objectId) {
            callback([]);
            return;
        }
        function remoteObjectBinder(properties)
        {
            for (var i = 0; properties && i < properties.length; ++i)
                properties[i].value = WebInspector.RemoteObject.fromPayload(properties[i].value);
            callback(properties);
        }
        InjectedScriptAccess.getForObjectId(this._objectId).getProperties(this._objectId, ignoreHasOwnProperty, abbreviate, remoteObjectBinder);
    },

    setPropertyValue: function(name, value, callback)
    {
        if (!this._objectId) {
            callback(false);
            return;
        }
        InjectedScriptAccess.getForObjectId(this._objectId).setPropertyValue(this._objectId, name, value, callback);
    },

    pushNodeToFrontend: function(callback)
    {
        InjectedScriptAccess.getForObjectId(this._objectId).pushNodeToFrontend(this._objectId, callback);
    }
}

WebInspector.RemoteObjectProperty = function(name, value)
{
    this.name = name;
    this.value = value;
}

/* ObjectPropertiesSection.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor)
{
    this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties"));
    this.object = object;
    this.ignoreHasOwnProperty = ignoreHasOwnProperty;
    this.extraProperties = extraProperties;
    this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
    this.editable = true;

    WebInspector.PropertiesSection.call(this, title, subtitle);
}

WebInspector.ObjectPropertiesSection.prototype = {
    onpopulate: function()
    {
        this.update();
    },

    update: function()
    {
        var self = this;
        var callback = function(properties) {
            if (!properties)
                return;
            self.updateProperties(properties);
        };
        this.object.getProperties(this.ignoreHasOwnProperty, true, callback);
    },

    updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
    {
        if (!rootTreeElementConstructor)
            rootTreeElementConstructor = this.treeElementConstructor;
            
        if (!rootPropertyComparer)
            rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
            
        if (this.extraProperties)
            for (var i = 0; i < this.extraProperties.length; ++i)
                properties.push(this.extraProperties[i]);
                
        properties.sort(rootPropertyComparer);

        this.propertiesTreeOutline.removeChildren();

        for (var i = 0; i < properties.length; ++i) {
            properties[i].parentObject = this.object;
            this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
        }

        if (!this.propertiesTreeOutline.children.length) {
            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
            var infoElement = new TreeElement(title, null, false);
            this.propertiesTreeOutline.appendChild(infoElement);
        }
        this.propertiesForTest = properties;
    }
}

WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) 
{
    var a = propertyA.name;
    var b = propertyB.name;
    if (a === "__proto__")
        return 1;
    if (b === "__proto__")
        return -1;

    // if used elsewhere make sure to
    //  - convert a and b to strings (not needed here, properties are all strings)
    //  - check if a == b (not needed here, no two properties can be the same)

    var diff = 0;
    var chunk = /^\d+|^\D+/;
    var chunka, chunkb, anum, bnum;
    while (diff === 0) {
        if (!a && b)
            return -1;
        if (!b && a)
            return 1;
        chunka = a.match(chunk)[0];
        chunkb = b.match(chunk)[0];
        anum = !isNaN(chunka);
        bnum = !isNaN(chunkb);
        if (anum && !bnum)
            return -1;
        if (bnum && !anum)
            return 1;
        if (anum && bnum) {
            diff = chunka - chunkb;
            if (diff === 0 && chunka.length !== chunkb.length) {
                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
                    return chunka.length - chunkb.length;
                else
                    return chunkb.length - chunka.length;
            }
        } else if (chunka !== chunkb)
            return (chunka < chunkb) ? -1 : 1;
        a = a.substring(chunka.length);
        b = b.substring(chunkb.length);
    }
    return diff;
}

WebInspector.ObjectPropertyTreeElement = function(property)
{
    this.property = property;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, false);
}

WebInspector.ObjectPropertyTreeElement.prototype = {
    onpopulate: function()
    {
        if (this.children.length && !this.shouldRefreshChildren)
            return;

        var callback = function(properties) {
            this.removeChildren();
            if (!properties)
                return;

            properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
            for (var i = 0; i < properties.length; ++i) {
                this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
            }
        };
        this.property.value.getOwnProperties(true, callback.bind(this));
    },

    ondblclick: function(event)
    {
        this.startEditing();
    },

    onattach: function()
    {
        this.update();
    },

    update: function()
    {
        this.nameElement = document.createElement("span");
        this.nameElement.className = "name";
        this.nameElement.textContent = this.property.name;

        var separatorElement = document.createElement("span");
        separatorElement.className = "separator";
        separatorElement.textContent = ": ";
        
        this.valueElement = document.createElement("span");
        this.valueElement.className = "value";
        this.valueElement.textContent = this.property.value.description;
        if (this.property.isGetter)
            this.valueElement.addStyleClass("dimmed");
        if (this.property.value.isError())
            this.valueElement.addStyleClass("error");
        if (this.property.value.type)
            this.valueElement.addStyleClass("console-formatted-" + this.property.value.type);

        this.listItemElement.removeChildren();

        this.listItemElement.appendChild(this.nameElement);
        this.listItemElement.appendChild(separatorElement);
        this.listItemElement.appendChild(this.valueElement);
        this.hasChildren = this.property.value.hasChildren;
    },

    updateSiblings: function()
    {
        if (this.parent.root)
            this.treeOutline.section.update();
        else
            this.parent.shouldRefreshChildren = true;
    },

    startEditing: function()
    {
        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
            return;

        var context = { expanded: this.expanded };

        // Lie about our children to prevent expanding on double click and to collapse subproperties.
        this.hasChildren = false;

        this.listItemElement.addStyleClass("editing-sub-part");

        WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingEnded: function(context)
    {
        this.listItemElement.scrollLeft = 0;
        this.listItemElement.removeStyleClass("editing-sub-part");
        if (context.expanded)
            this.expand();
    },

    editingCancelled: function(element, context)
    {
        this.update();
        this.editingEnded(context);
    },

    editingCommitted: function(element, userInput, previousContent, context)
    {
        if (userInput === previousContent)
            return this.editingCancelled(element, context); // nothing changed, so cancel

        this.applyExpression(userInput, true);

        this.editingEnded(context);
    },

    applyExpression: function(expression, updateInterface)
    {
        expression = expression.trim();
        var expressionLength = expression.length;
        var self = this;
        var callback = function(success) {
            if (!updateInterface)
                return;

            if (!success)
                self.update();

            if (!expressionLength) {
                // The property was deleted, so remove this tree element.
                self.parent.removeChild(this);
            } else {
                // Call updateSiblings since their value might be based on the value that just changed.
                self.updateSiblings();
            }
        };
        this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback);
    }
}

WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;

/* BreakpointsSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.BreakpointsSidebarPane = function(title)
{
    WebInspector.SidebarPane.call(this, title);

    this.listElement = document.createElement("ol");
    this.listElement.className = "breakpoint-list";

    this.emptyElement = document.createElement("div");
    this.emptyElement.className = "info";
    this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");

    this.bodyElement.appendChild(this.emptyElement);
}

WebInspector.BreakpointsSidebarPane.prototype = {
    reset: function()
    {
        this.listElement.removeChildren();
        if (this.listElement.parentElement) {
            this.bodyElement.removeChild(this.listElement);
            this.bodyElement.appendChild(this.emptyElement);
        }
    },

    addBreakpoint: function(breakpointItem)
    {
        breakpointItem.addEventListener("removed", this._breakpointRemoved, this);

        var element = breakpointItem.element();
        element._breakpointItem = breakpointItem;

        var currentElement = this.listElement.firstChild;
        while (currentElement) {
             if (currentElement._breakpointItem.compareTo(element._breakpointItem) > 0) {
                this.listElement.insertBefore(element, currentElement);
                break;
            }
            currentElement = currentElement.nextSibling;
        }
        if (!currentElement)
            this.listElement.appendChild(element);

        element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, breakpointItem), true);

        if (this.emptyElement.parentElement) {
            this.bodyElement.removeChild(this.emptyElement);
            this.bodyElement.appendChild(this.listElement);
        }
    },

    _breakpointRemoved: function(event)
    {
        this.listElement.removeChild(event.target.element());
        if (!this.listElement.firstChild) {
            this.bodyElement.removeChild(this.listElement);
            this.bodyElement.appendChild(this.emptyElement);
        }
    },

    _contextMenuEventFired: function(breakpointItem, event)
    {
        var contextMenu = new WebInspector.ContextMenu();
        contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), breakpointItem.remove.bind(breakpointItem));
        contextMenu.show(event);
    }
}

WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.XHRBreakpointsSidebarPane = function()
{
    WebInspector.BreakpointsSidebarPane.call(this, WebInspector.UIString("XHR Breakpoints"));

    var addButton = document.createElement("button");
    addButton.className = "add";
    addButton.addEventListener("click", this._showEditBreakpointDialog.bind(this), false);
    this.titleElement.appendChild(addButton);

    this.urlInputElement = document.createElement("span");
    this.urlInputElement.className = "breakpoint-condition editing";
}

WebInspector.XHRBreakpointsSidebarPane.prototype = {
    _showEditBreakpointDialog: function(event)
    {
        event.stopPropagation();

        if (this.urlInputElement.parentElement)
            return;

        if (!this.expanded)
            this.expanded = true;

        this.urlInputElement.textContent = "";
        this.bodyElement.insertBefore(this.urlInputElement, this.bodyElement.firstChild);
        WebInspector.startEditing(this.urlInputElement, this._hideEditBreakpointDialog.bind(this, false), this._hideEditBreakpointDialog.bind(this, true));
    },

    _hideEditBreakpointDialog: function(discard)
    {
        if (!discard)
            WebInspector.breakpointManager.createXHRBreakpoint(this.urlInputElement.textContent.toLowerCase());
        this.bodyElement.removeChild(this.urlInputElement);
    }
}

WebInspector.XHRBreakpointsSidebarPane.prototype.__proto__ = WebInspector.BreakpointsSidebarPane.prototype;

WebInspector.BreakpointItem = function(breakpoint)
{
    this._breakpoint = breakpoint;

    this._element = document.createElement("li");
    this._element.addEventListener("click", this._breakpointClicked.bind(this), false);

    var checkboxElement = document.createElement("input");
    checkboxElement.className = "checkbox-elem";
    checkboxElement.type = "checkbox";
    checkboxElement.checked = this._breakpoint.enabled;
    checkboxElement.addEventListener("click", this._checkboxClicked.bind(this), false);
    this._element.appendChild(checkboxElement);

    if ("label" in this._breakpoint)
        this._element.appendChild(document.createTextNode(this._breakpoint.label));

    this._breakpoint.addEventListener("enable-changed", this._enableChanged, this);
    this._breakpoint.addEventListener("hit-state-changed", this._hitStateChanged, this);
    this._breakpoint.addEventListener("removed", this.dispatchEventToListeners.bind(this, "removed"));
}

WebInspector.BreakpointItem.prototype = {
    element: function()
    {
        return this._element;
    },

    compareTo: function(other)
    {
        return this._breakpoint.compareTo(other._breakpoint);
    },

    remove: function()
    {
        this._breakpoint.remove();
    },

    _breakpointClicked: function(event)
    {
    },

    _checkboxClicked: function(event)
    {
        this._breakpoint.enabled = !this._breakpoint.enabled;

        // Breakpoint element may have it's own click handler.
        event.stopPropagation();
    },

    _enableChanged: function(event)
    {
        var checkbox = this._element.firstChild;
        checkbox.checked = this._breakpoint.enabled;
    },

    _hitStateChanged: function(event)
    {
        if (event.target.hit)
            this._element.addStyleClass("breakpoint-hit");
        else
            this._element.removeStyleClass("breakpoint-hit");
    }
}

WebInspector.BreakpointItem.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.JSBreakpointItem = function(breakpoint)
{
    WebInspector.BreakpointItem.call(this, breakpoint);

    var displayName = this._breakpoint.url ? WebInspector.displayNameForURL(this._breakpoint.url) : WebInspector.UIString("(program)");
    var labelElement = document.createTextNode(displayName + ":" + this._breakpoint.line);
    this._element.appendChild(labelElement);

    var sourceTextElement = document.createElement("div");
    sourceTextElement.textContent = this._breakpoint.sourceText;
    sourceTextElement.className = "source-text monospace";
    this._element.appendChild(sourceTextElement);

    this._breakpoint.addEventListener("text-changed", this._textChanged, this);
}

WebInspector.JSBreakpointItem.prototype = {
    _breakpointClicked: function()
    {
        WebInspector.panels.scripts.showSourceLine(this._breakpoint.url, this._breakpoint.line);
    },

    _textChanged: function()
    {
        var sourceTextElement = this._element.firstChild.nextSibling.nextSibling;
        sourceTextElement.textContent = this._breakpoint.sourceText;
    }
}

WebInspector.JSBreakpointItem.prototype.__proto__ = WebInspector.BreakpointItem.prototype;

WebInspector.DOMBreakpointItem = function(breakpoint)
{
    WebInspector.BreakpointItem.call(this, breakpoint);

    var link = WebInspector.panels.elements.linkifyNodeById(this._breakpoint.nodeId);
    this._element.appendChild(link);

    var type = WebInspector.DOMBreakpoint.labelForType(this._breakpoint.type);
    var typeElement = document.createTextNode(" - " + type);
    this._element.appendChild(typeElement);
}

WebInspector.DOMBreakpointItem.prototype = {
    _breakpointClicked: function()
    {
        WebInspector.updateFocusedNode(this._breakpoint.nodeId);
    }
}

WebInspector.DOMBreakpointItem.prototype.__proto__ = WebInspector.BreakpointItem.prototype;

WebInspector.EventListenerBreakpointsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listener Breakpoints"));

    this.categoriesElement = document.createElement("ol");
    this.categoriesElement.tabIndex = 0;
    this.categoriesElement.addStyleClass("properties-tree event-listener-breakpoints");
    this.categoriesTreeOutline = new TreeOutline(this.categoriesElement);
    this.bodyElement.appendChild(this.categoriesElement);
}

WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
    _populate: function()
    {
        var categories = {
            "Mouse": ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel"],
            "Keyboard": ["keydown", "keypress", "keyup"]
        };

        for (var category in categories) {
            var categoryTreeElement = new TreeElement(WebInspector.UIString(category));
            this.categoriesTreeOutline.appendChild(categoryTreeElement);
            categoryTreeElement.listItemElement.addStyleClass("event-category");
            categoryTreeElement.selectable = true;

            var categoryItem = {};
            categoryItem.checkbox = this._createCheckbox(categoryTreeElement, this._categoryCheckboxClicked.bind(this, categoryItem));
            categoryItem.children = {};

            var eventNames = categories[category];
            for (var i = 0; i < eventNames.length; ++i) {
                var eventName = eventNames[i];

                var breakpoint = WebInspector.breakpointManager.createEventListenerBreakpoint(eventName);

                var eventNameTreeElement = new TreeElement(breakpoint.label);
                categoryTreeElement.appendChild(eventNameTreeElement);
                eventNameTreeElement.listItemElement.addStyleClass("source-code");
                eventNameTreeElement.selectable = true;

                var eventNameItem = {};
                eventNameItem.checkbox = this._createCheckbox(eventNameTreeElement, this._eventNameCheckboxClicked.bind(this, categoryItem, eventNameItem));
                eventNameItem.breakpoint = breakpoint;

                breakpoint.addEventListener("enable-changed", this._breakpointEnableChanged.bind(this, categoryItem, eventNameItem), true);

                categoryItem.children[eventName] = eventNameItem;
            }
        }
    },

    _createCheckbox: function(treeElement, checkboxClickedDelegate)
    {
        var checkbox = document.createElement("input");
        checkbox.className = "checkbox-elem";
        checkbox.type = "checkbox";
        checkbox.addEventListener("click", checkboxClickedDelegate, true);
        treeElement.listItemElement.insertBefore(checkbox, treeElement.listItemElement.firstChild);
        return checkbox;
    },

    _categoryCheckboxClicked: function(categoryItem)
    {
        var checkbox = categoryItem.checkbox;
        checkbox.indeterminate = false;
        for (var eventName in categoryItem.children) {
            var eventNameItem = categoryItem.children[eventName];
            eventNameItem.checkbox.checked = checkbox.checked;
            eventNameItem.breakpoint.enabled = checkbox.checked;
        }
    },

    _eventNameCheckboxClicked: function(categoryItem, eventNameItem)
    {
        this._updateCategoryCheckbox(categoryItem);
        eventNameItem.breakpoint.enabled = eventNameItem.checkbox.checked;
    },

    _breakpointEnableChanged: function(categoryItem, eventNameItem)
    {
        if (eventNameItem.checkbox.checked === eventNameItem.breakpoint.enabled)
            return;

        eventNameItem.checkbox.checked = eventNameItem.breakpoint.enabled;
        this._updateCategoryCheckbox(categoryItem);
    },

    _updateCategoryCheckbox: function(categoryItem)
    {
        var hasEnabled = false, hasDisabled = false;
        for (var eventName in categoryItem.children) {
            var eventNameItem = categoryItem.children[eventName];
            if (eventNameItem.checkbox.checked)
                hasEnabled = true;
            else
                hasDisabled = true;
        }
        categoryItem.checkbox.checked = hasEnabled;
        categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled;
    },

    reset: function()
    {
        this.categoriesTreeOutline.removeChildren();
        this._populate();
    }
}

WebInspector.EventListenerBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* CallStackSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.CallStackSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack"));
}

WebInspector.CallStackSidebarPane.prototype = {
    update: function(callFrames, sourceIDMap)
    {
        this.bodyElement.removeChildren();

        this.placards = [];
        delete this._selectedCallFrame;

        if (!callFrames) {
            var infoElement = document.createElement("div");
            infoElement.className = "info";
            infoElement.textContent = WebInspector.UIString("Not Paused");
            this.bodyElement.appendChild(infoElement);
            return;
        }

        var title;
        var subtitle;
        var scriptOrResource;

        for (var i = 0; i < callFrames.length; ++i) {
            var callFrame = callFrames[i];
            switch (callFrame.type) {
            case "function":
                title = callFrame.functionName || WebInspector.UIString("(anonymous function)");
                break;
            case "program":
                title = WebInspector.UIString("(program)");
                break;
            }

            scriptOrResource = sourceIDMap[callFrame.sourceID];
            if (scriptOrResource)
                subtitle = WebInspector.displayNameForURL(scriptOrResource.sourceURL || scriptOrResource.url);
            else
                subtitle = WebInspector.UIString("(internal script)");

            if (callFrame.line > 0) {
                if (subtitle)
                    subtitle += ":" + callFrame.line;
                else
                    subtitle = WebInspector.UIString("line %d", callFrame.line);
            }

            var placard = new WebInspector.Placard(title, subtitle);
            placard.callFrame = callFrame;

            placard.element.addEventListener("click", this._placardSelected.bind(this), false);

            this.placards.push(placard);
            this.bodyElement.appendChild(placard.element);
        }
    },

    updateStatus:  function(eventType, eventData)
    {
        var statusElement = document.createElement("div");
        if (eventType === WebInspector.BreakpointManager.DebuggerEventType.DOMBreakpoint) {
            var breakpoint = eventData.breakpoint;
            var substitutions = [WebInspector.DOMBreakpoint.labelForType(breakpoint.type), WebInspector.panels.elements.linkifyNodeById(breakpoint.nodeId)];
            var formatters = {
                s: function(substitution)
                {
                    return substitution;
                }
            };
            function append(a, b)
            {
                if (typeof b === "string")
                    b = document.createTextNode(b);
                statusElement.appendChild(b);
            }
            if (breakpoint.type === WebInspector.DOMBreakpoint.Types.SubtreeModified) {
                var targetNode = WebInspector.panels.elements.linkifyNodeById(eventData.targetNodeId);
                if (eventData.insertion) {
                    if (eventData.targetNodeId !== breakpoint.nodeId)
                        WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.", substitutions.concat(targetNode), formatters, "", append);
                    else
                        WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.", substitutions, formatters, "", append);
                } else
                    WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.", substitutions.concat(targetNode), formatters, "", append);
            } else
                WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s.", substitutions, formatters, "", append);
        } else if (eventType === WebInspector.BreakpointManager.DebuggerEventType.NativeBreakpoint) {
            if (eventData.type === "XHR")
                statusElement.appendChild(document.createTextNode(WebInspector.UIString("Paused on a XMLHttpRequest.")));
            else if (eventData.type === "EventListener")
                statusElement.appendChild(document.createTextNode(WebInspector.UIString("Paused on a \"%s\" Event Listener.", eventData.eventName)));
        } else
            return;

        statusElement.className = "info";
        this.bodyElement.appendChild(statusElement);
    },

    get selectedCallFrame()
    {
        return this._selectedCallFrame;
    },

    set selectedCallFrame(x)
    {
        if (this._selectedCallFrame === x)
            return;

        this._selectedCallFrame = x;

        for (var i = 0; i < this.placards.length; ++i) {
            var placard = this.placards[i];
            placard.selected = (placard.callFrame === this._selectedCallFrame);
        }

        this.dispatchEventToListeners("call frame selected");
    },

    handleShortcut: function(event)
    {
        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            handler(event);
            event.handled = true;
        }
    },

    _selectNextCallFrameOnStack: function()
    {
        var index = this._selectedCallFrameIndex();
        if (index == -1)
            return;
        this._selectedPlacardByIndex(index + 1);
    },

    _selectPreviousCallFrameOnStack: function()
    {
        var index = this._selectedCallFrameIndex();
        if (index == -1)
            return;
        this._selectedPlacardByIndex(index - 1);
    },

    _selectedPlacardByIndex: function(index)
    {
        if (index < 0 || index >= this.placards.length)
            return;
        var placard = this.placards[index];
        this.selectedCallFrame = placard.callFrame
    },

    _selectedCallFrameIndex: function()
    {
        if (!this._selectedCallFrame)
            return -1;
        for (var i = 0; i < this.placards.length; ++i) {
            var placard = this.placards[i];
            if (placard.callFrame === this._selectedCallFrame)
                return i;
        }
        return -1;
    },

    _placardSelected: function(event)
    {
        var placardElement = event.target.enclosingNodeOrSelfWithClass("placard");
        this.selectedCallFrame = placardElement.placard.callFrame;
    },

    registerShortcuts: function(section)
    {
        this._shortcuts = {};

        var nextCallFrame = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Period,
            WebInspector.KeyboardShortcut.Modifiers.Ctrl);
        this._shortcuts[nextCallFrame.key] = this._selectNextCallFrameOnStack.bind(this);

        var prevCallFrame = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Comma,
            WebInspector.KeyboardShortcut.Modifiers.Ctrl);
        this._shortcuts[prevCallFrame.key] = this._selectPreviousCallFrameOnStack.bind(this);

        section.addRelatedKeys([ nextCallFrame.name, prevCallFrame.name ], WebInspector.UIString("Next/previous call frame"));
    }
}

WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* ScopeChainSidebarPane.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScopeChainSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Scope Variables"));
    this._expandedProperties = [];
}

WebInspector.ScopeChainSidebarPane.prototype = {
    update: function(callFrame)
    {
        this.bodyElement.removeChildren();

        this.sections = [];
        this.callFrame = callFrame;

        if (!callFrame) {
            var infoElement = document.createElement("div");
            infoElement.className = "info";
            infoElement.textContent = WebInspector.UIString("Not Paused");
            this.bodyElement.appendChild(infoElement);
            return;
        }

        var foundLocalScope = false;
        var scopeChain = callFrame.scopeChain;
        for (var i = 0; i < scopeChain.length; ++i) {
            var scopeObjectProxy = scopeChain[i];
            var title = null;
            var subtitle = scopeObjectProxy.description;
            var emptyPlaceholder = null;
            var extraProperties = null;

            if (scopeObjectProxy.isLocal) {
                foundLocalScope = true;
                title = WebInspector.UIString("Local");
                emptyPlaceholder = WebInspector.UIString("No Variables");
                subtitle = null;
                if (scopeObjectProxy.thisObject)
                    extraProperties = [ new WebInspector.RemoteObjectProperty("this", WebInspector.RemoteObject.fromPayload(scopeObjectProxy.thisObject)) ];
            } else if (scopeObjectProxy.isClosure) {
                title = WebInspector.UIString("Closure");
                emptyPlaceholder = WebInspector.UIString("No Variables");
                subtitle = null;
            } else if (i === (scopeChain.length - 1))
                title = WebInspector.UIString("Global");
            else if (scopeObjectProxy.isElement)
                title = WebInspector.UIString("Event Target");
            else if (scopeObjectProxy.isDocument)
                title = WebInspector.UIString("Event Document");
            else if (scopeObjectProxy.isWithBlock)
                title = WebInspector.UIString("With Block");

            if (!title || title === subtitle)
                subtitle = null;

            var section = new WebInspector.ObjectPropertiesSection(WebInspector.RemoteObject.fromPayload(scopeObjectProxy), title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement);
            section.editInSelectedCallFrameWhenPaused = true;
            section.pane = this;

            if (!foundLocalScope || scopeObjectProxy.isLocal)
                section.expanded = true;

            this.sections.push(section);
            this.bodyElement.appendChild(section.element);
        }
    }
}

WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.ScopeVariableTreeElement = function(property)
{
    WebInspector.ObjectPropertyTreeElement.call(this, property);
}

WebInspector.ScopeVariableTreeElement.prototype = {
    onattach: function()
    {
        WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
        if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane._expandedProperties)
            this.expand();
    },

    onexpand: function()
    {
        this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier] = true;
    },

    oncollapse: function()
    {
        delete this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier];
    },

    get propertyIdentifier()
    {
        if ("_propertyIdentifier" in this)
            return this._propertyIdentifier;
        var section = this.treeOutline.section;
        this._propertyIdentifier = section.title + ":" + (section.subtitle ? section.subtitle + ":" : "") + this.propertyPath;
        return this._propertyIdentifier;
    },

    get propertyPath()
    {
        if ("_propertyPath" in this)
            return this._propertyPath;

        var current = this;
        var result;

        do {
            if (result)
                result = current.property.name + "." + result;
            else
                result = current.property.name;
            current = current.parent;
        } while (current && !current.root);

        this._propertyPath = result;
        return result;
    }
}

WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;

/* WatchExpressionsSidebarPane.js */

/*
 * Copyright (C) IBM Corp. 2009  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of IBM Corp. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.WatchExpressionsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));
    WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
}

WebInspector.WatchExpressionsSidebarPane.prototype = {
    _settingsLoaded: function()
    {
        this.bodyElement.removeChildren();

        this.expanded = WebInspector.applicationSettings.watchExpressions.length > 0;
        this.section = new WebInspector.WatchExpressionsSection();
        this.bodyElement.appendChild(this.section.element);

        var addElement = document.createElement("button");
        addElement.setAttribute("type", "button");
        addElement.textContent = WebInspector.UIString("Add");
        addElement.addEventListener("click", this.section.addExpression.bind(this.section), false);

        var refreshElement = document.createElement("button");
        refreshElement.setAttribute("type", "button");
        refreshElement.textContent = WebInspector.UIString("Refresh");
        refreshElement.addEventListener("click", this.section.update.bind(this.section), false);

        var centerElement = document.createElement("div");
        centerElement.addStyleClass("watch-expressions-buttons-container");
        centerElement.appendChild(addElement);
        centerElement.appendChild(refreshElement);
        this.bodyElement.appendChild(centerElement);

        this.onexpand = this.refreshExpressions.bind(this);
    },

    refreshExpressions: function()
    {
        if (this.section)
            this.section.update();
    }
}

WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.WatchExpressionsSection = function()
{
    this._watchObjectGroupId = "watch-group";

    WebInspector.ObjectPropertiesSection.call(this);

    this.watchExpressions = WebInspector.applicationSettings.watchExpressions;

    this.headerElement.className = "hidden";
    this.editable = true;
    this.expanded = true;
    this.propertiesElement.addStyleClass("watch-expressions");
}

WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";

WebInspector.WatchExpressionsSection.prototype = {
    update: function()
    {
        function appendResult(expression, watchIndex, result)
        {
            var property = new WebInspector.RemoteObjectProperty(expression, result);
            property.watchIndex = watchIndex;

            // To clarify what's going on here: 
            // In the outer function, we calculate the number of properties
            // that we're going to be updating, and set that in the
            // propertyCount variable.  
            // In this function, we test to see when we are processing the 
            // last property, and then call the superclass's updateProperties() 
            // method to get all the properties refreshed at once.
            properties.push(property);
            
            if (properties.length == propertyCount) {
                this.updateProperties(properties, WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);

                // check to see if we just added a new watch expression,
                // which will always be the last property
                if (this._newExpressionAdded) {
                    delete this._newExpressionAdded;

                    treeElement = this.findAddedTreeElement();
                    if (treeElement)
                        treeElement.startEditing();
                }
            }
        }

        // TODO: pass exact injected script id.
        InspectorBackend.releaseWrapperObjectGroup(0, this._watchObjectGroupId)
        var properties = [];

        // Count the properties, so we known when to call this.updateProperties()
        // in appendResult()
        var propertyCount = 0;
        for (var i = 0; i < this.watchExpressions.length; ++i) {
            if (!this.watchExpressions[i]) 
                continue;
            ++propertyCount;
        }

        // Now process all the expressions, since we have the actual count,
        // which is checked in the appendResult inner function.
        for (var i = 0; i < this.watchExpressions.length; ++i) {
            var expression = this.watchExpressions[i];
            if (!expression)
                continue;

            WebInspector.console.evalInInspectedWindow("(" + expression + ")", this._watchObjectGroupId, appendResult.bind(this, expression, i));
        }

        // note this is setting the expansion of the tree, not the section;
        // with no expressions, and expanded tree, we get some extra vertical
        // white space
        // FIXME: should change to use header buttons instead of the buttons
        // at the bottom of the section, then we can add a "No Watch Expressions
        // element when there are no watch expressions, and this issue should
        // go away.
        this.expanded = (propertyCount != 0);
    },

    addExpression: function()
    {
        this._newExpressionAdded = true;
        this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
        this.update();
    },

    updateExpression: function(element, value)
    {
        this.watchExpressions[element.property.watchIndex] = value;
        this.saveExpressions();
        this.update();
    },

    findAddedTreeElement: function()
    {
        var children = this.propertiesTreeOutline.children;
        for (var i = 0; i < children.length; ++i)
            if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
                return children[i];
    },

    saveExpressions: function()
    {
        var toSave = [];
        for (var i = 0; i < this.watchExpressions.length; i++)
            if (this.watchExpressions[i])
                toSave.push(this.watchExpressions[i]);

        WebInspector.applicationSettings.watchExpressions = toSave;
        return toSave.length;
    }
}

WebInspector.WatchExpressionsSection.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype;

WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB) 
{
    if (propertyA.watchIndex == propertyB.watchIndex)
        return 0;
    else if (propertyA.watchIndex < propertyB.watchIndex)
        return -1;
    else
        return 1;
}

WebInspector.WatchExpressionTreeElement = function(property)
{
    WebInspector.ObjectPropertyTreeElement.call(this, property);
}

WebInspector.WatchExpressionTreeElement.prototype = {
    update: function()
    {
        WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);

        if (this.property.value.isError())
            this.valueElement.addStyleClass("watch-expressions-error-level");

        var deleteButton = document.createElement("input");
        deleteButton.type = "button";
        deleteButton.title = WebInspector.UIString("Delete watch expression.");
        deleteButton.addStyleClass("enabled-button");
        deleteButton.addStyleClass("delete-button");
        deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);

        this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
    },

    _deleteButtonClicked: function()
    {
        this.treeOutline.section.updateExpression(this, null);
    },

    startEditing: function()
    {
        if (WebInspector.isBeingEdited(this.nameElement) || !this.treeOutline.section.editable)
            return;

        this.nameElement.textContent = this.property.name.trim();

        var context = { expanded: this.expanded };

        // collapse temporarily, if required
        this.hasChildren = false;

        this.listItemElement.addStyleClass("editing-sub-part");

        WebInspector.startEditing(this.nameElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingCancelled: function(element, context)
    {
        if (!this.nameElement.textContent)
            this.treeOutline.section.updateExpression(this, null);
            
        this.update();
        this.editingEnded(context);
    },

    applyExpression: function(expression, updateInterface)
    {
        expression = expression.trim();

        if (!expression)
            expression = null;

        this.property.name = expression;
        this.treeOutline.section.updateExpression(this, expression);
    }
}

WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;

/* WorkersSidebarPane.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.WorkersSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Workers"));
    
    this._workers = {};

    this._enableWorkersCheckbox = new WebInspector.Checkbox(
        WebInspector.UIString("Debug"),
        "sidebar-pane-subtitle",
        WebInspector.UIString("Allow debugging workers. Enabling this option will replace native workers with the iframe-based JavaScript implementation"));
    this.titleElement.insertBefore(this._enableWorkersCheckbox.element, this.titleElement.firstChild);

    this._enableWorkersCheckbox.addEventListener(this._onTriggerInstrument.bind(this));
    this._enableWorkersCheckbox.checked = false;

    this._listElement = document.createElement("ol");
    this._listElement.className = "workers-list";

    this.bodyElement.appendChild(this._listElement);
    this._treeOutline = new TreeOutline(this._listElement);
}

WebInspector.WorkersSidebarPane.prototype = {
    addWorker: function(id, url, isShared)
    {
        if (id in this._workers) 
            return;
        var worker = new WebInspector.Worker(id, url, isShared);
        this._workers[id] = worker;

        var title = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url), "worker-item", true, url);
        var treeElement = new TreeElement(title, worker, false);
        this._treeOutline.appendChild(treeElement);
    },

    removeWorker: function(id)
    {
        if (id in this._workers) {
            this._treeOutline.removeChild(this._treeOutline.findTreeElement(this._workers[id]));
            delete this._workers[id];
        }
    },

    setInstrumentation: function(enabled)
    {
        InspectorBackend.removeAllScriptsToEvaluateOnLoad();
        if (enabled)
            InspectorBackend.addScriptToEvaluateOnLoad("(" + InjectedFakeWorker + ")");
    },

    reset: function()
    {
        InspectorBackend.removeAllScriptsToEvaluateOnLoad();
        this.setInstrumentation(this._enableWorkersCheckbox.checked);
        this._treeOutline.removeChildren();
        this._workers = {};
    },

    _onTriggerInstrument: function(event)
    {
        this.setInstrumentation(this._enableWorkersCheckbox.checked);
    }
};

WebInspector.WorkersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.Worker = function(id, url, shared)
{
    this.id = id;
    this.url = url;
    this.shared = shared;
}

WebInspector.didCreateWorker = function()
{
    var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
    workersPane.addWorker.apply(workersPane, arguments);
}

WebInspector.didDestroyWorker = function()
{
    var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
    workersPane.removeWorker.apply(workersPane, arguments);
}

/* MetricsSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.MetricsSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics"));
    this._inlineStyleId = null;
}

WebInspector.MetricsSidebarPane.prototype = {
    update: function(node)
    {
        if (node)
            this.node = node;
        else
            node = this.node;

        if (!node || !node.ownerDocument.defaultView || node.nodeType !== Node.ELEMENT_NODE) {
            this.bodyElement.removeChildren();
            return;
        }

        var self = this;
        var callback = function(stylePayload) {
            if (!stylePayload)
                return;
            var style = WebInspector.CSSStyleDeclaration.parseStyle(stylePayload);
            self._update(style);
        };
        InspectorBackend.getComputedStyle(node.id, callback);

        var inlineStyleCallback = function(stylePayload) {
            if (!stylePayload)
                return;
            self._inlineStyleId = stylePayload.id;
        };
        InspectorBackend.getInlineStyle(node.id, inlineStyleCallback);
    },

    _update: function(style)
    {
        var metricsElement = document.createElement("div");
        metricsElement.className = "metrics";

        function createBoxPartElement(style, name, side, suffix)
        {
            var propertyName = (name !== "position" ? name + "-" : "") + side + suffix;
            var value = style.getPropertyValue(propertyName);
            if (value === "" || (name !== "position" && value === "0px"))
                value = "\u2012";
            else if (name === "position" && value === "auto")
                value = "\u2012";
            value = value.replace(/px$/, "");

            var element = document.createElement("div");
            element.className = side;
            element.textContent = value;
            element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName), false);
            return element;
        }

        // Display types for which margin is ignored.
        var noMarginDisplayType = {
            "table-cell": true,
            "table-column": true,
            "table-column-group": true,
            "table-footer-group": true,
            "table-header-group": true,
            "table-row": true,
            "table-row-group": true
        };

        // Display types for which padding is ignored.
        var noPaddingDisplayType = {
            "table-column": true,
            "table-column-group": true,
            "table-footer-group": true,
            "table-header-group": true,
            "table-row": true,
            "table-row-group": true
        };

        // Position types for which top, left, bottom and right are ignored.
        var noPositionType = {
            "static": true
        };

        var boxes = ["content", "padding", "border", "margin", "position"];
        var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")];
        var previousBox;
        for (var i = 0; i < boxes.length; ++i) {
            var name = boxes[i];

            if (name === "margin" && noMarginDisplayType[style.display])
                continue;
            if (name === "padding" && noPaddingDisplayType[style.display])
                continue;
            if (name === "position" && noPositionType[style.position])
                continue;

            var boxElement = document.createElement("div");
            boxElement.className = name;

            if (name === "content") {
                var width = style.width.replace(/px$/, "");
                var widthElement = document.createElement("span");
                widthElement.textContent = width;
                widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width"), false);

                var height = style.height.replace(/px$/, "");
                var heightElement = document.createElement("span");
                heightElement.textContent = height;
                heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height"), false);

                boxElement.appendChild(widthElement);
                boxElement.appendChild(document.createTextNode(" \u00D7 "));
                boxElement.appendChild(heightElement);
            } else {
                var suffix = (name === "border" ? "-width" : "");

                var labelElement = document.createElement("div");
                labelElement.className = "label";
                labelElement.textContent = boxLabels[i];
                boxElement.appendChild(labelElement);

                boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix));
                boxElement.appendChild(document.createElement("br"));
                boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix));

                if (previousBox)
                    boxElement.appendChild(previousBox);

                boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix));
                boxElement.appendChild(document.createElement("br"));
                boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix));
            }

            previousBox = boxElement;
        }

        metricsElement.appendChild(previousBox);
        this.bodyElement.removeChildren();
        this.bodyElement.appendChild(metricsElement);
    },

    startEditing: function(targetElement, box, styleProperty)
    {
        if (WebInspector.isBeingEdited(targetElement))
            return;

        var context = { box: box, styleProperty: styleProperty };

        WebInspector.startEditing(targetElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
    },

    editingCancelled: function(element, context)
    {
        this.update();
    },

    editingCommitted: function(element, userInput, previousContent, context)
    {
        if (!this._inlineStyleId) {
            // Element has no renderer.
            return this.editingCancelled(element, context); // nothing changed, so cancel
        }

        if (userInput === previousContent)
            return this.editingCancelled(element, context); // nothing changed, so cancel

        if (context.box !== "position" && (!userInput || userInput === "\u2012"))
            userInput = "0px";
        else if (context.box === "position" && (!userInput || userInput === "\u2012"))
            userInput = "auto";

        // Append a "px" unit if the user input was just a number.
        if (/^\d+$/.test(userInput))
            userInput += "px";

        var self = this;
        var callback = function(success) {
            if (!success)
                return;
            self.dispatchEventToListeners("metrics edited");
            self.update();
        };

        InspectorBackend.setStyleProperty(this._inlineStyleId, context.styleProperty, userInput, callback);
    }
}

WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* PropertiesSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PropertiesSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties"));
}

WebInspector.PropertiesSidebarPane.prototype = {
    update: function(node)
    {
        var body = this.bodyElement;

        if (!node) {
            body.removeChildren();
            this.sections = [];
            return;
        }

        var self = this;
        var callback = function(prototypes) {
            var body = self.bodyElement;
            body.removeChildren();
            self.sections = [];

            // Get array of prototype user-friendly names.
            for (var i = 0; i < prototypes.length; ++i) {
                var prototype = WebInspector.RemoteObject.fromPayload(prototypes[i]);
                var title = prototype.description;
                if (title.match(/Prototype$/))
                    title = title.replace(/Prototype$/, "");
                var section = new WebInspector.ObjectPropertiesSection(prototype, title, WebInspector.UIString("Prototype"));
                self.sections.push(section);
                body.appendChild(section.element);
            }
        };
        InjectedScriptAccess.getForNode(node).getPrototypes(node.id, callback);
    }
}

WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* EventListenersSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.EventListenersSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listeners"));
    this.bodyElement.addStyleClass("events-pane");

    this.sections = [];

    this.settingsSelectElement = document.createElement("select");

    var option = document.createElement("option");
    option.value = "all";
    option.label = WebInspector.UIString("All Nodes");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "selected";
    option.label = WebInspector.UIString("Selected Node Only");
    this.settingsSelectElement.appendChild(option);

    WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
    this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);

    this.titleElement.appendChild(this.settingsSelectElement);
}

WebInspector.EventListenersSidebarPane.prototype = {
    _settingsLoaded: function()
    {
        var filter = WebInspector.applicationSettings.eventListenersFilter;
        if (filter === "all")
            this.settingsSelectElement[0].selected = true;
        if (filter === "selected")
            this.settingsSelectElement[1].selected = true;
    },

    update: function(node)
    {
        var body = this.bodyElement;
        body.removeChildren();
        this.sections = [];

        var self = this;
        function callback(nodeId, eventListeners) {
            var sectionNames = [];
            var sectionMap = {};
            for (var i = 0; i < eventListeners.length; ++i) {
                var eventListener = eventListeners[i];
                eventListener.node = WebInspector.domAgent.nodeForId(eventListener.nodeId);
                delete eventListener.nodeId; // no longer needed
                if (/^function _inspectorCommandLineAPI_logEvent\(/.test(eventListener.listenerBody.toString()))
                    continue; // ignore event listeners generated by monitorEvent
                var type = eventListener.type;
                var section = sectionMap[type];
                if (!section) {
                    section = new WebInspector.EventListenersSection(type, nodeId);
                    sectionMap[type] = section;
                    sectionNames.push(type);
                    self.sections.push(section);
                }
                section.addListener(eventListener);
            }
            
            if (sectionNames.length === 0) {
                var div = document.createElement("div");
                div.className = "info";
                div.textContent = WebInspector.UIString("No Event Listeners");
                body.appendChild(div);
                return;
            }

            sectionNames.sort();
            for (var i = 0; i < sectionNames.length; ++i) {
                var section = sectionMap[sectionNames[i]];
                section.update();
                body.appendChild(section.element);
            }
        }

        WebInspector.EventListeners.getEventListenersForNodeAsync(node, callback);
    },

    _changeSetting: function(event)
    {
        var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
        WebInspector.applicationSettings.eventListenersFilter = selectedOption.value;

        for (var i = 0; i < this.sections.length; ++i)
            this.sections[i].update();
    }
}

WebInspector.EventListenersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.EventListenersSection = function(title, nodeId)
{
    this.eventListeners = [];
    this._nodeId = nodeId;
    WebInspector.PropertiesSection.call(this, title);

    // Changed from a Properties List
    this.propertiesElement.parentNode.removeChild(this.propertiesElement);
    delete this.propertiesElement;
    delete this.propertiesTreeOutline;

    this.eventBars = document.createElement("div");
    this.eventBars.className = "event-bars";
    this.element.appendChild(this.eventBars);
}

WebInspector.EventListenersSection.prototype = {
    update: function()
    {
        // A Filtered Array simplifies when to create connectors
        var filteredEventListeners = this.eventListeners;
        if (WebInspector.applicationSettings.eventListenersFilter === "selected") {
            filteredEventListeners = [];
            for (var i = 0; i < this.eventListeners.length; ++i) {
                var eventListener = this.eventListeners[i];
                if (eventListener.node.id === this._nodeId)
                    filteredEventListeners.push(eventListener);
            }
        }

        this.eventBars.removeChildren();
        var length = filteredEventListeners.length;
        for (var i = 0; i < length; ++i) {
            var eventListener = filteredEventListeners[i];
            var eventListenerBar = new WebInspector.EventListenerBar(eventListener, this._nodeId);
            if (i < length - 1) {
                var connector = document.createElement("div");
                connector.className = "event-bar-connector";
                eventListenerBar.element.appendChild(connector);
            }

            this.eventBars.appendChild(eventListenerBar.element);
        }
    },

    addListener: function(eventListener)
    {
        this.eventListeners.push(eventListener);
    }
}

WebInspector.EventListenersSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.EventListenerBar = function(eventListener, nodeId)
{
    this.eventListener = eventListener;
    this._nodeId = nodeId;
    WebInspector.ObjectPropertiesSection.call(this);
    this._setNodeTitle();
    this._setFunctionSubtitle();
    this.editable = false;
    this.element.className = "event-bar"; /* Changed from "section" */
    this.propertiesElement.className = "event-properties properties-tree source-code"; /* Changed from "properties" */
}

WebInspector.EventListenerBar.prototype = {
    update: function()
    {
        function updateWithNodeObject(nodeObject)
        {
            var properties = [];
            if (nodeObject)
                properties.push(new WebInspector.RemoteObjectProperty("node", nodeObject));

            for (var propertyName in this.eventListener) {
                var value = WebInspector.RemoteObject.fromPrimitiveValue(this.eventListener[propertyName]);
                properties.push(new WebInspector.RemoteObjectProperty(propertyName, value));
            }
            this.updateProperties(properties);
        }
        var node = this.eventListener.node;
        delete this.eventListener.node;
        WebInspector.RemoteObject.resolveNode(node, updateWithNodeObject.bind(this));
    },

    _setNodeTitle: function()
    {
        var node = this.eventListener.node;
        if (!node)
            return;

        if (node.nodeType === Node.DOCUMENT_NODE) {
            this.titleElement.textContent = "document";
            return;
        }

        if (node.id === this._nodeId) {
            this.titleElement.textContent = appropriateSelectorForNode(node);
            return;
        }

        this.titleElement.removeChildren();
        this.titleElement.appendChild(WebInspector.panels.elements.linkifyNodeReference(this.eventListener.node));
    },

    _setFunctionSubtitle: function()
    {
        // Requires that Function.toString() return at least the function's signature.
        if (this.eventListener.sourceName) {
            this.subtitleElement.removeChildren();
            this.subtitleElement.appendChild(WebInspector.linkifyResourceAsNode(this.eventListener.sourceName, "scripts", this.eventListener.lineNumber));
        } else {
            var match = this.eventListener.listenerBody.match(/function ([^\(]+?)\(/);
            if (match)
                this.subtitleElement.textContent = match[1];
            else
                this.subtitleElement.textContent = WebInspector.UIString("(anonymous function)");
        }
    }
}

WebInspector.EventListenerBar.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype;

/* Color.js */

/*
 * Copyright (C) 2009 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.Color = function(str)
{
    this.value = str;
    this._parse();
}

WebInspector.Color.prototype = {
    get shorthex()
    {
        if ("_short" in this)
            return this._short;

        if (!this.simple)
            return null;

        var hex = this.hex;
        if (hex.charAt(0) === hex.charAt(1) && hex.charAt(2) === hex.charAt(3) && hex.charAt(4) === hex.charAt(5))
            this._short = hex.charAt(0) + hex.charAt(2) + hex.charAt(4);
        else
            this._short = hex;

        return this._short;
    },

    get hex()
    {
        if (!this.simple)
            return null;

        return this._hex;
    },

    set hex(x)
    {
        this._hex = x;
    },

    get rgb()
    {
        if ("_rgb" in this)
            return this._rgb;

        if (this.simple)
            this._rgb = this._hexToRGB(this.hex);
        else {
            var rgba = this.rgba;
            this._rgb = [rgba[0], rgba[1], rgba[2]];
        }

        return this._rgb;
    },

    set rgb(x)
    {
        this._rgb = x;
    },

    get hsl()
    {
        if ("_hsl" in this)
            return this._hsl;

        this._hsl = this._rgbToHSL(this.rgb);
        return this._hsl;
    },

    set hsl(x)
    {
        this._hsl = x;
    },

    get nickname()
    {
        if (typeof this._nickname !== "undefined") // would be set on parse if there was a nickname
            return this._nickname;
        else
            return null;
    },

    set nickname(x)
    {
        this._nickname = x;
    },

    get rgba()
    {
        return this._rgba;
    },

    set rgba(x)
    {
        this._rgba = x;
    },

    get hsla()
    {
        return this._hsla;
    },

    set hsla(x)
    {
        this._hsla = x;
    },

    hasShortHex: function()
    {
        var shorthex = this.shorthex;
        return (shorthex && shorthex.length === 3);
    },

    toString: function(format)
    {
        if (!format)
            format = this.format;

        switch (format) {
            case "rgb":
                return "rgb(" + this.rgb.join(", ") + ")";
            case "rgba":
                return "rgba(" + this.rgba.join(", ") + ")";
            case "hsl":
                var hsl = this.hsl;
                return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)";
            case "hsla":
                var hsla = this.hsla;
                return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")";
            case "hex":
                return "#" + this.hex;
            case "shorthex":
                return "#" + this.shorthex;
            case "nickname":
                return this.nickname;
        }

        throw "invalid color format";
    },

    _rgbToHex: function(rgb)
    {
        var r = parseInt(rgb[0]).toString(16);
        var g = parseInt(rgb[1]).toString(16);
        var b = parseInt(rgb[2]).toString(16);
        if (r.length === 1)
            r = "0" + r;
        if (g.length === 1)
            g = "0" + g;
        if (b.length === 1)
            b = "0" + b;

        return (r + g + b).toUpperCase();
    },

    _hexToRGB: function(hex)
    {
        var r = parseInt(hex.substring(0,2), 16);
        var g = parseInt(hex.substring(2,4), 16);
        var b = parseInt(hex.substring(4,6), 16);

        return [r, g, b];
    },

    _rgbToHSL: function(rgb)
    {
        var r = parseInt(rgb[0]) / 255;
        var g = parseInt(rgb[1]) / 255;
        var b = parseInt(rgb[2]) / 255;
        var max = Math.max(r, g, b);
        var min = Math.min(r, g, b);
        var diff = max - min;
        var add = max + min;

        if (min === max)
            var h = 0;
        else if (r === max)
            var h = ((60 * (g - b) / diff) + 360) % 360;
        else if (g === max)
            var h = (60 * (b - r) / diff) + 120;
        else
            var h = (60 * (r - g) / diff) + 240;

        var l = 0.5 * add;

        if (l === 0)
            var s = 0;
        else if (l === 1)
            var s = 1;
        else if (l <= 0.5)
            var s = diff / add;
        else
            var s = diff / (2 - add);

        h = Math.round(h);
        s = Math.round(s*100);
        l = Math.round(l*100);

        return [h, s, l];
    },

    _hslToRGB: function(hsl)
    {
        var h = parseFloat(hsl[0]) / 360;
        var s = parseFloat(hsl[1]) / 100;
        var l = parseFloat(hsl[2]) / 100;

        if (l <= 0.5)
            var q = l * (1 + s);
        else
            var q = l + s - (l * s);

        var p = 2 * l - q;

        var tr = h + (1 / 3);
        var tg = h;
        var tb = h - (1 / 3);

        var r = Math.round(hueToRGB(p, q, tr) * 255);
        var g = Math.round(hueToRGB(p, q, tg) * 255);
        var b = Math.round(hueToRGB(p, q, tb) * 255);
        return [r, g, b];

        function hueToRGB(p, q, h) {
            if (h < 0)
                h += 1;
            else if (h > 1)
                h -= 1;

            if ((h * 6) < 1)
                return p + (q - p) * h * 6;
            else if ((h * 2) < 1)
                return q;
            else if ((h * 3) < 2)
                return p + (q - p) * ((2 / 3) - h) * 6;
            else
                return p;
        }
    },

    _rgbaToHSLA: function(rgba)
    {
        var alpha = rgba[3];
        var hsl = this._rgbToHSL(rgba)
        hsl.push(alpha);
        return hsl;
    },

    _hslaToRGBA: function(hsla)
    {
        var alpha = hsla[3];
        var rgb = this._hslToRGB(hsla);
        rgb.push(alpha);
        return rgb;
    },

    _parse: function()
    {
        // Special Values - Advanced but Must Be Parsed First - transparent
        var value = this.value.toLowerCase().replace(/%|\s+/g, "");
        if (value in WebInspector.Color.AdvancedNickNames) {
            this.format = "nickname";
            var set = WebInspector.Color.AdvancedNickNames[value];
            this.simple = false;
            this.rgba = set[0];
            this.hsla = set[1];
            this.nickname = set[2];
            this.alpha = set[0][3];
            return;
        }

        // Simple - #hex, rgb(), nickname, hsl()
        var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i;
        var match = this.value.match(simple);
        if (match) {
            this.simple = true;

            if (match[1]) { // hex
                var hex = match[1].toUpperCase();
                if (hex.length === 3) {
                    this.format = "shorthex";
                    this.hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
                } else {
                    this.format = "hex";
                    this.hex = hex;
                }
            } else if (match[2]) { // rgb
                this.format = "rgb";
                var rgb = match[2].split(/\s*,\s*/);
                this.rgb = rgb;
                this.hex = this._rgbToHex(rgb);
            } else if (match[3]) { // nickname
                var nickname = match[3].toLowerCase();
                if (nickname in WebInspector.Color.Nicknames) {
                    this.format = "nickname";
                    this.hex = WebInspector.Color.Nicknames[nickname];
                } else // unknown name
                    throw "unknown color name";
            } else if (match[4]) { // hsl
                this.format = "hsl";
                var hsl = match[4].replace(/%g/, "").split(/\s*,\s*/);
                this.hsl = hsl;
                this.rgb = this._hslToRGB(hsl);
                this.hex = this._rgbToHex(this.rgb);
            }

            // Fill in the values if this is a known hex color
            var hex = this.hex;
            if (hex && hex in WebInspector.Color.HexTable) {
                var set = WebInspector.Color.HexTable[hex];
                this.rgb = set[0];
                this.hsl = set[1];
                this.nickname = set[2];
            }

            return;
        }

        // Advanced - rgba(), hsla()
        var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/;
        match = this.value.match(advanced);
        if (match) {
            this.simple = false;
            if (match[1]) { // rgba
                this.format = "rgba";
                this.rgba = match[1].split(/\s*,\s*/);
                this.hsla = this._rgbaToHSLA(this.rgba);
                this.alpha = this.rgba[3];
            } else if (match[2]) { // hsla
                this.format = "hsla";
                this.hsla = match[2].replace(/%/g, "").split(/\s*,\s*/);
                this.rgba = this._hslaToRGBA(this.hsla);
                this.alpha = this.hsla[3];
            }

            return;
        }

        // Could not parse as a valid color
        throw "could not parse color";
    }
}

// Simple Values: [rgb, hsl, nickname]
WebInspector.Color.HexTable = {
    "000000": [[0, 0, 0], [0, 0, 0], "black"],
    "000080": [[0, 0, 128], [240, 100, 25], "navy"],
    "00008B": [[0, 0, 139], [240, 100, 27], "darkBlue"],
    "0000CD": [[0, 0, 205], [240, 100, 40], "mediumBlue"],
    "0000FF": [[0, 0, 255], [240, 100, 50], "blue"],
    "006400": [[0, 100, 0], [120, 100, 20], "darkGreen"],
    "008000": [[0, 128, 0], [120, 100, 25], "green"],
    "008080": [[0, 128, 128], [180, 100, 25], "teal"],
    "008B8B": [[0, 139, 139], [180, 100, 27], "darkCyan"],
    "00BFFF": [[0, 191, 255], [195, 100, 50], "deepSkyBlue"],
    "00CED1": [[0, 206, 209], [181, 100, 41], "darkTurquoise"],
    "00FA9A": [[0, 250, 154], [157, 100, 49], "mediumSpringGreen"],
    "00FF00": [[0, 255, 0], [120, 100, 50], "lime"],
    "00FF7F": [[0, 255, 127], [150, 100, 50], "springGreen"],
    "00FFFF": [[0, 255, 255], [180, 100, 50], "cyan"],
    "191970": [[25, 25, 112], [240, 64, 27], "midnightBlue"],
    "1E90FF": [[30, 144, 255], [210, 100, 56], "dodgerBlue"],
    "20B2AA": [[32, 178, 170], [177, 70, 41], "lightSeaGreen"],
    "228B22": [[34, 139, 34], [120, 61, 34], "forestGreen"],
    "2E8B57": [[46, 139, 87], [146, 50, 36], "seaGreen"],
    "2F4F4F": [[47, 79, 79], [180, 25, 25], "darkSlateGray"],
    "32CD32": [[50, 205, 50], [120, 61, 50], "limeGreen"],
    "3CB371": [[60, 179, 113], [147, 50, 47], "mediumSeaGreen"],
    "40E0D0": [[64, 224, 208], [174, 72, 56], "turquoise"],
    "4169E1": [[65, 105, 225], [225, 73, 57], "royalBlue"],
    "4682B4": [[70, 130, 180], [207, 44, 49], "steelBlue"],
    "483D8B": [[72, 61, 139], [248, 39, 39], "darkSlateBlue"],
    "48D1CC": [[72, 209, 204], [178, 60, 55], "mediumTurquoise"],
    "4B0082": [[75, 0, 130], [275, 100, 25], "indigo"],
    "556B2F": [[85, 107, 47], [82, 39, 30], "darkOliveGreen"],
    "5F9EA0": [[95, 158, 160], [182, 25, 50], "cadetBlue"],
    "6495ED": [[100, 149, 237], [219, 79, 66], "cornflowerBlue"],
    "66CDAA": [[102, 205, 170], [160, 51, 60], "mediumAquaMarine"],
    "696969": [[105, 105, 105], [0, 0, 41], "dimGray"],
    "6A5ACD": [[106, 90, 205], [248, 53, 58], "slateBlue"],
    "6B8E23": [[107, 142, 35], [80, 60, 35], "oliveDrab"],
    "708090": [[112, 128, 144], [210, 13, 50], "slateGray"],
    "778899": [[119, 136, 153], [210, 14, 53], "lightSlateGray"],
    "7B68EE": [[123, 104, 238], [249, 80, 67], "mediumSlateBlue"],
    "7CFC00": [[124, 252, 0], [90, 100, 49], "lawnGreen"],
    "7FFF00": [[127, 255, 0], [90, 100, 50], "chartreuse"],
    "7FFFD4": [[127, 255, 212], [160, 100, 75], "aquamarine"],
    "800000": [[128, 0, 0], [0, 100, 25], "maroon"],
    "800080": [[128, 0, 128], [300, 100, 25], "purple"],
    "808000": [[128, 128, 0], [60, 100, 25], "olive"],
    "808080": [[128, 128, 128], [0, 0, 50], "gray"],
    "87CEEB": [[135, 206, 235], [197, 71, 73], "skyBlue"],
    "87CEFA": [[135, 206, 250], [203, 92, 75], "lightSkyBlue"],
    "8A2BE2": [[138, 43, 226], [271, 76, 53], "blueViolet"],
    "8B0000": [[139, 0, 0], [0, 100, 27], "darkRed"],
    "8B008B": [[139, 0, 139], [300, 100, 27], "darkMagenta"],
    "8B4513": [[139, 69, 19], [25, 76, 31], "saddleBrown"],
    "8FBC8F": [[143, 188, 143], [120, 25, 65], "darkSeaGreen"],
    "90EE90": [[144, 238, 144], [120, 73, 75], "lightGreen"],
    "9370D8": [[147, 112, 219], [260, 60, 65], "mediumPurple"],
    "9400D3": [[148, 0, 211], [282, 100, 41], "darkViolet"],
    "98FB98": [[152, 251, 152], [120, 93, 79], "paleGreen"],
    "9932CC": [[153, 50, 204], [280, 61, 50], "darkOrchid"],
    "9ACD32": [[154, 205, 50], [80, 61, 50], "yellowGreen"],
    "A0522D": [[160, 82, 45], [19, 56, 40], "sienna"],
    "A52A2A": [[165, 42, 42], [0, 59, 41], "brown"],
    "A9A9A9": [[169, 169, 169], [0, 0, 66], "darkGray"],
    "ADD8E6": [[173, 216, 230], [195, 53, 79], "lightBlue"],
    "ADFF2F": [[173, 255, 47], [84, 100, 59], "greenYellow"],
    "AFEEEE": [[175, 238, 238], [180, 65, 81], "paleTurquoise"],
    "B0C4DE": [[176, 196, 222], [214, 41, 78], "lightSteelBlue"],
    "B0E0E6": [[176, 224, 230], [187, 52, 80], "powderBlue"],
    "B22222": [[178, 34, 34], [0, 68, 42], "fireBrick"],
    "B8860B": [[184, 134, 11], [43, 89, 38], "darkGoldenrod"],
    "BA55D3": [[186, 85, 211], [288, 59, 58], "mediumOrchid"],
    "BC8F8F": [[188, 143, 143], [0, 25, 65], "rosyBrown"],
    "BDB76B": [[189, 183, 107], [56, 38, 58], "darkKhaki"],
    "C0C0C0": [[192, 192, 192], [0, 0, 75], "silver"],
    "C71585": [[199, 21, 133], [322, 81, 43], "mediumVioletRed"],
    "CD5C5C": [[205, 92, 92], [0, 53, 58], "indianRed"],
    "CD853F": [[205, 133, 63], [30, 59, 53], "peru"],
    "D2691E": [[210, 105, 30], [25, 75, 47], "chocolate"],
    "D2B48C": [[210, 180, 140], [34, 44, 69], "tan"],
    "D3D3D3": [[211, 211, 211], [0, 0, 83], "lightGrey"],
    "D87093": [[219, 112, 147], [340, 60, 65], "paleVioletRed"],
    "D8BFD8": [[216, 191, 216], [300, 24, 80], "thistle"],
    "DA70D6": [[218, 112, 214], [302, 59, 65], "orchid"],
    "DAA520": [[218, 165, 32], [43, 74, 49], "goldenrod"],
    "DC143C": [[237, 164, 61], [35, 83, 58], "crimson"],
    "DCDCDC": [[220, 220, 220], [0, 0, 86], "gainsboro"],
    "DDA0DD": [[221, 160, 221], [300, 47, 75], "plum"],
    "DEB887": [[222, 184, 135], [34, 57, 70], "burlyWood"],
    "E0FFFF": [[224, 255, 255], [180, 100, 94], "lightCyan"],
    "E6E6FA": [[230, 230, 250], [240, 67, 94], "lavender"],
    "E9967A": [[233, 150, 122], [15, 72, 70], "darkSalmon"],
    "EE82EE": [[238, 130, 238], [300, 76, 72], "violet"],
    "EEE8AA": [[238, 232, 170], [55, 67, 80], "paleGoldenrod"],
    "F08080": [[240, 128, 128], [0, 79, 72], "lightCoral"],
    "F0E68C": [[240, 230, 140], [54, 77, 75], "khaki"],
    "F0F8FF": [[240, 248, 255], [208, 100, 97], "aliceBlue"],
    "F0FFF0": [[240, 255, 240], [120, 100, 97], "honeyDew"],
    "F0FFFF": [[240, 255, 255], [180, 100, 97], "azure"],
    "F4A460": [[244, 164, 96], [28, 87, 67], "sandyBrown"],
    "F5DEB3": [[245, 222, 179], [39, 77, 83], "wheat"],
    "F5F5DC": [[245, 245, 220], [60, 56, 91], "beige"],
    "F5F5F5": [[245, 245, 245], [0, 0, 96], "whiteSmoke"],
    "F5FFFA": [[245, 255, 250], [150, 100, 98], "mintCream"],
    "F8F8FF": [[248, 248, 255], [240, 100, 99], "ghostWhite"],
    "FA8072": [[250, 128, 114], [6, 93, 71], "salmon"],
    "FAEBD7": [[250, 235, 215], [34, 78, 91], "antiqueWhite"],
    "FAF0E6": [[250, 240, 230], [30, 67, 94], "linen"],
    "FAFAD2": [[250, 250, 210], [60, 80, 90], "lightGoldenrodYellow"],
    "FDF5E6": [[253, 245, 230], [39, 85, 95], "oldLace"],
    "FF0000": [[255, 0, 0], [0, 100, 50], "red"],
    "FF00FF": [[255, 0, 255], [300, 100, 50], "magenta"],
    "FF1493": [[255, 20, 147], [328, 100, 54], "deepPink"],
    "FF4500": [[255, 69, 0], [16, 100, 50], "orangeRed"],
    "FF6347": [[255, 99, 71], [9, 100, 64], "tomato"],
    "FF69B4": [[255, 105, 180], [330, 100, 71], "hotPink"],
    "FF7F50": [[255, 127, 80], [16, 100, 66], "coral"],
    "FF8C00": [[255, 140, 0], [33, 100, 50], "darkOrange"],
    "FFA07A": [[255, 160, 122], [17, 100, 74], "lightSalmon"],
    "FFA500": [[255, 165, 0], [39, 100, 50], "orange"],
    "FFB6C1": [[255, 182, 193], [351, 100, 86], "lightPink"],
    "FFC0CB": [[255, 192, 203], [350, 100, 88], "pink"],
    "FFD700": [[255, 215, 0], [51, 100, 50], "gold"],
    "FFDAB9": [[255, 218, 185], [28, 100, 86], "peachPuff"],
    "FFDEAD": [[255, 222, 173], [36, 100, 84], "navajoWhite"],
    "FFE4B5": [[255, 228, 181], [38, 100, 85], "moccasin"],
    "FFE4C4": [[255, 228, 196], [33, 100, 88], "bisque"],
    "FFE4E1": [[255, 228, 225], [6, 100, 94], "mistyRose"],
    "FFEBCD": [[255, 235, 205], [36, 100, 90], "blanchedAlmond"],
    "FFEFD5": [[255, 239, 213], [37, 100, 92], "papayaWhip"],
    "FFF0F5": [[255, 240, 245], [340, 100, 97], "lavenderBlush"],
    "FFF5EE": [[255, 245, 238], [25, 100, 97], "seaShell"],
    "FFF8DC": [[255, 248, 220], [48, 100, 93], "cornsilk"],
    "FFFACD": [[255, 250, 205], [54, 100, 90], "lemonChiffon"],
    "FFFAF0": [[255, 250, 240], [40, 100, 97], "floralWhite"],
    "FFFAFA": [[255, 250, 250], [0, 100, 99], "snow"],
    "FFFF00": [[255, 255, 0], [60, 100, 50], "yellow"],
    "FFFFE0": [[255, 255, 224], [60, 100, 94], "lightYellow"],
    "FFFFF0": [[255, 255, 240], [60, 100, 97], "ivory"],
    "FFFFFF": [[255, 255, 255], [0, 100, 100], "white"]
};

// Simple Values
WebInspector.Color.Nicknames = {
    "aliceblue": "F0F8FF",
    "antiquewhite": "FAEBD7",
    "aqua": "00FFFF",
    "aquamarine": "7FFFD4",
    "azure": "F0FFFF",
    "beige": "F5F5DC",
    "bisque": "FFE4C4",
    "black": "000000",
    "blanchedalmond": "FFEBCD",
    "blue": "0000FF",
    "blueviolet": "8A2BE2",
    "brown": "A52A2A",
    "burlywood": "DEB887",
    "cadetblue": "5F9EA0",
    "chartreuse": "7FFF00",
    "chocolate": "D2691E",
    "coral": "FF7F50",
    "cornflowerblue": "6495ED",
    "cornsilk": "FFF8DC",
    "crimson": "DC143C",
    "cyan": "00FFFF",
    "darkblue": "00008B",
    "darkcyan": "008B8B",
    "darkgoldenrod": "B8860B",
    "darkgray": "A9A9A9",
    "darkgreen": "006400",
    "darkkhaki": "BDB76B",
    "darkmagenta": "8B008B",
    "darkolivegreen": "556B2F",
    "darkorange": "FF8C00",
    "darkorchid": "9932CC",
    "darkred": "8B0000",
    "darksalmon": "E9967A",
    "darkseagreen": "8FBC8F",
    "darkslateblue": "483D8B",
    "darkslategray": "2F4F4F",
    "darkturquoise": "00CED1",
    "darkviolet": "9400D3",
    "deeppink": "FF1493",
    "deepskyblue": "00BFFF",
    "dimgray": "696969",
    "dodgerblue": "1E90FF",
    "firebrick": "B22222",
    "floralwhite": "FFFAF0",
    "forestgreen": "228B22",
    "fuchsia": "FF00FF",
    "gainsboro": "DCDCDC",
    "ghostwhite": "F8F8FF",
    "gold": "FFD700",
    "goldenrod": "DAA520",
    "gray": "808080",
    "green": "008000",
    "greenyellow": "ADFF2F",
    "honeydew": "F0FFF0",
    "hotpink": "FF69B4",
    "indianred": "CD5C5C",
    "indigo": "4B0082",
    "ivory": "FFFFF0",
    "khaki": "F0E68C",
    "lavender": "E6E6FA",
    "lavenderblush": "FFF0F5",
    "lawngreen": "7CFC00",
    "lemonchiffon": "FFFACD",
    "lightblue": "ADD8E6",
    "lightcoral": "F08080",
    "lightcyan": "E0FFFF",
    "lightgoldenrodyellow": "FAFAD2",
    "lightgreen": "90EE90",
    "lightgrey": "D3D3D3",
    "lightpink": "FFB6C1",
    "lightsalmon": "FFA07A",
    "lightseagreen": "20B2AA",
    "lightskyblue": "87CEFA",
    "lightslategray": "778899",
    "lightsteelblue": "B0C4DE",
    "lightyellow": "FFFFE0",
    "lime": "00FF00",
    "limegreen": "32CD32",
    "linen": "FAF0E6",
    "magenta": "FF00FF",
    "maroon": "800000",
    "mediumaquamarine": "66CDAA",
    "mediumblue": "0000CD",
    "mediumorchid": "BA55D3",
    "mediumpurple": "9370D8",
    "mediumseagreen": "3CB371",
    "mediumslateblue": "7B68EE",
    "mediumspringgreen": "00FA9A",
    "mediumturquoise": "48D1CC",
    "mediumvioletred": "C71585",
    "midnightblue": "191970",
    "mintcream": "F5FFFA",
    "mistyrose": "FFE4E1",
    "moccasin": "FFE4B5",
    "navajowhite": "FFDEAD",
    "navy": "000080",
    "oldlace": "FDF5E6",
    "olive": "808000",
    "olivedrab": "6B8E23",
    "orange": "FFA500",
    "orangered": "FF4500",
    "orchid": "DA70D6",
    "palegoldenrod": "EEE8AA",
    "palegreen": "98FB98",
    "paleturquoise": "AFEEEE",
    "palevioletred": "D87093",
    "papayawhip": "FFEFD5",
    "peachpuff": "FFDAB9",
    "peru": "CD853F",
    "pink": "FFC0CB",
    "plum": "DDA0DD",
    "powderblue": "B0E0E6",
    "purple": "800080",
    "red": "FF0000",
    "rosybrown": "BC8F8F",
    "royalblue": "4169E1",
    "saddlebrown": "8B4513",
    "salmon": "FA8072",
    "sandybrown": "F4A460",
    "seagreen": "2E8B57",
    "seashell": "FFF5EE",
    "sienna": "A0522D",
    "silver": "C0C0C0",
    "skyblue": "87CEEB",
    "slateblue": "6A5ACD",
    "slategray": "708090",
    "snow": "FFFAFA",
    "springgreen": "00FF7F",
    "steelblue": "4682B4",
    "tan": "D2B48C",
    "teal": "008080",
    "thistle": "D8BFD8",
    "tomato": "FF6347",
    "turquoise": "40E0D0",
    "violet": "EE82EE",
    "wheat": "F5DEB3",
    "white": "FFFFFF",
    "whitesmoke": "F5F5F5",
    "yellow": "FFFF00",
    "yellowgreen": "9ACD32"
};

// Advanced Values [rgba, hsla, nickname]
WebInspector.Color.AdvancedNickNames = {
    "transparent": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
    "rgba(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
    "hsla(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
};

/* CSSCompletions.js */

WebInspector.CSSCompletions = [];

WebInspector.CSSCompletions.startsWith = function(prefix)
{
    var firstIndex = this._firstIndexOfPrefix(prefix);
    if (firstIndex === -1)
        return [];

    var results = [];
    while (this[firstIndex].indexOf(prefix) === 0)
        results.push(this[firstIndex++]);
    return results;
}

WebInspector.CSSCompletions.firstStartsWith = function(prefix)
{
    var foundIndex = this._firstIndexOfPrefix(prefix);
    return (foundIndex === -1 ? "" : this[foundIndex]);
}

WebInspector.CSSCompletions._firstIndexOfPrefix = function(prefix)
{
    if (!prefix)
        return -1;
    if (!this.length)
        return -1;

    var maxIndex = this.length - 1;
    var minIndex = 0;
    var foundIndex;

    do {
        var middleIndex = (maxIndex + minIndex) >> 1;
        if (this[middleIndex].indexOf(prefix) === 0) {
            foundIndex = middleIndex;
            break;
        }
        if (this[middleIndex] < prefix)
            minIndex = middleIndex + 1;
        else
            maxIndex = middleIndex - 1;
    } while (minIndex <= maxIndex);

    if (!foundIndex)
        return -1;

    while (foundIndex && this[foundIndex - 1].indexOf(prefix) === 0)
        foundIndex--;

    return foundIndex;
}

WebInspector.CSSCompletions.next = function(str, prefix)
{
    return WebInspector.CSSCompletions._closest(str, prefix, 1);
}

WebInspector.CSSCompletions.previous = function(str, prefix)
{
    return WebInspector.CSSCompletions._closest(str, prefix, -1);
}

WebInspector.CSSCompletions._closest = function(str, prefix, shift)
{
    if (!str)
        return "";

    var index = this.indexOf(str);
    if (index === -1)
        return "";

    if (!prefix) {
        index = (index + this.length + shift) % this.length;
        return this[index];
    }

    var propertiesWithPrefix = this.startsWith(prefix);
    var j = propertiesWithPrefix.indexOf(str);
    j = (j + propertiesWithPrefix.length + shift) % propertiesWithPrefix.length;
    return propertiesWithPrefix[j];
}

WebInspector.CSSCompletions._load = function(properties)
{
    for (var i = 0; i < properties.length; ++i)
        WebInspector.CSSCompletions.push(properties[i]);
    WebInspector.CSSCompletions.sort();
}

/* StylesSidebarPane.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StylesSidebarPane = function(computedStylePane)
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));

    this.settingsSelectElement = document.createElement("select");

    var option = document.createElement("option");
    option.value = "hex";
    option.action = this._changeColorFormat.bind(this);
    option.label = WebInspector.UIString("Hex Colors");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "rgb";
    option.action = this._changeColorFormat.bind(this);
    option.label = WebInspector.UIString("RGB Colors");
    this.settingsSelectElement.appendChild(option);

    option = document.createElement("option");
    option.value = "hsl";
    option.action = this._changeColorFormat.bind(this);
    option.label = WebInspector.UIString("HSL Colors");
    this.settingsSelectElement.appendChild(option);

    this.settingsSelectElement.appendChild(document.createElement("hr"));

    option = document.createElement("option");
    option.action = this._createNewRule.bind(this);
    option.label = WebInspector.UIString("New Style Rule");
    this.settingsSelectElement.appendChild(option);

    this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);
    WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);

    this.titleElement.appendChild(this.settingsSelectElement);
    this._computedStylePane = computedStylePane;
    this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
}

// Taken from http://www.w3.org/TR/CSS21/propidx.html.
WebInspector.StylesSidebarPane.InheritedProperties = [
    "azimuth", "border-collapse", "border-spacing", "caption-side", "color", "cursor", "direction", "elevation",
    "empty-cells", "font-family", "font-size", "font-style", "font-variant", "font-weight", "font", "letter-spacing",
    "line-height", "list-style-image", "list-style-position", "list-style-type", "list-style", "orphans", "pitch-range",
    "pitch", "quotes", "richness", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress",
    "text-align", "text-indent", "text-transform", "text-shadow", "visibility", "voice-family", "volume", "white-space", "widows", "word-spacing"
].keySet();

// Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
// First item is empty due to its artificial NOPSEUDO nature in the enum.
// FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
// runtime.
WebInspector.StylesSidebarPane.PseudoIdNames = [
    "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button",
    "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration",
    "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel",
    "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline",
    "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider",
    "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display",
    "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-seek-back-button", "-webkit-media-controls-seek-forward-button",
    "-webkit-media-controls-fullscreen-button", "-webkit-media-controls-rewind-button", "-webkit-media-controls-return-to-realtime-button",
    "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb",
    "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner",
    "-webkit-resizer", "-webkit-input-list-button", "-webkit-inner-spin-button", "-webkit-outer-spin-button"
];

WebInspector.StylesSidebarPane.prototype = {
    _settingsLoaded: function()
    {
        var format = WebInspector.applicationSettings.colorFormat;
        if (format === "hex")
            this.settingsSelectElement[0].selected = true;
        if (format === "rgb")
            this.settingsSelectElement[1].selected = true;
        if (format === "hsl")
            this.settingsSelectElement[2].selected = true;
    },

    _contextMenuEventFired: function(event)
    {
        var href = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link");
        if (href) {
            var contextMenu = new WebInspector.ContextMenu();
            var filled = WebInspector.panels.elements.populateHrefContextMenu(contextMenu, event, href);
            if (filled)
                contextMenu.show(event);
        }
    },

    update: function(node, editedSection, forceUpdate)
    {
        var refresh = false;

        if (forceUpdate)
            delete this.node;

        if (!forceUpdate && (!node || node === this.node))
            refresh = true;

        if (node && node.nodeType === Node.TEXT_NODE && node.parentNode)
            node = node.parentNode;

        if (node && node.nodeType !== Node.ELEMENT_NODE)
            node = null;

        if (node)
            this.node = node;
        else
            node = this.node;

        if (!node) {
            this.bodyElement.removeChildren();
            this._computedStylePane.bodyElement.removeChildren();
            this.sections = {};
            return;
        }

        function stylesCallback(styles)
        {
            if (styles)
                this._rebuildUpdate(node, styles);
        }

        function computedStyleCallback(computedStyle)
        {
            if (computedStyle)
                this._refreshUpdate(node, computedStyle, editedSection);
        }

        if (refresh)
            WebInspector.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
        else
            WebInspector.cssModel.getStylesAsync(node.id, !WebInspector.applicationSettings.showUserAgentStyles, stylesCallback.bind(this));
    },

    _refreshUpdate: function(node, computedStyle, editedSection)
    {
        for (var pseudoId in this.sections) {
            var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
            var usedProperties = {};
            var disabledComputedProperties = {};
            this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
            this._refreshSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, editedSection);
        }
        // Trace the computed style.
        this.sections[0][0].rebuildComputedTrace(this.sections[0]);
    },

    _rebuildUpdate: function(node, styles)
    {
        this.bodyElement.removeChildren();
        this._computedStylePane.bodyElement.removeChildren();

        var styleRules = this._rebuildStyleRules(node, styles);
        var usedProperties = {};
        var disabledComputedProperties = {};
        this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
        this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, 0);
        var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
        // Trace the computed style.
        this.sections[0][0].rebuildComputedTrace(this.sections[0]);

        for (var i = 0; i < styles.pseudoElements.length; ++i) {
            var pseudoElementCSSRules = styles.pseudoElements[i];

            styleRules = [];
            var pseudoId = pseudoElementCSSRules.pseudoId;

            var entry = { isStyleSeparator: true, pseudoId: pseudoId };
            styleRules.push(entry);

            // Add rules in reverse order to match the cascade order.
            for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
                var rule = WebInspector.CSSStyleDeclaration.parseRule(pseudoElementCSSRules.rules[j]);
                styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule });
            }
            usedProperties = {};
            disabledComputedProperties = {};
            this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties);
            this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement);
        }
    },

    _refreshStyleRules: function(sections, computedStyle)
    {
        var nodeComputedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
        var styleRules = [];
        for (var i = 0; sections && i < sections.length; ++i) {
            var section = sections[i];
            if (section instanceof WebInspector.BlankStylePropertiesSection)
                continue;
            if (section.computedStyle)
                section.styleRule.style = nodeComputedStyle;
            var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule };
            styleRules.push(styleRule);
        }
        return styleRules;
    },

    _rebuildStyleRules: function(node, styles)
    {
        var nodeComputedStyle = new WebInspector.CSSStyleDeclaration(styles.computedStyle);
        this.sections = {};

        var styleRules = [];

        styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });

        var styleAttributes = {};
        for (var name in styles.styleAttributes) {
            var attrStyle = { style: new WebInspector.CSSStyleDeclaration(styles.styleAttributes[name]), editable: false };
            attrStyle.selectorText = WebInspector.panels.elements.treeOutline.nodeNameToCorrectCase(node.nodeName) + "[" + name;
            if (node.getAttribute(name))
                attrStyle.selectorText += "=" + node.getAttribute(name);
            attrStyle.selectorText += "]";
            styleRules.push(attrStyle);
        }

        // Show element's Style Attributes
        if (styles.inlineStyle && node.nodeType === Node.ELEMENT_NODE) {
            var inlineStyle = { selectorText: "element.style", style: new WebInspector.CSSStyleDeclaration(styles.inlineStyle), isAttribute: true };
            styleRules.push(inlineStyle);
        }

        // Add rules in reverse order to match the cascade order.
        if (styles.matchedCSSRules.length)
            styleRules.push({ isStyleSeparator: true, text: WebInspector.UIString("Matched CSS Rules") });
        for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
            var rule = WebInspector.CSSStyleDeclaration.parseRule(styles.matchedCSSRules[i]);
            styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule });
        }

        // Walk the node structure and identify styles with inherited properties.
        var parentStyles = styles.parent;
        var parentNode = node.parentNode;
        function insertInheritedNodeSeparator(node)
        {
            var entry = {};
            entry.isStyleSeparator = true;
            entry.node = node;
            styleRules.push(entry);
        }

        while (parentStyles) {
            var separatorInserted = false;
            if (parentStyles.inlineStyle) {
                if (this._containsInherited(parentStyles.inlineStyle)) {
                    var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: new WebInspector.CSSStyleDeclaration(parentStyles.inlineStyle), isAttribute: true, isInherited: true };
                    if (!separatorInserted) {
                        insertInheritedNodeSeparator(parentNode);
                        separatorInserted = true;
                    }
                    styleRules.push(inlineStyle);
                }
            }

            for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
                var rulePayload = parentStyles.matchedCSSRules[i];
                if (!this._containsInherited(rulePayload.style))
                    continue;
                var rule = WebInspector.CSSStyleDeclaration.parseRule(rulePayload);
                if (!separatorInserted) {
                    insertInheritedNodeSeparator(parentNode);
                    separatorInserted = true;
                }
                styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule, isInherited: true });
            }
            parentStyles = parentStyles.parent;
            parentNode = parentNode.parentNode;
        }
        return styleRules;
    },

    _markUsedProperties: function(styleRules, usedProperties, disabledComputedProperties)
    {
        function deleteDisabledProperty(style, name)
        {
            if (!style || !name)
                return;
            if (style.__disabledPropertyValues)
                delete style.__disabledPropertyValues[name];
            if (style.__disabledPropertyPriorities)
                delete style.__disabledPropertyPriorities[name];
            if (style.__disabledProperties)
                delete style.__disabledProperties[name];
        }

        var priorityUsed = false;

        // Walk the style rules and make a list of all used and overloaded properties.
        for (var i = 0; i < styleRules.length; ++i) {
            var styleRule = styleRules[i];
            if (styleRule.computedStyle || styleRule.isStyleSeparator)
                continue;
            if (styleRule.section && styleRule.section.noAffect)
                continue;

            styleRule.usedProperties = {};

            var style = styleRule.style;
            for (var j = 0; j < style.length; ++j) {
                var name = style[j];

                if (!priorityUsed && style.getPropertyPriority(name).length)
                    priorityUsed = true;

                // If the property name is already used by another rule then this rule's
                // property is overloaded, so don't add it to the rule's usedProperties.
                if (!(name in usedProperties))
                    styleRule.usedProperties[name] = true;

                if (name === "font") {
                    // The font property is not reported as a shorthand. Report finding the individual
                    // properties so they are visible in computed style.
                    // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed.
                    styleRule.usedProperties["font-family"] = true;
                    styleRule.usedProperties["font-size"] = true;
                    styleRule.usedProperties["font-style"] = true;
                    styleRule.usedProperties["font-variant"] = true;
                    styleRule.usedProperties["font-weight"] = true;
                    styleRule.usedProperties["line-height"] = true;
                }

                // Delete any disabled properties, since the property does exist.
                // This prevents it from showing twice.
                deleteDisabledProperty(style, name);
                deleteDisabledProperty(style, style.getPropertyShorthand(name));
            }

            // Add all the properties found in this style to the used properties list.
            // Do this here so only future rules are affect by properties used in this rule.
            for (var name in styleRules[i].usedProperties)
                usedProperties[name] = true;

            // Remember all disabled properties so they show up in computed style.
            if (style.__disabledProperties)
                for (var name in style.__disabledProperties)
                    disabledComputedProperties[name] = true;
        }

        if (priorityUsed) {
            // Walk the properties again and account for !important.
            var foundPriorityProperties = [];

            // Walk in reverse to match the order !important overrides.
            for (var i = (styleRules.length - 1); i >= 0; --i) {
                if (styleRules[i].computedStyle || styleRules[i].isStyleSeparator)
                    continue;

                var style = styleRules[i].style;
                for (var j = 0; j < style.length; ++j) {
                    var name = style[j];
                    if (style.getPropertyPriority(name).length) {
                        if (!(name in foundPriorityProperties))
                            styleRules[i].usedProperties[name] = true;
                        else
                            delete styleRules[i].usedProperties[name];
                        foundPriorityProperties[name] = true;
                    } else if (name in foundPriorityProperties)
                        delete styleRules[i].usedProperties[name];
                }
            }
        }
    },

    _refreshSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, editedSection)
    {
        // Walk the style rules and update the sections with new overloaded and used properties.
        for (var i = 0; i < styleRules.length; ++i) {
            var styleRule = styleRules[i];
            var section = styleRule.section;
            if (styleRule.computedStyle) {
                section._disabledComputedProperties = disabledComputedProperties;
                section._usedProperties = usedProperties;
                section.update();
            } else {
                section._usedProperties = styleRule.usedProperties;
                section.update(section === editedSection);
            }
        }
    },

    _rebuildSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement)
    {
        // Make a property section for each style rule.
        var sections = [];
        var lastWasSeparator = true;
        for (var i = 0; i < styleRules.length; ++i) {
            var styleRule = styleRules[i];
            if (styleRule.isStyleSeparator) {
                var separatorElement = document.createElement("div");
                separatorElement.className = "styles-sidebar-separator";
                if (styleRule.node) {
                    var link = WebInspector.panels.elements.linkifyNodeReference(styleRule.node);
                    separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " "));
                    separatorElement.appendChild(link);
                    if (!sections.inheritedPropertiesSeparatorElement)
                        sections.inheritedPropertiesSeparatorElement = separatorElement;
                } else if ("pseudoId" in styleRule) {
                    var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
                    if (pseudoName)
                        separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
                    else
                        separatorElement.textContent = WebInspector.UIString("Pseudo element");
                } else
                    separatorElement.textContent = styleRule.text;
                this.bodyElement.insertBefore(separatorElement, anchorElement);
                lastWasSeparator = true;
                continue;
            }
            var computedStyle = styleRule.computedStyle;

            // Default editable to true if it was omitted.
            var editable = styleRule.editable;
            if (typeof editable === "undefined")
                editable = true;

            if (computedStyle)
                var section = new WebInspector.ComputedStylePropertiesSection(styleRule, usedProperties, disabledComputedProperties, styleRules);
            else
                var section = new WebInspector.StylePropertiesSection(styleRule, editable, styleRule.isInherited, lastWasSeparator);
            section.pane = this;
            section.expanded = true;

            if (computedStyle) {
                this._computedStylePane.bodyElement.appendChild(section.element);
                lastWasSeparator = true;
            } else {
                this.bodyElement.insertBefore(section.element, anchorElement);
                lastWasSeparator = false;
            }
            sections.push(section);
        }
        return sections;
    },

    _containsInherited: function(payload)
    {
        if (this._arrayContainsInheritedProperty(payload.properties))
            return true;
        if (payload.disabled && this._arrayContainsInheritedProperty(payload.disabled))
            return true;
        return false;
    },

    _arrayContainsInheritedProperty: function(properties)
    {
        for (var i = 0; i < properties.length; ++i) {
            var property = properties[i];
            // Does this style contain non-overridden inherited property?
            if (property.name in WebInspector.StylesSidebarPane.InheritedProperties)
                return true;
        }
        return false;
    },

    _changeSetting: function(event)
    {
        var options = this.settingsSelectElement.options;
        var selectedOption = options[this.settingsSelectElement.selectedIndex];
        selectedOption.action(event);

        // Select the correct color format setting again, since it needs to be selected.
        var selectedIndex = 0;
        for (var i = 0; i < options.length; ++i) {
            if (options[i].value === WebInspector.applicationSettings.colorFormat) {
                selectedIndex = i;
                break;
            }
        }

        this.settingsSelectElement.selectedIndex = selectedIndex;
    },

    _changeColorFormat: function(event)
    {
        var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
        WebInspector.applicationSettings.colorFormat = selectedOption.value;

        for (var pseudoId in this.sections) {
            var sections = this.sections[pseudoId];
            for (var i = 0; i < sections.length; ++i)
                sections[i].update(true);
        }
    },

    _createNewRule: function(event)
    {
        this.addBlankSection().startEditingSelector();
    },

    addBlankSection: function()
    {
        var blankSection = new WebInspector.BlankStylePropertiesSection(appropriateSelectorForNode(this.node, true));
        blankSection.pane = this;

        var elementStyleSection = this.sections[0][1];
        this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);

        this.sections[0].splice(2, 0, blankSection);

        return blankSection;
    },

    removeSection: function(section)
    {
        for (var pseudoId in this.sections) {
            var sections = this.sections[pseudoId];
            var index = sections.indexOf(section);
            if (index === -1)
                continue;
            sections.splice(index, 1);
            if (section.element.parentNode)
                section.element.parentNode.removeChild(section.element);
        }
    },

    registerShortcuts: function()
    {
        var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Styles Pane"));
        var shortcut = WebInspector.KeyboardShortcut;
        var keys = [
            shortcut.shortcutToString(shortcut.Keys.Tab),
            shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Next/previous property"));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.Up),
            shortcut.shortcutToString(shortcut.Keys.Down)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement value"));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.Up, shortcut.Modifiers.Shift),
            shortcut.shortcutToString(shortcut.Keys.Down, shortcut.Modifiers.Shift)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.PageUp),
            shortcut.shortcutToString(shortcut.Keys.PageDown)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Shift),
            shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Shift)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 100));
        keys = [
            shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Alt),
            shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Alt)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 0.1));
    }
}

WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.ComputedStyleSidebarPane = function()
{
    WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
    var showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited"), "sidebar-pane-subtitle");
    this.titleElement.appendChild(showInheritedCheckbox.element);

    function settingsLoaded()
    {
        if (WebInspector.applicationSettings.showInheritedComputedStyleProperties) {
            this.bodyElement.addStyleClass("show-inherited");
            showInheritedCheckbox.checked = true;
        }
    }

    WebInspector.applicationSettings.addEventListener("loaded", settingsLoaded.bind(this));

    function showInheritedToggleFunction(event)
    {
        WebInspector.applicationSettings.showInheritedComputedStyleProperties = showInheritedCheckbox.checked;
        if (WebInspector.applicationSettings.showInheritedComputedStyleProperties)
            this.bodyElement.addStyleClass("show-inherited");
        else
            this.bodyElement.removeStyleClass("show-inherited");
    }

    showInheritedCheckbox.addEventListener(showInheritedToggleFunction.bind(this));
}

WebInspector.ComputedStyleSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

WebInspector.StylePropertiesSection = function(styleRule, editable, isInherited, isFirstSection)
{
    WebInspector.PropertiesSection.call(this, "");
    this.element.className = "styles-section monospace" + (isFirstSection ? " first-styles-section" : "");

    this._selectorElement = document.createElement("span");
    this._selectorElement.textContent = styleRule.selectorText;
    this.titleElement.appendChild(this._selectorElement);

    var openBrace = document.createElement("span");
    openBrace.textContent = " {";
    this.titleElement.appendChild(openBrace);

    var closeBrace = document.createElement("div");
    closeBrace.textContent = "}";
    this.element.appendChild(closeBrace);

    this._selectorElement.addEventListener("dblclick", this._handleSelectorDoubleClick.bind(this), false);
    this.element.addEventListener("dblclick", this._handleEmptySpaceDoubleClick.bind(this), false);

    this.styleRule = styleRule;
    this.rule = this.styleRule.rule;
    this.editable = editable;
    this.isInherited = isInherited;

    // Prevent editing the user agent and user rules.
    var isUserAgent = this.rule && this.rule.isUserAgent;
    var isUser = this.rule && this.rule.isUser;
    var isViaInspector = this.rule && this.rule.isViaInspector;

    if (isUserAgent || isUser)
        this.editable = false;

    this._usedProperties = styleRule.usedProperties;

    if (this.rule)
        this.titleElement.addStyleClass("styles-selector");

    function linkifyUncopyable(url, line)
    {
        var link = WebInspector.linkifyResourceAsNode(url, "resources", line + 1);
        link.setAttribute("data-uncopyable", link.textContent);
        link.textContent = "";
        return link;
    }

    var subtitle = "";
    if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href)
        this.subtitleElement.appendChild(linkifyUncopyable(this.styleRule.parentStyleSheet.href, this.rule.sourceLine));
    else if (isUserAgent)
        subtitle = WebInspector.UIString("user agent stylesheet");
    else if (isUser)
        subtitle = WebInspector.UIString("user stylesheet");
    else if (isViaInspector)
        subtitle = WebInspector.UIString("via inspector");
    else if (this.rule && this.rule.documentURL)
        this.subtitleElement.appendChild(linkifyUncopyable(this.rule.documentURL, this.rule.sourceLine));

    if (isInherited)
        this.element.addStyleClass("show-inherited"); // This one is related to inherited rules, not compted style.
    if (subtitle)
        this.subtitle = subtitle;

    this.identifier = styleRule.selectorText;
    if (this.subtitle)
        this.identifier += ":" + this.subtitle;

    if (!this.editable)
        this.element.addStyleClass("read-only");
}

WebInspector.StylePropertiesSection.prototype = {
    collapse: function(dontRememberState)
    {
        // Overriding with empty body.
    },

    isPropertyInherited: function(property)
    {
        if (this.isInherited) {
            // While rendering inherited stylesheet, reverse meaning of this property.
            // Render truly inherited properties with black, i.e. return them as non-inherited.
            return !(property in WebInspector.StylesSidebarPane.InheritedProperties);
        }
        return false;
    },

    isPropertyOverloaded: function(property, shorthand)
    {
        if (!this._usedProperties || this.noAffect)
            return false;

        if (this.isInherited && !(property in WebInspector.StylesSidebarPane.InheritedProperties)) {
            // In the inherited sections, only show overrides for the potentially inherited properties.
            return false;
        }

        var used = (property in this._usedProperties);
        if (used || !shorthand)
            return !used;

        // Find out if any of the individual longhand properties of the shorthand
        // are used, if none are then the shorthand is overloaded too.
        var longhandProperties = this.styleRule.style.getLonghandProperties(property);
        for (var j = 0; j < longhandProperties.length; ++j) {
            var individualProperty = longhandProperties[j];
            if (individualProperty in this._usedProperties)
                return false;
        }

        return true;
    },

    isPropertyDisabled: function(property)
    {
        if (!this.styleRule.style.__disabledPropertyValues)
            return false;
        return property in this.styleRule.style.__disabledPropertyValues;
    },

    update: function(full)
    {
        if (full) {
            this.propertiesTreeOutline.removeChildren();
            this.populated = false;
        } else {
            var child = this.propertiesTreeOutline.children[0];
            while (child) {
                child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand);
                child = child.traverseNextTreeElement(false, null, true);
            }
        }
        this.afterUpdate();
    },

    afterUpdate: function()
    {
        if (this._afterUpdate) {
            this._afterUpdate(this);
            delete this._afterUpdate;
        }
    },

    onpopulate: function()
    {
        var style = this.styleRule.style;

        var foundShorthands = {};
        var disabledProperties = style.__disabledPropertyValues || {};

        this.uniqueProperties = [];
        for (var i = 0; i < style.length; ++i)
            this.uniqueProperties.push(style[i]);

        for (var name in disabledProperties)
            this.uniqueProperties.push(name);

        this.uniqueProperties.sort();

        for (var i = 0; i < this.uniqueProperties.length; ++i) {
            var name = this.uniqueProperties[i];
            var disabled = name in disabledProperties;
            var shorthand = !disabled ? style.getPropertyShorthand(name) : null;

            if (shorthand && shorthand in foundShorthands)
                continue;

            if (shorthand) {
                foundShorthands[shorthand] = true;
                name = shorthand;
            }

            var isShorthand = (shorthand ? true : false);
            var inherited = this.isPropertyInherited(name);
            var overloaded = this.isPropertyOverloaded(name, isShorthand);

            var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, isShorthand, inherited, overloaded, disabled);
            this.propertiesTreeOutline.appendChild(item);
        }
    },

    findTreeElementWithName: function(name)
    {
        var treeElement = this.propertiesTreeOutline.children[0];
        while (treeElement) {
            if (treeElement.name === name)
                return treeElement;
            treeElement = treeElement.traverseNextTreeElement(true, null, true);
        }
        return null;
    },

    addNewBlankProperty: function()
    {
        var item = new WebInspector.StylePropertyTreeElement(this.styleRule, this.styleRule.style, "", false, false, false, false);
        this.propertiesTreeOutline.appendChild(item);
        item.listItemElement.textContent = "";
        item._newProperty = true;
        return item;
    },

    _handleEmptySpaceDoubleClick: function(event)
    {
        if (event.target.hasStyleClass("header")) {
            event.stopPropagation();
            return;
        }
        this.expand();
        this.addNewBlankProperty().startEditing();
    },

    _handleSelectorClick: function(event)
    {
        event.stopPropagation();
    },

    _handleSelectorDoubleClick: function(event)
    {
        this._startEditingOnMouseEvent();
        event.stopPropagation();
    },

    _startEditingOnMouseEvent: function()
    {
        if (!this.editable)
            return;

        if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
            this.expand();
            this.addNewBlankProperty().startEditing();
            return;
        }

        if (!this.rule)
            return;

        this.startEditingSelector();
    },

    startEditingSelector: function()
    {
        var element = this._selectorElement;
        if (WebInspector.isBeingEdited(element))
            return;

        WebInspector.startEditing(this._selectorElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null);
        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    },

    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
    {
        function moveToNextIfNeeded() {
            if (!moveDirection || moveDirection !== "forward")
                return;

            this.expand();
            if (this.propertiesTreeOutline.children.length === 0)
                this.addNewBlankProperty().startEditing();
            else {
                var item = this.propertiesTreeOutline.children[0]
                item.startEditing(item.valueElement);
            }
        }

        if (newContent === oldContent)
            return moveToNextIfNeeded.call(this);

        var self = this;

        function successCallback(newRule, doesAffectSelectedNode)
        {
            if (!doesAffectSelectedNode) {
                self.noAffect = true;
                self.element.addStyleClass("no-affect");
            } else {
                delete self.noAffect;
                self.element.removeStyleClass("no-affect");
            }

            self.rule = newRule;
            self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, parentStyleSheet: newRule.parentStyleSheet, rule: newRule };

            var oldIdentifier = this.identifier;
            self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent;

            self.pane.update();

            WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent);

            moveToNextIfNeeded.call(self);
        }

        WebInspector.cssModel.setRuleSelector(this.rule.id, newContent, this.pane.node.id, successCallback, moveToNextIfNeeded.bind(this));
    },

    editingSelectorCancelled: function()
    {
        // Do nothing, this is overridden by BlankStylePropertiesSection.
    }
}

WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.ComputedStylePropertiesSection = function(styleRule, usedProperties, disabledComputedProperties)
{
    WebInspector.PropertiesSection.call(this, "");
    this.headerElement.addStyleClass("hidden");
    this.element.className = "styles-section monospace first-styles-section read-only computed-style";
    this.styleRule = styleRule;
    this._usedProperties = usedProperties;
    this._disabledComputedProperties = disabledComputedProperties;
    this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
    this.computedStyle = true;
    this._propertyTreeElements = {};
    this._expandedPropertyNames = {};
}

WebInspector.ComputedStylePropertiesSection.prototype = {
    collapse: function(dontRememberState)
    {
        // Overriding with empty body.
    },

    _isPropertyInherited: function(property)
    {
        return !(property in this._usedProperties) && !(property in this._alwaysShowComputedProperties) && !(property in this._disabledComputedProperties);
    },

    update: function()
    {
        this._expandedPropertyNames = {};
        for (var name in this._propertyTreeElements) {
            if (this._propertyTreeElements[name].expanded)
                this._expandedPropertyNames[name] = true;
        }
        this._propertyTreeElements = {};
        this.propertiesTreeOutline.removeChildren();
        this.populated = false;
    },

    onpopulate: function()
    {
        var style = this.styleRule.style;
        var uniqueProperties = [];
        for (var i = 0; i < style.length; ++i)
            uniqueProperties.push(style[i]);
        uniqueProperties.sort();

        this._propertyTreeElements = {};
        for (var i = 0; i < uniqueProperties.length; ++i) {
            var name = uniqueProperties[i];
            var inherited = this._isPropertyInherited(name);
            var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, false, inherited, false, false);
            this.propertiesTreeOutline.appendChild(item);
            this._propertyTreeElements[name] = item;
        }
    },

    rebuildComputedTrace: function(sections)
    {
        for (var i = 0; i < sections.length; ++i) {
            var section = sections[i];
            if (section.computedStyle || section instanceof WebInspector.BlankStylePropertiesSection)
                continue;

            for (var j = 0; j < section.uniqueProperties.length; ++j) {
                var name = section.uniqueProperties[j];
                if (section.isPropertyDisabled(name))
                    continue;
                if (section.isInherited && !(name in WebInspector.StylesSidebarPane.InheritedProperties))
                    continue;

                var treeElement = this._propertyTreeElements[name];
                if (treeElement) {
                    var selectorText = section.styleRule.selectorText;
                    var value = section.styleRule.style.getPropertyValue(name);
                    var title = "<span style='color: gray'>" + selectorText + "</span> - " + value;
                    var subtitle = " <span style='float:right'>" + section.subtitleElement.innerHTML + "</span>";
                    var childElement = new TreeElement(title + subtitle, null, false);
                    treeElement.appendChild(childElement);
                    if (section.isPropertyOverloaded(name))
                        childElement.listItemElement.addStyleClass("overloaded");
                }
            }
        }

        // Restore expanded state after update.
        for (var name in this._expandedPropertyNames) {
            if (name in this._propertyTreeElements)
                this._propertyTreeElements[name].expand();
        }
    }
}

WebInspector.ComputedStylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;

WebInspector.BlankStylePropertiesSection = function(defaultSelectorText)
{
    WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, true, false, false);
    this.element.addStyleClass("blank-section");
}

WebInspector.BlankStylePropertiesSection.prototype = {
    expand: function()
    {
        // Do nothing, blank sections are not expandable.
    },

    editingSelectorCommitted: function(element, newContent, oldContent, context)
    {
        var self = this;
        function successCallback(styleRule, doesSelectorAffectSelectedNode)
        {
            self.makeNormal(styleRule);

            if (!doesSelectorAffectSelectedNode) {
                self.noAffect = true;
                self.element.addStyleClass("no-affect");
            }

            self.subtitleElement.textContent = WebInspector.UIString("via inspector");
            self.expand();

            self.addNewBlankProperty().startEditing();
        }

        WebInspector.cssModel.addRule(this.pane.node.id, newContent, successCallback, this.editingSelectorCancelled.bind(this));
    },

    editingSelectorCancelled: function()
    {
        this.pane.removeSection(this);
    },

    makeNormal: function(styleRule)
    {
        this.element.removeStyleClass("blank-section");
        this.styleRule = styleRule;
        this.rule = styleRule.rule;
        this.identifier = styleRule.selectorText + ":via inspector";
        this.__proto__ = WebInspector.StylePropertiesSection.prototype;
    }
}

WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype;

WebInspector.StylePropertyTreeElement = function(styleRule, style, name, shorthand, inherited, overloaded, disabled)
{
    this._styleRule = styleRule;
    this.style = style;
    this.name = name;
    this.shorthand = shorthand;
    this._inherited = inherited;
    this._overloaded = overloaded;
    this._disabled = disabled;

    // Pass an empty title, the title gets made later in onattach.
    TreeElement.call(this, "", null, shorthand);
}

WebInspector.StylePropertyTreeElement.prototype = {
    get inherited()
    {
        return this._inherited;
    },

    set inherited(x)
    {
        if (x === this._inherited)
            return;
        this._inherited = x;
        this.updateState();
    },

    get overloaded()
    {
        return this._overloaded;
    },

    set overloaded(x)
    {
        if (x === this._overloaded)
            return;
        this._overloaded = x;
        this.updateState();
    },

    get disabled()
    {
        return this._disabled;
    },

    set disabled(x)
    {
        if (x === this._disabled)
            return;
        this._disabled = x;
        this.updateState();
    },

    get priority()
    {
        if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities)
            return this.style.__disabledPropertyPriorities[this.name];
        return (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name));
    },

    get value()
    {
        if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues)
            return this.style.__disabledPropertyValues[this.name];
        return (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name));
    },

    onattach: function()
    {
        this.updateTitle();
    },

    updateTitle: function()
    {
        var priority = this.priority;
        var value = this.value;

        if (priority && !priority.length)
            delete priority;
        if (priority)
            priority = "!" + priority;

        this.updateState();

        var enabledCheckboxElement = document.createElement("input");
        enabledCheckboxElement.className = "enabled-button";
        enabledCheckboxElement.type = "checkbox";
        enabledCheckboxElement.checked = !this.disabled;
        enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false);

        var nameElement = document.createElement("span");
        nameElement.className = "webkit-css-property";
        nameElement.textContent = this.name;
        this.nameElement = nameElement;

        var valueElement = document.createElement("span");
        valueElement.className = "value";
        this.valueElement = valueElement;

        if (value) {
            function processValue(regex, processor, nextProcessor, valueText)
            {
                var container = document.createDocumentFragment();

                var items = valueText.replace(regex, "\0$1\0").split("\0");
                for (var i = 0; i < items.length; ++i) {
                    if ((i % 2) === 0) {
                        if (nextProcessor)
                            container.appendChild(nextProcessor(items[i]));
                        else
                            container.appendChild(document.createTextNode(items[i]));
                    } else {
                        var processedNode = processor(items[i]);
                        if (processedNode)
                            container.appendChild(processedNode);
                    }
                }

                return container;
            }

            function linkifyURL(url)
            {
                var container = document.createDocumentFragment();
                container.appendChild(document.createTextNode("url("));
                container.appendChild(WebInspector.linkifyURLAsNode(url, url, null, (url in WebInspector.resourceURLMap)));
                container.appendChild(document.createTextNode(")"));
                return container;
            }

            function processColor(text)
            {
                try {
                    var color = new WebInspector.Color(text);
                } catch (e) {
                    return document.createTextNode(text);
                }

                var swatchElement = document.createElement("span");
                swatchElement.title = WebInspector.UIString("Click to change color format");
                swatchElement.className = "swatch";
                swatchElement.style.setProperty("background-color", text);

                swatchElement.addEventListener("click", changeColorDisplay, false);
                swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false);

                var format;
                if (Preferences.showColorNicknames && color.nickname)
                    format = "nickname";
                else if (WebInspector.applicationSettings.colorFormat === "rgb")
                    format = (color.simple ? "rgb" : "rgba");
                else if (WebInspector.applicationSettings.colorFormat === "hsl")
                    format = (color.simple ? "hsl" : "hsla");
                else if (color.simple)
                    format = (color.hasShortHex() ? "shorthex" : "hex");
                else
                    format = "rgba";

                var colorValueElement = document.createElement("span");
                colorValueElement.textContent = color.toString(format);

                function changeColorDisplay(event)
                {
                    switch (format) {
                        case "rgb":
                            format = "hsl";
                            break;

                        case "shorthex":
                            format = "hex";
                            break;

                        case "hex":
                            format = "rgb";
                            break;

                        case "nickname":
                            if (color.simple) {
                                if (color.hasShortHex())
                                    format = "shorthex";
                                else
                                    format = "hex";
                                break;
                            }

                            format = "rgba";
                            break;

                        case "hsl":
                            if (color.nickname)
                                format = "nickname";
                            else if (color.hasShortHex())
                                format = "shorthex";
                            else
                                format = "hex";
                            break;

                        case "rgba":
                            format = "hsla";
                            break;

                        case "hsla":
                            if (color.nickname)
                                format = "nickname";
                            else
                                format = "rgba";
                            break;
                    }

                    colorValueElement.textContent = color.toString(format);
                }

                var container = document.createDocumentFragment();
                container.appendChild(swatchElement);
                container.appendChild(colorValueElement);
                return container;
            }

            var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
            var colorProcessor = processValue.bind(window, colorRegex, processColor, null);

            valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value));
        }

        if (priority) {
            var priorityElement = document.createElement("span");
            priorityElement.className = "priority";
            priorityElement.textContent = priority;
        }

        this.listItemElement.removeChildren();

        if (!this.treeOutline)
            return;

        // Append the checkbox for root elements of an editable section.
        if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root)
            this.listItemElement.appendChild(enabledCheckboxElement);
        this.listItemElement.appendChild(nameElement);
        this.listItemElement.appendChild(document.createTextNode(": "));
        this.listItemElement.appendChild(valueElement);

        if (priorityElement) {
            this.listItemElement.appendChild(document.createTextNode(" "));
            this.listItemElement.appendChild(priorityElement);
        }

        this.listItemElement.appendChild(document.createTextNode(";"));

        this.tooltip = this.name + ": " + valueElement.textContent + (priority ? " " + priority : "");
    },

    updateAll: function(updateAllRules)
    {
        if (!this.treeOutline)
            return;
        if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane)
            this.treeOutline.section.pane.update(null, this.treeOutline.section);
        else if (this.treeOutline.section)
            this.treeOutline.section.update(true);
        else
            this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet.
    },

    toggleEnabled: function(event)
    {
        var disabled = !event.target.checked;

        var self = this;
        function callback(newStyle)
        {
            if (!newStyle)
                return;

            self.style = newStyle;
            self._styleRule.style = self.style;

            // Set the disabled property here, since the code above replies on it not changing
            // until after the value and priority are retrieved.
            self.disabled = disabled;

            if (self.treeOutline.section && self.treeOutline.section.pane)
                self.treeOutline.section.pane.dispatchEventToListeners("style property toggled");

            self.updateAll(true);
        }

        WebInspector.cssModel.toggleStyleEnabled(this.style.id, this.name, disabled, callback);
    },

    updateState: function()
    {
        if (!this.listItemElement)
            return;

        if (this.style.isPropertyImplicit(this.name) || this.value === "initial")
            this.listItemElement.addStyleClass("implicit");
        else
            this.listItemElement.removeStyleClass("implicit");

        if (this.inherited)
            this.listItemElement.addStyleClass("inherited");
        else
            this.listItemElement.removeStyleClass("inherited");

        if (this.overloaded)
            this.listItemElement.addStyleClass("overloaded");
        else
            this.listItemElement.removeStyleClass("overloaded");

        if (this.disabled)
            this.listItemElement.addStyleClass("disabled");
        else
            this.listItemElement.removeStyleClass("disabled");
    },

    onpopulate: function()
    {
        // Only populate once and if this property is a shorthand.
        if (this.children.length || !this.shorthand)
            return;

        var longhandProperties = this.style.getLonghandProperties(this.name);
        for (var i = 0; i < longhandProperties.length; ++i) {
            var name = longhandProperties[i];

            if (this.treeOutline.section) {
                var inherited = this.treeOutline.section.isPropertyInherited(name);
                var overloaded = this.treeOutline.section.isPropertyOverloaded(name);
            }

            var item = new WebInspector.StylePropertyTreeElement(this._styleRule, this.style, name, false, inherited, overloaded);
            this.appendChild(item);
        }
    },

    ondblclick: function(event)
    {
        this.startEditing(event.target);
        event.stopPropagation();
    },

    restoreNameElement: function()
    {
        // Restore <span class="webkit-css-property"> if it doesn't yet exist or was accidentally deleted.
        if (this.nameElement === this.listItemElement.querySelector(".webkit-css-property"))
            return;

        this.nameElement = document.createElement("span");
        this.nameElement.className = "webkit-css-property";
        this.nameElement.textContent = "";
        this.listItemElement.insertBefore(this.nameElement, this.listItemElement.firstChild);
    },

    startEditing: function(selectElement)
    {
        // FIXME: we don't allow editing of longhand properties under a shorthand right now.
        if (this.parent.shorthand)
            return;

        if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable))
            return;

        var context = {
            expanded: this.expanded,
            hasChildren: this.hasChildren,
            keyDownListener: this.editingKeyDown.bind(this),
            keyPressListener: this.editingKeyPress.bind(this)
        };

        // Lie about our children to prevent expanding on double click and to collapse shorthands.
        this.hasChildren = false;

        if (!selectElement)
            selectElement = this.listItemElement;

        this.listItemElement.addEventListener("keydown", context.keyDownListener, false);
        this.listItemElement.addEventListener("keypress", context.keyPressListener, false);

        WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
    },

    editingKeyPress: function(event)
    {
        var selection = window.getSelection();
        var colonIndex = this.listItemElement.textContent.indexOf(":");
        var selectionLeftOffset = event.target.selectionLeftOffset;

        if (colonIndex < 0 || selectionLeftOffset <= colonIndex) {
            // Complete property names.
            var character = event.data.toLowerCase();
            if (character && /[a-z-]/.test(character)) {
                var prefix = selection.anchorNode.textContent.substring(0, selection.anchorOffset);
                var property = WebInspector.CSSCompletions.firstStartsWith(prefix + character);

                if (!selection.isCollapsed)
                    selection.deleteFromDocument();

                this.restoreNameElement();

                if (property) {
                    if (property !== this.nameElement.textContent)
                        this.nameElement.textContent = property;
                    this.nameElement.firstChild.select(prefix.length + 1);
                    event.preventDefault();
                }
            }
        } else {
            // FIXME: This should complete property values.
        }
    },

    editingKeyDown: function(event)
    {
        var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
        var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
        if (!arrowKeyPressed && !pageKeyPressed)
            return;

        var selection = window.getSelection();
        if (!selection.rangeCount)
            return;

        var selectionRange = selection.getRangeAt(0);
        if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement))
            return;

        const styleValueDelimeters = " \t\n\"':;,/()";
        var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement);
        var wordString = wordRange.toString();
        var replacementString = wordString;

        var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString);
        if (matches && matches.length) {
            var prefix = matches[1];
            var number = parseFloat(matches[2]);
            var suffix = matches[3];

            // If the number is near zero or the number is one and the direction will take it near zero.
            var numberNearZero = (number < 1 && number > -1);
            if (number === 1 && event.keyIdentifier === "Down")
                numberNearZero = true;
            else if (number === -1 && event.keyIdentifier === "Up")
                numberNearZero = true;

            if (numberNearZero && event.altKey && arrowKeyPressed) {
                if (event.keyIdentifier === "Down")
                    number = Math.ceil(number - 1);
                else
                    number = Math.floor(number + 1);
            } else {
                // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down.
                // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
                var changeAmount = 1;
                if (event.shiftKey && pageKeyPressed)
                    changeAmount = 100;
                else if (event.shiftKey || pageKeyPressed)
                    changeAmount = 10;
                else if (event.altKey || numberNearZero)
                    changeAmount = 0.1;

                if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
                    changeAmount *= -1;

                // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
                // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
                number = Number((number + changeAmount).toFixed(6));
            }

            replacementString = prefix + number + suffix;
        } else if (selection.containsNode(this.nameElement, true)) {
            var prefix = selectionRange.startContainer.textContent.substring(0, selectionRange.startOffset);
            var property;

            if (event.keyIdentifier === "Up")
                property = WebInspector.CSSCompletions.previous(wordString, prefix);
            else if (event.keyIdentifier === "Down")
                property = WebInspector.CSSCompletions.next(wordString, prefix);

            var startOffset = selectionRange.startOffset;
            if (property) {
                this.nameElement.textContent = property;
                this.nameElement.firstChild.select(startOffset);
            }
            event.preventDefault();
            return;
        } else {
            // FIXME: this should cycle through known keywords for the current property value.
        }

        var replacementTextNode = document.createTextNode(replacementString);

        wordRange.deleteContents();
        wordRange.insertNode(replacementTextNode);

        var finalSelectionRange = document.createRange();
        finalSelectionRange.setStart(replacementTextNode, 0);
        finalSelectionRange.setEnd(replacementTextNode, replacementString.length);

        selection.removeAllRanges();
        selection.addRange(finalSelectionRange);

        event.preventDefault();

        if (!this.originalCSSText) {
            // Remember the rule's original CSS text, so it can be restored
            // if the editing is canceled and before each apply.
            this.originalCSSText = this.style.styleTextWithShorthands();
        } else {
            // Restore the original CSS text before applying user changes. This is needed to prevent
            // new properties from sticking around if the user adds one, then removes it.
            WebInspector.cssModel.setCSSText(this.style.id, this.originalCSSText);
        }

        this.applyStyleText(this.listItemElement.textContent);
    },

    editingEnded: function(context)
    {
        this.hasChildren = context.hasChildren;
        if (context.expanded)
            this.expand();
        this.listItemElement.removeEventListener("keydown", context.keyDownListener, false);
        this.listItemElement.removeEventListener("keypress", context.keyPressListener, false);
        delete this.originalCSSText;
    },

    editingCancelled: function(element, context)
    {
        if (this._newProperty)
            this.treeOutline.removeChild(this);
        else if (this.originalCSSText) {
            WebInspector.cssModel.setCSSText(this.style.id, this.originalCSSText);

            if (this.treeOutline.section && this.treeOutline.section.pane)
                this.treeOutline.section.pane.dispatchEventToListeners("style edited");

            this.updateAll();
        } else
            this.updateTitle();

        this.editingEnded(context);
    },

    editingCommitted: function(element, userInput, previousContent, context, moveDirection)
    {
        this.editingEnded(context);

        // Determine where to move to before making changes
        var newProperty, moveToPropertyName, moveToSelector;
        var moveTo = (moveDirection === "forward" ? this.nextSibling : this.previousSibling);
        if (moveTo)
            moveToPropertyName = moveTo.name;
        else if (moveDirection === "forward")
            newProperty = true;
        else if (moveDirection === "backward" && this.treeOutline.section.rule)
            moveToSelector = true;

        // Make the Changes and trigger the moveToNextCallback after updating
        var blankInput = /^\s*$/.test(userInput);
        if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank
            this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput);
            this.applyStyleText(userInput, true);
        } else
            moveToNextCallback(this._newProperty, false, this.treeOutline.section, false);

        // The Callback to start editing the next property
        function moveToNextCallback(alreadyNew, valueChanged, section)
        {
            if (!moveDirection)
                return;

            // User just tabbed through without changes
            if (moveTo && moveTo.parent) {
                moveTo.startEditing(moveTo.valueElement);
                return;
            }

            // User has made a change then tabbed, wiping all the original treeElements,
            // recalculate the new treeElement for the same property we were going to edit next
            if (moveTo && !moveTo.parent) {
                var treeElement = section.findTreeElementWithName(moveToPropertyName);
                if (treeElement)
                    treeElement.startEditing(treeElement.valueElement);
                return;
            }

            // Create a new attribute in this section
            if (newProperty) {
                if (alreadyNew && !valueChanged)
                    return;

                section.addNewBlankProperty().startEditing();
                return;
            }

            if (moveToSelector)
                section.startEditingSelector();
        }
    },

    applyStyleText: function(styleText, updateInterface)
    {
        var section = this.treeOutline.section;
        var elementsPanel = WebInspector.panels.elements;
        styleText = styleText.replace(/\s/g, " ").trim(); // replace &nbsp; with whitespace.
        var styleTextLength = styleText.length;
        if (!styleTextLength && updateInterface) {
            if (this._newProperty) {
                // The user deleted everything, so remove the tree element and update.
                this.parent.removeChild(this);
                section.afterUpdate();
                return;
            } else {
                delete section._afterUpdate;
            }
        }

        var self = this;

        function failureCallback()
        {
            // The user typed something, but it didn't parse. Just abort and restore
            // the original title for this property.  If this was a new attribute and
            // we couldn't parse, then just remove it.
            if (self._newProperty) {
                self.parent.removeChild(self);
                return;
            }
            if (updateInterface)
                self.updateTitle();
        }

        function successCallback(newStyle, changedProperties)
        {
            elementsPanel.removeStyleChange(section.identifier, self.style, self.name);

            if (!styleTextLength) {
                // Do remove ourselves from UI when the property removal is confirmed.
                self.parent.removeChild(self);
            } else {
                self.style = newStyle;
                for (var i = 0; i < changedProperties.length; ++i)
                    elementsPanel.addStyleChange(section.identifier, self.style, changedProperties[i]);
                self._styleRule.style = self.style;
            }

            if (section && section.pane)
                section.pane.dispatchEventToListeners("style edited");

            if (updateInterface)
                self.updateAll(true);
        }

        WebInspector.cssModel.applyStyleText(this.style.id, styleText, this.name, successCallback, failureCallback);
    }
}

WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype;

/* PanelEnablerView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.PanelEnablerView = function(identifier, headingText, disclaimerText, buttonTitle)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("panel-enabler-view");
    this.element.addStyleClass(identifier);

    this.contentElement = document.createElement("div");
    this.contentElement.className = "panel-enabler-view-content";
    this.element.appendChild(this.contentElement);

    this.imageElement = document.createElement("img");
    this.contentElement.appendChild(this.imageElement);

    this.choicesForm = document.createElement("form");
    this.contentElement.appendChild(this.choicesForm);

    this.headerElement = document.createElement("h1");
    this.headerElement.textContent = headingText;
    this.choicesForm.appendChild(this.headerElement);

    var self = this;
    function enableOption(text, checked) {
        var label = document.createElement("label");
        var option = document.createElement("input");
        option.type = "radio";
        option.name = "enable-option";
        if (checked)
            option.checked = true;
        label.appendChild(option);
        label.appendChild(document.createTextNode(text));
        self.choicesForm.appendChild(label);
        return option;
    };

    this.enabledForSession = enableOption(WebInspector.UIString("Only enable for this session"), true);
    this.enabledAlways = enableOption(WebInspector.UIString("Always enable"));

    this.disclaimerElement = document.createElement("div");
    this.disclaimerElement.className = "panel-enabler-disclaimer";
    this.disclaimerElement.textContent = disclaimerText;
    this.choicesForm.appendChild(this.disclaimerElement);

    this.enableButton = document.createElement("button");
    this.enableButton.setAttribute("type", "button");
    this.enableButton.textContent = buttonTitle;
    this.enableButton.addEventListener("click", this._enableButtonCicked.bind(this), false);
    this.choicesForm.appendChild(this.enableButton);
}

WebInspector.PanelEnablerView.prototype = {
    _enableButtonCicked: function()
    {
        this.dispatchEventToListeners("enable clicked");
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);

        setTimeout(this.resize.bind(this), 0);
    },

    resize: function()
    {
        this.imageElement.removeStyleClass("hidden");

        if (this.element.offsetWidth < (this.choicesForm.offsetWidth + this.imageElement.offsetWidth))
            this.imageElement.addStyleClass("hidden");
    },

    get alwaysEnabled() {
        return this.enabledAlways.checked;
    }
}

WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype;

/* WelcomeView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.WelcomeView = function(identifier, headingText, instructionsText)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("panel-enabler-view");
    this.element.addStyleClass(identifier);
    this.element.addStyleClass("welcome");

    this.contentElement = document.createElement("div");
    this.contentElement.className = "panel-enabler-view-content";
    this.element.appendChild(this.contentElement);

    this.alignerElement = document.createElement("div");
    this.alignerElement.className = "welcome-instructions-aligner";
    this.contentElement.appendChild(this.alignerElement);

    this.instructionsElement = document.createElement("div");
    this.instructionsElement.className = "instructions";
    this.contentElement.appendChild(this.instructionsElement);

    this.headerElement = document.createElement("h1");
    this.headerElement.textContent = headingText;
    this.instructionsElement.appendChild(this.headerElement);

    if (instructionsText)
        this.addMessage(instructionsText);
}

WebInspector.WelcomeView.prototype = {
    addMessage: function(message)
    {
        var messageElement = document.createElement("div");
        messageElement.className = "message";
        if (typeof message == "string")
            // Message text can contain <br> tags for better text balancing, so we
            // put it into elements using 'innerHTML', not 'textContent'.
            messageElement.innerHTML = message;
        else
            messageElement.appendChild(message);
        this.instructionsElement.appendChild(messageElement);
    }
}

WebInspector.WelcomeView.prototype.__proto__ = WebInspector.View.prototype;

/* StatusBarButton.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StatusBarButton = function(title, className, states)
{
    this.element = document.createElement("button");
    this.element.className = className + " status-bar-item";
    this.element.addEventListener("click", this._clicked.bind(this), false);

    this.glyph = document.createElement("div");
    this.glyph.className = "glyph";
    this.element.appendChild(this.glyph);

    this.glyphShadow = document.createElement("div");
    this.glyphShadow.className = "glyph shadow";
    this.element.appendChild(this.glyphShadow);
    
    this.states = states;
    if (!states)
        this.states = 2;

    if (states == 2)
        this._state = false;
    else
        this._state = 0;
    
    this.title = title;
    this.disabled = false;
    this._visible = true;
}

WebInspector.StatusBarButton.prototype = {
    _clicked: function()
    {
        this.dispatchEventToListeners("click");
    },

    get disabled()
    {
        return this._disabled;
    },

    set disabled(x)
    {
        if (this._disabled === x)
            return;
        this._disabled = x;
        this.element.disabled = x;
    },

    get title()
    {
        return this._title;
    },

    set title(x)
    {
        if (this._title === x)
            return;
        this._title = x;
        this.element.title = x;
    },
    
    get state()
    {
        return this._state;
    },
    
    set state(x)
    {
        if (this._state === x)
            return;
        
        if (this.states === 2) {
            if (x)
                this.element.addStyleClass("toggled-on");
            else
                this.element.removeStyleClass("toggled-on");
        } else {
            if (x !== 0) {
                this.element.removeStyleClass("toggled-" + this._state);
                this.element.addStyleClass("toggled-" + x);
            } else 
                this.element.removeStyleClass("toggled-" + this._state);
        }
        this._state = x;
    },

    get toggled()
    {
        if (this.states !== 2)
            throw("Only used toggled when there are 2 states, otherwise, use state");
        return this.state;
    },

    set toggled(x)
    {
        if (this.states !== 2)
            throw("Only used toggled when there are 2 states, otherwise, use state");
        this.state = x;
    },

    get visible()
    {
        return this._visible;
    },

    set visible(x)
    {
        if (this._visible === x)
            return;

        if (x)
            this.element.removeStyleClass("hidden");
        else
            this.element.addStyleClass("hidden");
        this._visible = x;
    }
}

WebInspector.StatusBarButton.prototype.__proto__ = WebInspector.Object.prototype;

/* SummaryBar.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SummaryBar = function(categories)
{
    this.categories = categories;

    this.element = document.createElement("div");
    this.element.className = "summary-bar";

    this.graphElement = document.createElement("canvas");
    this.graphElement.setAttribute("width", "450");
    this.graphElement.setAttribute("height", "38");
    this.graphElement.className = "summary-graph";
    this.element.appendChild(this.graphElement);

    this.legendElement = document.createElement("div");
    this.legendElement.className = "summary-graph-legend";
    this.element.appendChild(this.legendElement);
}

WebInspector.SummaryBar.prototype = {

    get calculator() {
        return this._calculator;
    },

    set calculator(x) {
        this._calculator = x;
    },

    reset: function()
    {
        this.legendElement.removeChildren();
        this._drawSummaryGraph();
    },

    update: function(data)
    {
        var graphInfo = this.calculator.computeSummaryValues(data);

        var fillSegments = [];

        this.legendElement.removeChildren();

        for (var category in this.categories) {
            var size = graphInfo.categoryValues[category];
            if (!size)
                continue;

            var colorString = this.categories[category].color;

            var fillSegment = {color: colorString, value: size};
            fillSegments.push(fillSegment);

            var legendLabel = this._makeLegendElement(this.categories[category].title, this.calculator.formatValue(size), colorString);
            this.legendElement.appendChild(legendLabel);
        }

        if (graphInfo.total) {
            var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
            totalLegendLabel.addStyleClass("total");
            this.legendElement.appendChild(totalLegendLabel);
        }

        this._drawSummaryGraph(fillSegments);
    },

    _drawSwatch: function(canvas, color)
    {
        var ctx = canvas.getContext("2d");

        function drawSwatchSquare() {
            ctx.fillStyle = color;
            ctx.fillRect(0, 0, 13, 13);

            var gradient = ctx.createLinearGradient(0, 0, 13, 13);
            gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
            gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 13, 13);

            gradient = ctx.createLinearGradient(13, 13, 0, 0);
            gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
            gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 13, 13);

            ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
            ctx.strokeRect(0.5, 0.5, 12, 12);
        }

        ctx.clearRect(0, 0, 13, 24);

        drawSwatchSquare();

        ctx.save();

        ctx.translate(0, 25);
        ctx.scale(1, -1);

        drawSwatchSquare();

        ctx.restore();

        this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
    },

    _drawSummaryGraph: function(segments)
    {
        if (!segments || !segments.length) {
            segments = [{color: "white", value: 1}];
            this._showingEmptySummaryGraph = true;
        } else
            delete this._showingEmptySummaryGraph;

        // Calculate the total of all segments.
        var total = 0;
        for (var i = 0; i < segments.length; ++i)
            total += segments[i].value;

        // Calculate the percentage of each segment, rounded to the nearest percent.
        var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });

        // Calculate the total percentage.
        var percentTotal = 0;
        for (var i = 0; i < percents.length; ++i)
            percentTotal += percents[i];

        // Make sure our percentage total is not greater-than 100, it can be greater
        // if we rounded up for a few segments.
        while (percentTotal > 100) {
            for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
                if (percents[i] > 1) {
                    --percents[i];
                    --percentTotal;
                }
            }
        }

        // Make sure our percentage total is not less-than 100, it can be less
        // if we rounded down for a few segments.
        while (percentTotal < 100) {
            for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
                ++percents[i];
                ++percentTotal;
            }
        }

        var ctx = this.graphElement.getContext("2d");

        var x = 0;
        var y = 0;
        var w = 450;
        var h = 19;
        var r = (h / 2);

        function drawPillShadow()
        {
            // This draws a line with a shadow that is offset away from the line. The line is stroked
            // twice with different X shadow offsets to give more feathered edges. Later we erase the
            // line with destination-out 100% transparent black, leaving only the shadow. This only
            // works if nothing has been drawn into the canvas yet.

            ctx.beginPath();
            ctx.moveTo(x + 4, y + h - 3 - 0.5);
            ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
            ctx.closePath();

            ctx.save();

            ctx.shadowBlur = 2;
            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
            ctx.shadowOffsetX = 3;
            ctx.shadowOffsetY = 5;

            ctx.strokeStyle = "white";
            ctx.lineWidth = 1;

            ctx.stroke();

            ctx.shadowOffsetX = -3;

            ctx.stroke();

            ctx.restore();

            ctx.save();

            ctx.globalCompositeOperation = "destination-out";
            ctx.strokeStyle = "rgba(0, 0, 0, 1)";
            ctx.lineWidth = 1;

            ctx.stroke();

            ctx.restore();
        }

        function drawPill()
        {
            // Make a rounded rect path.
            ctx.beginPath();
            ctx.moveTo(x, y + r);
            ctx.lineTo(x, y + h - r);
            ctx.arc(x + r, y + h - r, r, Math.PI, Math.PI / 2, true);
            ctx.lineTo(x + w - r, y + h);
            ctx.arc(x + w - r, y + h - r, r, Math.PI / 2, 0, true);
            ctx.lineTo(x + w, y + r);
            ctx.arc(x + w - r, y + r, r, 0, 3 * Math.PI / 2, true);
            ctx.lineTo(x + r, y);
            ctx.arc(x + r, y + r, r, Math.PI / 2, Math.PI, true);
            ctx.closePath();

            // Clip to the rounded rect path.
            ctx.save();
            ctx.clip();

            // Fill the segments with the associated color.
            var previousSegmentsWidth = 0;
            for (var i = 0; i < segments.length; ++i) {
                var segmentWidth = Math.round(w * percents[i] / 100);
                ctx.fillStyle = segments[i].color;
                ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
                previousSegmentsWidth += segmentWidth;
            }

            // Draw the segment divider lines.
            ctx.lineWidth = 1;
            for (var i = 1; i < 20; ++i) {
                ctx.beginPath();
                ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
                ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
                ctx.closePath();

                ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
                ctx.stroke();

                ctx.beginPath();
                ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
                ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
                ctx.closePath();

                ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
                ctx.stroke();
            }

            // Draw the pill shading.
            var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
            lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
            lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
            lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");

            var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
            darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
            darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
            darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");

            ctx.fillStyle = darkGradient;
            ctx.fillRect(x, y, w, h);

            ctx.fillStyle = lightGradient;
            ctx.fillRect(x, y, w, h);

            ctx.restore();
        }

        ctx.clearRect(x, y, w, (h * 2));

        drawPillShadow();
        drawPill();

        ctx.save();

        ctx.translate(0, (h * 2) + 1);
        ctx.scale(1, -1);

        drawPill();

        ctx.restore();

        this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
    },

    _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
    {
        ctx.save();

        var gradient = ctx.createLinearGradient(x, y, x, y + h);
        gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
        gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
        gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");

        ctx.globalCompositeOperation = "destination-out";

        ctx.fillStyle = gradient;
        ctx.fillRect(x, y, w, h);

        ctx.restore();
    },

    _makeLegendElement: function(label, value, color)
    {
        var legendElement = document.createElement("label");
        legendElement.className = "summary-graph-legend-item";

        if (color) {
            var swatch = document.createElement("canvas");
            swatch.className = "summary-graph-legend-swatch";
            swatch.setAttribute("width", "13");
            swatch.setAttribute("height", "24");

            legendElement.appendChild(swatch);

            this._drawSwatch(swatch, color);
        }

        var labelElement = document.createElement("div");
        labelElement.className = "summary-graph-legend-label";
        legendElement.appendChild(labelElement);

        var headerElement = document.createElement("div");
        headerElement.className = "summary-graph-legend-header";
        headerElement.textContent = label;
        labelElement.appendChild(headerElement);

        var valueElement = document.createElement("div");
        valueElement.className = "summary-graph-legend-value";
        valueElement.textContent = value;
        labelElement.appendChild(valueElement);

        return legendElement;
    }
}

WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype;

/* ElementsPanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ElementsPanel = function()
{
    WebInspector.Panel.call(this, "elements");

    this.contentElement = document.createElement("div");
    this.contentElement.id = "elements-content";
    this.contentElement.className = "outline-disclosure source-code";

    this.treeOutline = new WebInspector.ElementsTreeOutline();
    this.treeOutline.panel = this;
    this.treeOutline.includeRootDOMNode = false;
    this.treeOutline.selectEnabled = true;

    this.treeOutline.focusedNodeChanged = function(forceUpdate)
    {
        if (this.panel.visible && WebInspector.currentFocusElement !== document.getElementById("search"))
            WebInspector.currentFocusElement = this.element;

        this.panel.updateBreadcrumb(forceUpdate);

        for (var pane in this.panel.sidebarPanes)
           this.panel.sidebarPanes[pane].needsUpdate = true;

        this.panel.updateStyles(true);
        this.panel.updateMetrics();
        this.panel.updateProperties();
        this.panel.updateEventListeners();

        if (this._focusedDOMNode) {
            InspectorBackend.addInspectedNode(this._focusedDOMNode.id);
            WebInspector.extensionServer.notifyObjectSelected(this.panel.name, "DOMNode");
        }
    };

    this.contentElement.appendChild(this.treeOutline.element);

    this.crumbsElement = document.createElement("div");
    this.crumbsElement.className = "crumbs";
    this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
    this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);

    this.sidebarPanes = {};
    this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
    this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle);
    this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
    this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
    if (Preferences.nativeInstrumentationEnabled)
        this.sidebarPanes.domBreakpoints = WebInspector.createDOMBreakpointsSidebarPane();
    this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane();

    this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this);
    this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this);
    this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this);
    this.sidebarPanes.eventListeners.onexpand = this.updateEventListeners.bind(this);

    this.sidebarPanes.styles.expanded = true;

    this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
    this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
    this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);

    this.sidebarElement = document.createElement("div");
    this.sidebarElement.id = "elements-sidebar";

    for (var pane in this.sidebarPanes)
        this.sidebarElement.appendChild(this.sidebarPanes[pane].element);

    this.sidebarResizeElement = document.createElement("div");
    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
    this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false);

    this._nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item");
    this._nodeSearchButton.addEventListener("click", this.toggleSearchingForNode.bind(this), false);

    this.element.appendChild(this.contentElement);
    this.element.appendChild(this.sidebarElement);
    this.element.appendChild(this.sidebarResizeElement);

    this._registerShortcuts();
    this._changedStyles = {};

    this.reset();
}

WebInspector.ElementsPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Elements");
    },

    get statusBarItems()
    {
        return [this._nodeSearchButton.element, this.crumbsElement];
    },

    get defaultFocusedElement()
    {
        return this.treeOutline.element;
    },

    updateStatusBarItems: function()
    {
        this.updateBreadcrumbSizes();
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
        this.updateBreadcrumb();
        this.treeOutline.updateSelection();
        if (this.recentlyModifiedNodes.length)
            this.updateModifiedNodes();
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);

        WebInspector.highlightDOMNode(0);
        this.setSearchingForNode(false);
    },

    resize: function()
    {
        this.treeOutline.updateSelection();
        this.updateBreadcrumbSizes();
    },

    reset: function()
    {
        if (this.focusedDOMNode)
            this._selectedPathOnReset = this.focusedDOMNode.path();

        this.rootDOMNode = null;
        this.focusedDOMNode = null;

        WebInspector.highlightDOMNode(0);

        this.recentlyModifiedNodes = [];

        delete this.currentQuery;

        if (Preferences.nativeInstrumentationEnabled)
            this.sidebarPanes.domBreakpoints.reset();
    },

    setDocument: function(inspectedRootDocument)
    {
        this.reset();
        this.searchCanceled();

        if (!inspectedRootDocument)
            return;

        inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInserted.bind(this));
        inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this));
        inspectedRootDocument.addEventListener("DOMAttrModified", this._attributesUpdated.bind(this));
        inspectedRootDocument.addEventListener("DOMCharacterDataModified", this._characterDataModified.bind(this));

        this.rootDOMNode = inspectedRootDocument;

        function selectNode(candidateFocusNode)
        {
            if (!candidateFocusNode)
                candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;

            if (!candidateFocusNode)
                return;

            this.focusedDOMNode = candidateFocusNode;
            if (this.treeOutline.selectedTreeElement)
                this.treeOutline.selectedTreeElement.expand();
        }

        function selectLastSelectedNode(nodeId)
        {
            if (this.focusedDOMNode) {
                // Focused node has been explicitly set while reaching out for the last selected node.
                return;
            }
            var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : 0;
            selectNode.call(this, node);
        }

        if (this._selectedPathOnReset)
            InspectorBackend.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
        else
            selectNode.call(this);
        delete this._selectedPathOnReset;
    },

    searchCanceled: function()
    {
        delete this._searchQuery;
        this._hideSearchHighlights();

        WebInspector.updateSearchMatchesCount(0, this);

        this._currentSearchResultIndex = 0;
        this._searchResults = [];
        InspectorBackend.searchCanceled();
    },

    performSearch: function(query)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        const whitespaceTrimmedQuery = query.trim();
        if (!whitespaceTrimmedQuery.length)
            return;

        this._updatedMatchCountOnce = false;
        this._matchesCountUpdateTimeout = null;
        this._searchQuery = query;

        InspectorBackend.performSearch(whitespaceTrimmedQuery, false);
    },

    populateHrefContextMenu: function(contextMenu, event, anchorElement)
    {
        if (!anchorElement.href)
            return false;

        var resourceURL = WebInspector.resourceURLForRelatedNode(this.focusedDOMNode, anchorElement.href);
        if (!resourceURL)
            return false;

        // Add resource-related actions.
        // Keep these consistent with those added in WebInspector.StylesSidebarPane.prototype._populateHrefContextMenu().
        contextMenu.appendItem(WebInspector.UIString("Open Link in New Window"), WebInspector.openResource.bind(null, resourceURL, false));
        if (WebInspector.resourceForURL(resourceURL))
            contextMenu.appendItem(WebInspector.UIString("Open Link in Resources Panel"), WebInspector.openResource.bind(null, resourceURL, true));
        return true;
    },

    _updateMatchesCount: function()
    {
        WebInspector.updateSearchMatchesCount(this._searchResults.length, this);
        this._matchesCountUpdateTimeout = null;
        this._updatedMatchCountOnce = true;
    },

    _updateMatchesCountSoon: function()
    {
        if (!this._updatedMatchCountOnce)
            return this._updateMatchesCount();
        if (this._matchesCountUpdateTimeout)
            return;
        // Update the matches count every half-second so it doesn't feel twitchy.
        this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bind(this), 500);
    },

    addNodesToSearchResult: function(nodeIds)
    {
        if (!nodeIds.length)
            return;

        for (var i = 0; i < nodeIds.length; ++i) {
            var nodeId = nodeIds[i];
            var node = WebInspector.domAgent.nodeForId(nodeId);
            if (!node)
                continue;

            this._currentSearchResultIndex = 0;
            this._searchResults.push(node);
        }
        this._highlightCurrentSearchResult();
        this._updateMatchesCountSoon();
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;

        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this._highlightCurrentSearchResult();
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;

        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._highlightCurrentSearchResult();
    },

    _highlightCurrentSearchResult: function()
    {
        this._hideSearchHighlights();
        var node = this._searchResults[this._currentSearchResultIndex];
        var treeElement = this.treeOutline.findTreeElement(node);
        if (treeElement) {
            treeElement.highlightSearchResults(this._searchQuery);
            treeElement.reveal();
        }
    },

    _hideSearchHighlights: function(node)
    {
        for (var i = 0; this._searchResults && i < this._searchResults.length; ++i) {
            var node = this._searchResults[i];
            var treeElement = this.treeOutline.findTreeElement(node);
            if (treeElement)
                treeElement.highlightSearchResults(null);
        }
    },

    renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelector)
    {
        // TODO: Implement Shifting the oldSelector, and its contents to a newSelector
    },

    addStyleChange: function(identifier, style, property)
    {
        if (!style.parentRule)
            return;

        var selector = style.parentRule.selectorText;
        if (!this._changedStyles[identifier])
            this._changedStyles[identifier] = {};

        if (!this._changedStyles[identifier][selector])
            this._changedStyles[identifier][selector] = {};

        if (!this._changedStyles[identifier][selector][property])
            WebInspector.styleChanges += 1;

        this._changedStyles[identifier][selector][property] = style.getPropertyValue(property);
    },

    removeStyleChange: function(identifier, style, property)
    {
        if (!style.parentRule)
            return;

        var selector = style.parentRule.selectorText;
        if (!this._changedStyles[identifier] || !this._changedStyles[identifier][selector])
            return;

        if (this._changedStyles[identifier][selector][property]) {
            delete this._changedStyles[identifier][selector][property];
            WebInspector.styleChanges -= 1;
        }
    },

    generateStylesheet: function()
    {
        if (!WebInspector.styleChanges)
            return;

        // Merge Down to Just Selectors
        var mergedSelectors = {};
        for (var identifier in this._changedStyles) {
            for (var selector in this._changedStyles[identifier]) {
                if (!mergedSelectors[selector])
                    mergedSelectors[selector] = this._changedStyles[identifier][selector];
                else { // merge on selector
                    var merge = {};
                    for (var property in mergedSelectors[selector])
                        merge[property] = mergedSelectors[selector][property];
                    for (var property in this._changedStyles[identifier][selector]) {
                        if (!merge[property])
                            merge[property] = this._changedStyles[identifier][selector][property];
                        else { // merge on property within a selector, include comment to notify user
                            var value1 = merge[property];
                            var value2 = this._changedStyles[identifier][selector][property];

                            if (value1 === value2)
                                merge[property] = [value1];
                            else if (value1 instanceof Array)
                                merge[property].push(value2);
                            else
                                merge[property] = [value1, value2];
                        }
                    }
                    mergedSelectors[selector] = merge;
                }
            }
        }

        var builder = [];
        builder.push("/**");
        builder.push(" * Inspector Generated Stylesheet"); // UIString?
        builder.push(" */\n");

        var indent = "  ";
        function displayProperty(property, value, comment) {
            if (comment)
                return indent + "/* " + property + ": " + value + "; */";
            else
                return indent + property + ": " + value + ";";
        }

        for (var selector in mergedSelectors) {
            var psuedoStyle = mergedSelectors[selector];
            var properties = Object.properties(psuedoStyle);
            if (properties.length) {
                builder.push(selector + " {");
                for (var i = 0; i < properties.length; ++i) {
                    var property = properties[i];
                    var value = psuedoStyle[property];
                    if (!(value instanceof Array))
                        builder.push(displayProperty(property, value));
                    else {
                        if (value.length === 1)
                            builder.push(displayProperty(property, value) + " /* merged from equivalent edits */"); // UIString?
                        else {                        
                            builder.push(indent + "/* There was a Conflict... There were Multiple Edits for '" + property + "' */"); // UIString?
                            for (var j = 0; j < value.length; ++j)
                                builder.push(displayProperty(property, value, true));
                        }
                    }
                }
                builder.push("}\n");
            }
        }

        WebInspector.showConsole();
        WebInspector.console.addMessage(WebInspector.ConsoleMessage.createTextMessage(builder.join("\n")));
    },

    get rootDOMNode()
    {
        return this.treeOutline.rootDOMNode;
    },

    set rootDOMNode(x)
    {
        this.treeOutline.rootDOMNode = x;
    },

    get focusedDOMNode()
    {
        return this.treeOutline.focusedDOMNode;
    },

    set focusedDOMNode(x)
    {
        this.treeOutline.focusedDOMNode = x;
    },

    _attributesUpdated: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, updated: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _characterDataModified: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, updated: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _nodeInserted: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _nodeRemoved: function(event)
    {
        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true});
        if (this.visible)
            this._updateModifiedNodesSoon();
    },

    _updateModifiedNodesSoon: function()
    {
        if ("_updateModifiedNodesTimeout" in this)
            return;
        this._updateModifiedNodesTimeout = setTimeout(this.updateModifiedNodes.bind(this), 0);
    },

    updateModifiedNodes: function()
    {
        if ("_updateModifiedNodesTimeout" in this) {
            clearTimeout(this._updateModifiedNodesTimeout);
            delete this._updateModifiedNodesTimeout;
        }

        var updatedParentTreeElements = [];
        var updateBreadcrumbs = false;

        for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) {
            var replaced = this.recentlyModifiedNodes[i].replaced;
            var parent = this.recentlyModifiedNodes[i].parent;
            var node = this.recentlyModifiedNodes[i].node;

            if (this.recentlyModifiedNodes[i].updated) {
                var nodeItem = this.treeOutline.findTreeElement(node);
                if (nodeItem)
                    nodeItem.updateTitle();
                continue;
            }
            
            if (!parent)
                continue;

            var parentNodeItem = this.treeOutline.findTreeElement(parent);
            if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
                parentNodeItem.updateChildren(replaced);
                parentNodeItem.alreadyUpdatedChildren = true;
                updatedParentTreeElements.push(parentNodeItem);
            }

            if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAncestorNode(this.focusedDOMNode, parent)))
                updateBreadcrumbs = true;
        }

        for (var i = 0; i < updatedParentTreeElements.length; ++i)
            delete updatedParentTreeElements[i].alreadyUpdatedChildren;

        this.recentlyModifiedNodes = [];

        if (updateBreadcrumbs)
            this.updateBreadcrumb(true);
    },

    _stylesPaneEdited: function()
    {
        this.sidebarPanes.metrics.needsUpdate = true;
        this.updateMetrics();
    },

    _metricsPaneEdited: function()
    {
        this.sidebarPanes.styles.needsUpdate = true;
        this.updateStyles(true);
    },

    _mouseMovedInCrumbs: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");

        WebInspector.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0);

        if ("_mouseOutOfCrumbsTimeout" in this) {
            clearTimeout(this._mouseOutOfCrumbsTimeout);
            delete this._mouseOutOfCrumbsTimeout;
        }
    },

    _mouseMovedOutOfCrumbs: function(event)
    {
        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
            return;

        WebInspector.highlightDOMNode(0);

        this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
    },

    updateBreadcrumb: function(forceUpdate)
    {
        if (!this.visible)
            return;

        var crumbs = this.crumbsElement;

        var handled = false;
        var foundRoot = false;
        var crumb = crumbs.firstChild;
        while (crumb) {
            if (crumb.representedObject === this.rootDOMNode)
                foundRoot = true;

            if (foundRoot)
                crumb.addStyleClass("dimmed");
            else
                crumb.removeStyleClass("dimmed");

            if (crumb.representedObject === this.focusedDOMNode) {
                crumb.addStyleClass("selected");
                handled = true;
            } else {
                crumb.removeStyleClass("selected");
            }

            crumb = crumb.nextSibling;
        }

        if (handled && !forceUpdate) {
            // We don't need to rebuild the crumbs, but we need to adjust sizes
            // to reflect the new focused or root node.
            this.updateBreadcrumbSizes();
            return;
        }

        crumbs.removeChildren();

        var panel = this;

        function selectCrumbFunction(event)
        {
            var crumb = event.currentTarget;
            if (crumb.hasStyleClass("collapsed")) {
                // Clicking a collapsed crumb will expose the hidden crumbs.
                if (crumb === panel.crumbsElement.firstChild) {
                    // If the focused crumb is the first child, pick the farthest crumb
                    // that is still hidden. This allows the user to expose every crumb.
                    var currentCrumb = crumb;
                    while (currentCrumb) {
                        var hidden = currentCrumb.hasStyleClass("hidden");
                        var collapsed = currentCrumb.hasStyleClass("collapsed");
                        if (!hidden && !collapsed)
                            break;
                        crumb = currentCrumb;
                        currentCrumb = currentCrumb.nextSibling;
                    }
                }

                panel.updateBreadcrumbSizes(crumb);
            } else {
                // Clicking a dimmed crumb or double clicking (event.detail >= 2)
                // will change the root node in addition to the focused node.
                if (event.detail >= 2 || crumb.hasStyleClass("dimmed"))
                    panel.rootDOMNode = crumb.representedObject.parentNode;
                panel.focusedDOMNode = crumb.representedObject;
            }

            event.preventDefault();
        }

        foundRoot = false;
        for (var current = this.focusedDOMNode; current; current = current.parentNode) {
            if (current.nodeType === Node.DOCUMENT_NODE)
                continue;

            if (current === this.rootDOMNode)
                foundRoot = true;

            var crumb = document.createElement("span");
            crumb.className = "crumb";
            crumb.representedObject = current;
            crumb.addEventListener("mousedown", selectCrumbFunction, false);

            var crumbTitle;
            switch (current.nodeType) {
                case Node.ELEMENT_NODE:
                    this.decorateNodeLabel(current, crumb);
                    break;

                case Node.TEXT_NODE:
                    if (isNodeWhitespace.call(current))
                        crumbTitle = WebInspector.UIString("(whitespace)");
                    else
                        crumbTitle = WebInspector.UIString("(text)");
                    break

                case Node.COMMENT_NODE:
                    crumbTitle = "<!-->";
                    break;

                case Node.DOCUMENT_TYPE_NODE:
                    crumbTitle = "<!DOCTYPE>";
                    break;

                default:
                    crumbTitle = this.treeOutline.nodeNameToCorrectCase(current.nodeName);
            }

            if (!crumb.childNodes.length) {
                var nameElement = document.createElement("span");
                nameElement.textContent = crumbTitle;
                crumb.appendChild(nameElement);
                crumb.title = crumbTitle;
            }

            if (foundRoot)
                crumb.addStyleClass("dimmed");
            if (current === this.focusedDOMNode)
                crumb.addStyleClass("selected");
            if (!crumbs.childNodes.length)
                crumb.addStyleClass("end");

            crumbs.appendChild(crumb);
        }

        if (crumbs.hasChildNodes())
            crumbs.lastChild.addStyleClass("start");

        this.updateBreadcrumbSizes();
    },

    decorateNodeLabel: function(node, parentElement)
    {
        var title = this.treeOutline.nodeNameToCorrectCase(node.nodeName);

        var nameElement = document.createElement("span");
        nameElement.textContent = title;
        parentElement.appendChild(nameElement);

        var idAttribute = node.getAttribute("id");
        if (idAttribute) {
            var idElement = document.createElement("span");
            parentElement.appendChild(idElement);

            var part = "#" + idAttribute;
            title += part;
            idElement.appendChild(document.createTextNode(part));

            // Mark the name as extra, since the ID is more important.
            nameElement.className = "extra";
        }

        var classAttribute = node.getAttribute("class");
        if (classAttribute) {
            var classes = classAttribute.split(/\s+/);
            var foundClasses = {};

            if (classes.length) {
                var classesElement = document.createElement("span");
                classesElement.className = "extra";
                parentElement.appendChild(classesElement);

                for (var i = 0; i < classes.length; ++i) {
                    var className = classes[i];
                    if (className && !(className in foundClasses)) {
                        var part = "." + className;
                        title += part;
                        classesElement.appendChild(document.createTextNode(part));
                        foundClasses[className] = true;
                    }
                }
            }
        }
        parentElement.title = title;
    },

    linkifyNodeReference: function(node)
    {
        var link = document.createElement("span");
        link.className = "node-link";
        this.decorateNodeLabel(node, link);
        WebInspector.wireElementWithDOMNode(link, node.id);
        return link;
    },

    linkifyNodeById: function(nodeId)
    {
        var node = WebInspector.domAgent.nodeForId(nodeId);
        if (!node)
            return document.createTextNode(WebInspector.UIString("<node>"));
        return this.linkifyNodeReference(node);
    },

    updateBreadcrumbSizes: function(focusedCrumb)
    {
        if (!this.visible)
            return;

        if (document.body.offsetWidth <= 0) {
            // The stylesheet hasn't loaded yet or the window is closed,
            // so we can't calculate what is need. Return early.
            return;
        }

        var crumbs = this.crumbsElement;
        if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
            return; // No crumbs, do nothing.

        // A Zero index is the right most child crumb in the breadcrumb.
        var selectedIndex = 0;
        var focusedIndex = 0;
        var selectedCrumb;

        var i = 0;
        var crumb = crumbs.firstChild;
        while (crumb) {
            // Find the selected crumb and index. 
            if (!selectedCrumb && crumb.hasStyleClass("selected")) {
                selectedCrumb = crumb;
                selectedIndex = i;
            }

            // Find the focused crumb index. 
            if (crumb === focusedCrumb)
                focusedIndex = i;

            // Remove any styles that affect size before
            // deciding to shorten any crumbs.
            if (crumb !== crumbs.lastChild)
                crumb.removeStyleClass("start");
            if (crumb !== crumbs.firstChild)
                crumb.removeStyleClass("end");

            crumb.removeStyleClass("compact");
            crumb.removeStyleClass("collapsed");
            crumb.removeStyleClass("hidden");

            crumb = crumb.nextSibling;
            ++i;
        }

        // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
        // The order of the crumbs in the document is opposite of the visual order.
        crumbs.firstChild.addStyleClass("end");
        crumbs.lastChild.addStyleClass("start");

        function crumbsAreSmallerThanContainer()
        {
            var rightPadding = 20;
            var errorWarningElement = document.getElementById("error-warning-count");
            if (!WebInspector.drawer.visible && errorWarningElement)
                rightPadding += errorWarningElement.offsetWidth;
            return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding) < window.innerWidth);
        }

        if (crumbsAreSmallerThanContainer())
            return; // No need to compact the crumbs, they all fit at full size.

        var BothSides = 0;
        var AncestorSide = -1;
        var ChildSide = 1;

        function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
        {
            if (!significantCrumb)
                significantCrumb = (focusedCrumb || selectedCrumb);

            if (significantCrumb === selectedCrumb)
                var significantIndex = selectedIndex;
            else if (significantCrumb === focusedCrumb)
                var significantIndex = focusedIndex;
            else {
                var significantIndex = 0;
                for (var i = 0; i < crumbs.childNodes.length; ++i) {
                    if (crumbs.childNodes[i] === significantCrumb) {
                        significantIndex = i;
                        break;
                    }
                }
            }

            function shrinkCrumbAtIndex(index)
            {
                var shrinkCrumb = crumbs.childNodes[index];
                if (shrinkCrumb && shrinkCrumb !== significantCrumb)
                    shrinkingFunction(shrinkCrumb);
                if (crumbsAreSmallerThanContainer())
                    return true; // No need to compact the crumbs more.
                return false;
            }

            // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
            // fit in the container or we run out of crumbs to shrink.
            if (direction) {
                // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
                var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
                while (index !== significantIndex) {
                    if (shrinkCrumbAtIndex(index))
                        return true;
                    index += (direction > 0 ? 1 : -1);
                }
            } else {
                // Crumbs are shrunk in order of descending distance from the signifcant crumb,
                // with a tie going to child crumbs.
                var startIndex = 0;
                var endIndex = crumbs.childNodes.length - 1;
                while (startIndex != significantIndex || endIndex != significantIndex) {
                    var startDistance = significantIndex - startIndex;
                    var endDistance = endIndex - significantIndex;
                    if (startDistance >= endDistance)
                        var index = startIndex++;
                    else
                        var index = endIndex--;
                    if (shrinkCrumbAtIndex(index))
                        return true;
                }
            }

            // We are not small enough yet, return false so the caller knows.
            return false;
        }

        function coalesceCollapsedCrumbs()
        {
            var crumb = crumbs.firstChild;
            var collapsedRun = false;
            var newStartNeeded = false;
            var newEndNeeded = false;
            while (crumb) {
                var hidden = crumb.hasStyleClass("hidden");
                if (!hidden) {
                    var collapsed = crumb.hasStyleClass("collapsed"); 
                    if (collapsedRun && collapsed) {
                        crumb.addStyleClass("hidden");
                        crumb.removeStyleClass("compact");
                        crumb.removeStyleClass("collapsed");

                        if (crumb.hasStyleClass("start")) {
                            crumb.removeStyleClass("start");
                            newStartNeeded = true;
                        }

                        if (crumb.hasStyleClass("end")) {
                            crumb.removeStyleClass("end");
                            newEndNeeded = true;
                        }

                        continue;
                    }

                    collapsedRun = collapsed;

                    if (newEndNeeded) {
                        newEndNeeded = false;
                        crumb.addStyleClass("end");
                    }
                } else
                    collapsedRun = true;
                crumb = crumb.nextSibling;
            }

            if (newStartNeeded) {
                crumb = crumbs.lastChild;
                while (crumb) {
                    if (!crumb.hasStyleClass("hidden")) {
                        crumb.addStyleClass("start");
                        break;
                    }
                    crumb = crumb.previousSibling;
                }
            }
        }

        function compact(crumb)
        {
            if (crumb.hasStyleClass("hidden"))
                return;
            crumb.addStyleClass("compact");
        }

        function collapse(crumb, dontCoalesce)
        {
            if (crumb.hasStyleClass("hidden"))
                return;
            crumb.addStyleClass("collapsed");
            crumb.removeStyleClass("compact");
            if (!dontCoalesce)
                coalesceCollapsedCrumbs();
        }

        function compactDimmed(crumb)
        {
            if (crumb.hasStyleClass("dimmed"))
                compact(crumb);
        }

        function collapseDimmed(crumb)
        {
            if (crumb.hasStyleClass("dimmed"))
                collapse(crumb);
        }

        if (!focusedCrumb) {
            // When not focused on a crumb we can be biased and collapse less important
            // crumbs that the user might not care much about.

            // Compact child crumbs.
            if (makeCrumbsSmaller(compact, ChildSide))
                return;

            // Collapse child crumbs.
            if (makeCrumbsSmaller(collapse, ChildSide))
                return;

            // Compact dimmed ancestor crumbs.
            if (makeCrumbsSmaller(compactDimmed, AncestorSide))
                return;

            // Collapse dimmed ancestor crumbs.
            if (makeCrumbsSmaller(collapseDimmed, AncestorSide))
                return;
        }

        // Compact ancestor crumbs, or from both sides if focused.
        if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
            return;

        // Collapse ancestor crumbs, or from both sides if focused.
        if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
            return;

        if (!selectedCrumb)
            return;

        // Compact the selected crumb.
        compact(selectedCrumb);
        if (crumbsAreSmallerThanContainer())
            return;

        // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
        collapse(selectedCrumb, true);
    },

    updateStyles: function(forceUpdate)
    {
        var stylesSidebarPane = this.sidebarPanes.styles;
        var computedStylePane = this.sidebarPanes.computedStyle;
        if ((!stylesSidebarPane.expanded && !computedStylePane.expanded) || !stylesSidebarPane.needsUpdate)
            return;

        stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate);
        stylesSidebarPane.needsUpdate = false;
    },

    updateMetrics: function()
    {
        var metricsSidebarPane = this.sidebarPanes.metrics;
        if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate)
            return;

        metricsSidebarPane.update(this.focusedDOMNode);
        metricsSidebarPane.needsUpdate = false;
    },

    updateProperties: function()
    {
        var propertiesSidebarPane = this.sidebarPanes.properties;
        if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate)
            return;

        propertiesSidebarPane.update(this.focusedDOMNode);
        propertiesSidebarPane.needsUpdate = false;
    },

    updateEventListeners: function()
    {
        var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
        if (!eventListenersSidebarPane.expanded || !eventListenersSidebarPane.needsUpdate)
            return;

        eventListenersSidebarPane.update(this.focusedDOMNode);
        eventListenersSidebarPane.needsUpdate = false;
    },

    _registerShortcuts: function()
    {
        var shortcut = WebInspector.KeyboardShortcut;
        var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Elements Panel"));
        var keys = [
            shortcut.shortcutToString(shortcut.Keys.Up),
            shortcut.shortcutToString(shortcut.Keys.Down)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Navigate elements"));
        var keys = [
            shortcut.shortcutToString(shortcut.Keys.Right),
            shortcut.shortcutToString(shortcut.Keys.Left)
        ];
        section.addRelatedKeys(keys, WebInspector.UIString("Expand/collapse"));
        section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Edit attribute"));

        this.sidebarPanes.styles.registerShortcuts();
    },

    handleShortcut: function(event)
    {
        // Cmd/Control + Shift + C should be a shortcut to clicking the Node Search Button.
        // This shortcut matches Firebug.
        if (event.keyIdentifier === "U+0043") {     // C key
            if (WebInspector.isMac())
                var isNodeSearchKey = event.metaKey && !event.ctrlKey && !event.altKey && event.shiftKey;
            else
                var isNodeSearchKey = event.ctrlKey && !event.metaKey && !event.altKey && event.shiftKey;

            if (isNodeSearchKey) {
                this.toggleSearchingForNode();
                event.handled = true;
                return;
            }
        }
    },

    handleCopyEvent: function(event)
    {
        // Don't prevent the normal copy if the user has a selection.
        if (!window.getSelection().isCollapsed)
            return;
        event.clipboardData.clearData();
        event.preventDefault();
        InspectorBackend.copyNode(this.focusedDOMNode.id);
    },

    rightSidebarResizerDragStart: function(event)
    {
        WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResizerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resize");
    },

    rightSidebarResizerDragEnd: function(event)
    {
        WebInspector.elementDragEnd(event);
        this.saveSidebarWidth();
    },

    rightSidebarResizerDrag: function(event)
    {
        var x = event.pageX;
        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66);
        this.setSidebarWidth(newWidth);
        event.preventDefault();
    },

    setSidebarWidth: function(newWidth)
    {
        this.sidebarElement.style.width = newWidth + "px";
        this.contentElement.style.right = newWidth + "px";
        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
        this.treeOutline.updateSelection();
    },

    updateFocusedNode: function(nodeId)
    {
        var node = WebInspector.domAgent.nodeForId(nodeId);
        if (!node)
            return;

        this.focusedDOMNode = node;
        this._nodeSearchButton.toggled = false;
    },

    _setSearchingForNode: function(enabled)
    {
        this._nodeSearchButton.toggled = enabled;
    },

    setSearchingForNode: function(enabled)
    {
        InspectorBackend.setSearchingForNode(enabled, this._setSearchingForNode.bind(this));
    },

    toggleSearchingForNode: function()
    {
        this.setSearchingForNode(!this._nodeSearchButton.toggled);
    },

    elementsToRestoreScrollPositionsFor: function()
    {
        return [ this.contentElement, this.sidebarElement ];
    }
}

WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype;

/* NetworkPanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.NetworkPanel = function()
{
    WebInspector.Panel.call(this, "network");

    this.createSidebar();
    this.sidebarElement.className = "network-sidebar";

    this._resources = [];
    this._staleResources = [];
    this._resourceGridNodes = {};
    this._mainResourceLoadTime = -1;
    this._mainResourceDOMContentTime = -1;
    this._hiddenCategories = {};

    this._categories = WebInspector.resourceCategories;

    this.containerElement = document.createElement("div");
    this.containerElement.id = "network-container";
    this.sidebarElement.appendChild(this.containerElement);

    this._viewsContainerElement = document.createElement("div");
    this._viewsContainerElement.id = "network-views";
    this._viewsContainerElement.className = "hidden";

    this.element.appendChild(this._viewsContainerElement);

    this._createSortingFunctions();
    this._createTable();
    this._createTimelineGrid();
    this._createStatusbarButtons();
    this._createFilterStatusBarItems();
    this._createSummaryBar();

    this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);

    this.calculator = new WebInspector.NetworkTransferTimeCalculator();
    this._filter(this._filterAllElement, false);

    this._toggleGridMode();
}

WebInspector.NetworkPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Network");
    },

    get statusBarItems()
    {
        return [this._largerResourcesButton.element, this._clearButton.element, this._filterBarElement];
    },

    isCategoryVisible: function(categoryName)
    {
        return true;
    },

    elementsToRestoreScrollPositionsFor: function()
    {
        return [this.containerElement];
    },

    resize: function()
    {
        WebInspector.Panel.prototype.resize.call(this);
        this._dataGrid.updateWidths();
        this._positionSummaryBar();
    },

    updateSidebarWidth: function()
    {
        if (!this._viewingResourceMode)
            return;
        WebInspector.Panel.prototype.updateSidebarWidth.apply(this, arguments);
    },

    updateMainViewWidth: function(width)
    {
        this._viewsContainerElement.style.left = width + "px";
    },

    handleShortcut: function(event)
    {
        if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
            this._toggleGridMode();
            event.handled = true;
        }
    },

    _positionSummaryBar: function()
    {
        // Position the total bar.
        const rowHeight = 22;
        const summaryBarHeight = 22;
        var offsetHeight = this.element.offsetHeight;

        var parentElement = this._summaryBarElement.parentElement;

        if (this._summaryBarElement.parentElement !== this.element && offsetHeight > (this._dataGrid.children.length - 1) * rowHeight + summaryBarHeight) {
            this._dataGrid.removeChild(this._summaryBarRowNode);
            this._summaryBarElement.addStyleClass("network-summary-bar-bottom");
            delete this._summaryBarRowNode;
            this.element.appendChild(this._summaryBarElement);
            this._dataGrid.element.style.bottom = "20px";
            return;
        }

        if (!this._summaryBarRowNode && offsetHeight - summaryBarHeight < this._dataGrid.children.length * rowHeight) {
            this._summaryBarRowNode = new WebInspector.NetworkTotalGridNode(this._summaryBarElement);
            this._summaryBarElement.removeStyleClass("network-summary-bar-bottom");
            this._dataGrid.appendChild(this._summaryBarRowNode);
            this._dataGrid.element.style.bottom = 0;
            this._sortItems();
        }
    },

    _createTimelineGrid: function()
    {
        this._timelineGrid = new WebInspector.TimelineGrid();
        this._timelineGrid.element.addStyleClass("network-timeline-grid");
        this._dataGrid.element.appendChild(this._timelineGrid.element);
    },

    _createTable: function()
    {
        var columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}};
        columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path"));
        columns.name.sortable = true;
        columns.name.width = "20%";
        columns.name.disclosure = true;

        columns.method.title = WebInspector.UIString("Method");
        columns.method.sortable = true;
        columns.method.width = "7%";

        columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text"));
        columns.status.sortable = true;
        columns.status.width = "8%";

        columns.type.title = WebInspector.UIString("Type");
        columns.type.sortable = true;
        columns.type.width = "7%";

        columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Transfer"));
        columns.size.sortable = true;
        columns.size.width = "10%";
        columns.size.aligned = "right";

        columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Duration"));
        columns.time.sortable = true;
        columns.time.width = "10%";
        columns.time.aligned = "right";

        columns.timeline.title = "";
        columns.timeline.sortable = false;
        columns.timeline.width = "40%";
        columns.timeline.sort = "ascending";

        this._dataGrid = new WebInspector.DataGrid(columns);
        this.containerElement.appendChild(this._dataGrid.element);
        this._dataGrid.addEventListener("sorting changed", this._sortItems, this);
        this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this);

        this._patchTimelineHeader();
    },

    _makeHeaderFragment: function(title, subtitle)
    {
        var fragment = document.createDocumentFragment();
        fragment.appendChild(document.createTextNode(title));
        var subtitleDiv = document.createElement("div");
        subtitleDiv.className = "network-header-subtitle";
        subtitleDiv.textContent = subtitle;
        fragment.appendChild(subtitleDiv);
        return fragment;
    },

    _patchTimelineHeader: function()
    {
        var timelineSorting = document.createElement("select");

        var option = document.createElement("option");
        option.value = "startTime";
        option.label = WebInspector.UIString("Timeline");
        timelineSorting.appendChild(option);

        option = document.createElement("option");
        option.value = "startTime";
        option.label = WebInspector.UIString("Start Time");
        timelineSorting.appendChild(option);

        option = document.createElement("option");
        option.value = "responseTime";
        option.label = WebInspector.UIString("Response Time");
        timelineSorting.appendChild(option);

        option = document.createElement("option");
        option.value = "endTime";
        option.label = WebInspector.UIString("End Time");
        timelineSorting.appendChild(option);

        option = document.createElement("option");
        option.value = "duration";
        option.label = WebInspector.UIString("Duration");
        timelineSorting.appendChild(option);

        option = document.createElement("option");
        option.value = "latency";
        option.label = WebInspector.UIString("Latency");
        timelineSorting.appendChild(option);

        var header = this._dataGrid.headerTableHeader("timeline");
        header.firstChild.appendChild(timelineSorting);

        timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false);
        timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
        this._timelineSortSelector = timelineSorting;
    },

    _createSortingFunctions: function()
    {
        this._sortingFunctions = {};
        this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
        this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false);
        this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false);
        this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false);
        this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
        this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false);
        this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
        this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
        this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false);
        this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false);
        this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true);
        this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true);

        var timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
        var durationCalculator = new WebInspector.NetworkTransferDurationCalculator();

        this._calculators = {};
        this._calculators.timeline = timeCalculator;
        this._calculators.startTime = timeCalculator;
        this._calculators.endTime = timeCalculator;
        this._calculators.responseTime = timeCalculator;
        this._calculators.duration = durationCalculator;
        this._calculators.latency = durationCalculator;
    },

    _sortItems: function()
    {
        var columnIdentifier = this._dataGrid.sortColumnIdentifier;
        if (columnIdentifier === "timeline") {
            this._sortByTimeline();
            return;
        }
        var sortingFunction = this._sortingFunctions[columnIdentifier];
        if (!sortingFunction)
            return;

        this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending");
        this._timelineSortSelector.selectedIndex = 0;
    },

    _sortByTimeline: function()
    {
        var selectedIndex = this._timelineSortSelector.selectedIndex;
        if (!selectedIndex)
            selectedIndex = 1; // Sort by start time by default.
        var selectedOption = this._timelineSortSelector[selectedIndex];
        var value = selectedOption.value;

        var sortingFunction = this._sortingFunctions[value];
        this._dataGrid.sortNodes(sortingFunction);
        this.calculator = this._calculators[value];
        if (this.calculator.startAtZero)
            this._timelineGrid.hideEventDividers();
        else
            this._timelineGrid.showEventDividers();
        this._dataGrid.markColumnAsSortedBy("timeline", "ascending");
    },

    _createFilterStatusBarItems: function()
    {
        var filterBarElement = document.createElement("div");
        filterBarElement.className = "scope-bar status-bar-item";
        filterBarElement.id = "network-filter";

        function createFilterElement(category, label)
        {
            var categoryElement = document.createElement("li");
            categoryElement.category = category;
            categoryElement.className = category;
            categoryElement.appendChild(document.createTextNode(label));
            categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
            filterBarElement.appendChild(categoryElement);

            return categoryElement;
        }

        this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));

        // Add a divider
        var dividerElement = document.createElement("div");
        dividerElement.addStyleClass("scope-bar-divider");
        filterBarElement.appendChild(dividerElement);

        for (var category in this._categories)
            createFilterElement.call(this, category, this._categories[category].title);
        this._filterBarElement = filterBarElement;
    },

    _createSummaryBar: function()
    {
        this._summaryBarElement = document.createElement("div");
        this._summaryBarElement.className = "network-summary-bar";
        this.containerElement.appendChild(this._summaryBarElement);
    },

    _updateSummaryBar: function()
    {
        this._positionSummaryBar(); // Grid is growing.
        var numRequests = this._resources.length;
        var transferSize = 0;
        var baseTime = -1;
        var maxTime = -1;
        for (var i = 0; i < this._resources.length; ++i) {
            var resource = this._resources[i];
            transferSize += resource.cached ? 0 : resource.transferSize;
            if (resource === WebInspector.mainResource)
                baseTime = resource.startTime;
            if (resource.endTime > maxTime) 
                maxTime = resource.endTime;
        }
        var text = String.sprintf(WebInspector.UIString("%d requests"), numRequests);
        text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
        if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1) {
            text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"),
                        Number.secondsToString(maxTime - baseTime),
                        Number.secondsToString(this._mainResourceLoadTime - baseTime),
                        Number.secondsToString(this._mainResourceDOMContentTime - baseTime));
        }
        this._summaryBarElement.textContent = text;
    },

    _showCategory: function(category)
    {
        this._dataGrid.element.addStyleClass("filter-" + category);
        delete this._hiddenCategories[category];
    },

    _hideCategory: function(category)
    {
        this._dataGrid.element.removeStyleClass("filter-" + category);
        this._hiddenCategories[category] = true;
    },

    _updateFilter: function(e)
    {
        var isMac = WebInspector.isMac();
        var selectMultiple = false;
        if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;
        if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;

        this._filter(e.target, selectMultiple);

        var searchField = document.getElementById("search");
        WebInspector.doPerformSearch(searchField.value, WebInspector.shortSearchWasForcedByKeyEvent, false, true);
    },

    _filter: function(target, selectMultiple)
    {
        function unselectAll()
        {
            for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) {
                var child = this._filterBarElement.childNodes[i];
                if (!child.category)
                    continue;

                child.removeStyleClass("selected");
                this._hideCategory(child.category);
            }
        }

        if (target.category === this._filterAllElement) {
            if (target.hasStyleClass("selected")) {
                // We can't unselect All, so we break early here
                return;
            }

            // If All wasn't selected, and now is, unselect everything else.
            unselectAll.call(this);
        } else {
            // Something other than All is being selected, so we want to unselect All.
            if (this._filterAllElement.hasStyleClass("selected")) {
                this._filterAllElement.removeStyleClass("selected");
                this._hideCategory("all");
            }
        }

        if (!selectMultiple) {
            // If multiple selection is off, we want to unselect everything else
            // and just select ourselves.
            unselectAll.call(this);

            target.addStyleClass("selected");
            this._showCategory(target.category);
            return;
        }

        if (target.hasStyleClass("selected")) {
            // If selectMultiple is turned on, and we were selected, we just
            // want to unselect ourselves.
            target.removeStyleClass("selected");
            this._hideCategory(target.category);
        } else {
            // If selectMultiple is turned on, and we weren't selected, we just
            // want to select ourselves.
            target.addStyleClass("selected");
            this._showCategory(target.category);
        }
    },

    _scheduleRefresh: function()
    {
        if (this._needsRefresh)
            return;

        this._needsRefresh = true;

        if (this.visible && !("_refreshTimeout" in this))
            this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
    },

    _updateDividersIfNeeded: function(force)
    {
        var timelineColumn = this._dataGrid.columns.timeline;
        for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
            if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
                // Position timline grid location.
                this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
                this._timelineGrid.element.style.right = "18px";
            }
        }

        var proceed = true;
        if (!this.visible) {
            this._scheduleRefresh();
            proceed = false;
        } else
            proceed = this._timelineGrid.updateDividers(force, this.calculator);
        
        if (!proceed)
            return;

        if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
            // If our current sorting method starts at zero, that means it shows all
            // resources starting at the same point, and so onLoad event and DOMContent
            // event lines really wouldn't make much sense here, so don't render them.
            // Additionally, if the calculator doesn't have the computePercentageFromEventTime
            // function defined, we are probably sorting by size, and event times aren't relevant
            // in this case.
            return;
        }

        this._timelineGrid.removeEventDividers();
        if (this._mainResourceLoadTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);

            var loadDivider = document.createElement("div");
            loadDivider.className = "network-event-divider network-red-divider";

            var loadDividerPadding = document.createElement("div");
            loadDividerPadding.className = "network-event-divider-padding";
            loadDividerPadding.title = WebInspector.UIString("Load event fired");
            loadDividerPadding.appendChild(loadDivider);
            loadDividerPadding.style.left = percent + "%";
            this._timelineGrid.addEventDivider(loadDividerPadding);
        }
        
        if (this._mainResourceDOMContentTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);

            var domContentDivider = document.createElement("div");
            domContentDivider.className = "network-event-divider network-blue-divider";
            
            var domContentDividerPadding = document.createElement("div");
            domContentDividerPadding.className = "network-event-divider-padding";
            domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
            domContentDividerPadding.appendChild(domContentDivider);
            domContentDividerPadding.style.left = percent + "%";
            this._timelineGrid.addEventDivider(domContentDividerPadding);
        }
    },

    _refreshIfNeeded: function()
    {
        if (this._needsRefresh)
            this.refresh();
    },

    _invalidateAllItems: function()
    {
        this._staleResources = this._resources.slice();
    },

    get calculator()
    {
        return this._calculator;
    },

    set calculator(x)
    {
        if (!x || this._calculator === x)
            return;

        this._calculator = x;
        this._calculator.reset();

        this._invalidateAllItems();
        this.refresh();
    },

    _resourceGridNode: function(resource)
    {
        return this._resourceGridNodes[resource.identifier];
    },

    revealAndSelectItem: function(resource)
    {
        var node = this._resourceGridNode(resource);
        if (node) {
            node.reveal();
            node.select(true);
        }
    },

    addEventDivider: function(divider)
    {
        this._timelineGrid.addEventDivider(divider);
    },

    get resourceTrackingEnabled()
    {
        return this._resourceTrackingEnabled;
    },

    _createStatusbarButtons: function()
    {
        this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
        this._clearButton.addEventListener("click", this.reset.bind(this), false);

        this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
        WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
        this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
    },

    _settingsLoaded: function()
    {
        this._largerResourcesButton.toggled = WebInspector.applicationSettings.resourcesLargeRows;
        if (!WebInspector.applicationSettings.resourcesLargeRows)
            this._setLargerResources(WebInspector.applicationSettings.resourcesLargeRows);
    },

    set mainResourceLoadTime(x)
    {
        if (this._mainResourceLoadTime === x)
            return;
        
        this._mainResourceLoadTime = x || -1;
        // Update the dividers to draw the new line
        this._updateDividersIfNeeded(true);
    },

    set mainResourceDOMContentTime(x)
    {
        if (this._mainResourceDOMContentTime === x)
            return;

        this._mainResourceDOMContentTime = x || -1;
        this._updateDividersIfNeeded(true);
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);

        this._refreshIfNeeded();

        var visibleView = this.visibleView;
        if (this.visibleResource) {
            this.visibleView.headersVisible = true;
            this.visibleView.show(this._viewsContainerElement);
        } else if (visibleView)
            visibleView.show();

        // Hide any views that are visible that are not this panel's current visible view.
        // This can happen when a ResourceView is visible in the Scripts panel then switched
        // to the this panel.
        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            var view = resource._resourcesView;
            if (!view || view === visibleView)
                continue;
            view.visible = false;
        }
        this._dataGrid.updateWidths();
        this._positionSummaryBar();
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);
        this._popoverHelper.hidePopup();
    },

    get searchableViews()
    {
        var views = [];
        return views;
    },

    searchMatchFound: function(view, matches)
    {
        this._resourceGridNode(view.resource).searchMatches = matches;
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);

        if (startingNewSearch || !this._resources)
            return;
    },

    performSearch: function(query)
    {
        WebInspector.Panel.prototype.performSearch.call(this, query);
    },

    get visibleView()
    {
        if (this.visibleResource)
            return this.visibleResource._resourcesView;
        return null;
    },

    refresh: function()
    {
        this._needsRefresh = false;
        if ("_refreshTimeout" in this) {
            clearTimeout(this._refreshTimeout);
            delete this._refreshTimeout;
        }

        var staleItemsLength = this._staleResources.length;

        var boundariesChanged = false;

        for (var i = 0; i < staleItemsLength; ++i) {
            var resource = this._staleResources[i];
            var node = this._resourceGridNode(resource);
            if (!node) {
                // Create the timeline tree element and graph.
                node = new WebInspector.NetworkDataGridNode(this, resource);
                this._resourceGridNodes[resource.identifier] = node;
                this._dataGrid.appendChild(node);
            }
            node.refreshResource();

            if (this.calculator.updateBoundaries(resource))
                boundariesChanged = true;
        }

        if (boundariesChanged) {
            // The boundaries changed, so all item graphs are stale.
            this._invalidateAllItems();
            staleItemsLength = this._staleResources.length;
        }

        for (var i = 0; i < staleItemsLength; ++i)
            this._resourceGridNode(this._staleResources[i]).refreshGraph(this.calculator);

        this._staleResources = [];
        this._sortItems();
        this._updateSummaryBar();
        this._dataGrid.updateWidths();
    },

    reset: function()
    {
        this._popoverHelper.hidePopup();
        this._closeVisibleResource();

        this._toggleGridMode();

        delete this.currentQuery;
        this.searchCanceled();

        if (this._resources) {
            var resourcesLength = this._resources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = this._resources[i];

                resource.warnings = 0;
                resource.errors = 0;

                delete resource._resourcesView;
            }
        }

        // Begin reset timeline
        if (this._calculator)
            this._calculator.reset();

        if (this._resources) {
            var itemsLength = this._resources.length;
            for (var i = 0; i < itemsLength; ++i) {
                var item = this._resources[i];
            }
        }

        this._resources = [];
        this._staleResources = [];
        this._resourceGridNodes = {};

        this._dataGrid.removeChildren();
        delete this._summaryBarRowNode;
        this._updateDividersIfNeeded(true);
        // End reset timeline.

        this._mainResourceLoadTime = -1;
        this._mainResourceDOMContentTime = -1;
 
        this._viewsContainerElement.removeChildren();
    },

    addResource: function(resource)
    {
        this._resources.push(resource);
        this.refreshResource(resource);
    },

    refreshResource: function(resource)
    {
        this._staleResources.push(resource);
        this._scheduleRefresh();

        if (!resource || !resource._resourcesView)
            return;

        if (this._resourceViewTypeMatchesResource(resource, resource._resourcesView))
            return;

        var newView = this._createResourceView(resource);
        if (newView.__proto__ === resource._resourcesView.__proto__)
            return;

        if (!this.currentQuery && this._resourceGridNode(resource))
            this._resourceGridNode(resource).updateErrorsAndWarnings();

        var oldView = resource._resourcesView;
        var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null;

        resource._resourcesView.detach();
        delete resource._resourcesView;

        resource._resourcesView = newView;

        newView.headersVisible = oldView.headersVisible;

        if (oldViewParentNode)
            newView.show(oldViewParentNode);

        WebInspector.panels.scripts.viewRecreated(oldView, newView);
    },

    canShowSourceLine: function(url, line)
    {
        return false;
    },

    showSourceLine: function(url, line)
    {
    },

    _showResource: function(resource, line)
    {
        if (!resource)
            return;

        this._popoverHelper.hidePopup();

        this._toggleViewingResourceMode();

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();

        var view = this._resourceViewForResource(resource);
        view.headersVisible = true;
        view.show(this._viewsContainerElement);

        if (line) {
            view.selectContentTab(true);
            if (view.revealLine)
                view.revealLine(line);
            if (view.highlightLine)
                view.highlightLine(line);
        }

        this.visibleResource = resource;
        this.updateSidebarWidth();
    },

    _closeVisibleResource: function()
    {
        this.element.removeStyleClass("viewing-resource");

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();
        delete this.visibleResource;

        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.select(true);

        this.updateSidebarWidth();
    },

    _resourceViewForResource: function(resource)
    {
        if (!resource)
            return null;
        if (!resource._resourcesView)
            resource._resourcesView = this._createResourceView(resource);
        return resource._resourcesView;
    },

    _toggleLargerResources: function()
    {
        WebInspector.applicationSettings.resourcesLargeRows = !WebInspector.applicationSettings.resourcesLargeRows;
        this._setLargerResources(WebInspector.applicationSettings.resourcesLargeRows);
    },

    _setLargerResources: function(enabled)
    {
        this._largerResourcesButton.toggled = enabled;
        if (!enabled) {
            this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
            this._dataGrid.element.addStyleClass("small");
            this._timelineGrid.element.addStyleClass("small");
        } else {
            this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
            this._dataGrid.element.removeStyleClass("small");
            this._timelineGrid.element.removeStyleClass("small");
        }
    },

    _createResourceView: function(resource)
    {
        switch (resource.category) {
            case WebInspector.resourceCategories.documents:
            case WebInspector.resourceCategories.stylesheets:
            case WebInspector.resourceCategories.scripts:
            case WebInspector.resourceCategories.xhr:
                return new WebInspector.SourceView(resource);
            case WebInspector.resourceCategories.images:
                return new WebInspector.ImageView(resource);
            case WebInspector.resourceCategories.fonts:
                return new WebInspector.FontView(resource);
            default:
                return new WebInspector.ResourceView(resource);
        }
    },

    _resourceViewTypeMatchesResource: function(resource, resourceView)
    {
        switch (resource.category) {
            case WebInspector.resourceCategories.documents:
            case WebInspector.resourceCategories.stylesheets:
            case WebInspector.resourceCategories.scripts:
            case WebInspector.resourceCategories.xhr:
                return resourceView instanceof WebInspector.SourceView;
            case WebInspector.resourceCategories.images:
                return resourceView instanceof WebInspector.ImageView;
            case WebInspector.resourceCategories.fonts:
                return resourceView instanceof WebInspector.FontView;
            default:
                return resourceView instanceof WebInspector.ResourceView;
        }
        return false;
    },

    _getPopoverAnchor: function(element)
    {
        var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
        if (!anchor)
            return null;
        var resource = anchor.parentElement.resource;
        return resource && resource.timing ? anchor : null;
    },

    _showPopover: function(anchor)
    {
        var tableElement = document.createElement("table");
        var resource = anchor.parentElement.resource;
        var rows = [];

        function addRow(title, start, end, color)
        {
            var row = {};
            row.title = title;
            row.start = start;
            row.end = end;
            rows.push(row);
        }

        if (resource.timing.proxyStart !== -1)
            addRow(WebInspector.UIString("Proxy"), resource.timing.proxyStart, resource.timing.proxyEnd);

        if (resource.timing.dnsStart !== -1)
            addRow(WebInspector.UIString("DNS Lookup"), resource.timing.dnsStart, resource.timing.dnsEnd);

        if (resource.timing.connectStart !== -1) {
            if (resource.connectionReused)
                addRow(WebInspector.UIString("Blocking"), resource.timing.connectStart, resource.timing.connectEnd);
            else {
                var connectStart = resource.timing.connectStart;
                // Connection includes DNS, subtract it here.
                if (resource.timing.dnsStart !== -1)
                    connectStart += resource.timing.dnsEnd - resource.timing.dnsStart;
                addRow(WebInspector.UIString("Connecting"), connectStart, resource.timing.connectEnd);
            }
        }

        if (resource.timing.sslStart !== -1)
            addRow(WebInspector.UIString("SSL"), resource.timing.sslStart, resource.timing.sslEnd);

        var sendStart = resource.timing.sendStart;
        if (resource.timing.sslStart !== -1)
            sendStart += resource.timing.sslEnd - resource.timing.sslStart;
        
        addRow(WebInspector.UIString("Sending"), resource.timing.sendStart, resource.timing.sendEnd);
        addRow(WebInspector.UIString("Waiting"), resource.timing.sendEnd, resource.timing.receiveHeadersEnd);
        addRow(WebInspector.UIString("Receiving"), (resource.responseReceivedTime - resource.timing.requestTime) * 1000, (resource.endTime - resource.timing.requestTime) * 1000);

        const chartWidth = 200;
        var total = (resource.endTime - resource.timing.requestTime) * 1000;
        var scale = chartWidth / total;

        for (var i = 0; i < rows.length; ++i) {
            var tr = document.createElement("tr");
            tableElement.appendChild(tr);

            var td = document.createElement("td");
            td.textContent = rows[i].title;
            tr.appendChild(td);

            td = document.createElement("td");
            td.width = chartWidth + "px";

            var row = document.createElement("div");
            row.className = "network-timing-row";
            td.appendChild(row);

            var bar = document.createElement("span");
            bar.className = "network-timing-bar";
            bar.style.left = scale * rows[i].start + "px";
            bar.style.right = scale * (total - rows[i].end) + "px";
            bar.style.backgroundColor = rows[i].color;
            bar.textContent = "\u200B"; // Important for 0-time items to have 0 width.
            row.appendChild(bar);

            var title = document.createElement("span");
            title.className = "network-timing-bar-title";
            if (total - rows[i].end < rows[i].start)
                title.style.right = (scale * (total - rows[i].end) + 3) + "px";
            else
                title.style.left = (scale * rows[i].start + 3) + "px";
            title.textContent = Number.millisToString(rows[i].end - rows[i].start);
            row.appendChild(title);

            tr.appendChild(td);
        }

        var popover = new WebInspector.Popover(tableElement);
        popover.show(anchor);
        return popover;
    },

    _toggleGridMode: function()
    {
        if (this._viewingResourceMode) {
            this._viewingResourceMode = false;
            this.element.removeStyleClass("viewing-resource");
            this._dataGrid.element.removeStyleClass("viewing-resource-mode");
            this._viewsContainerElement.addStyleClass("hidden");
            this.sidebarElement.style.right = 0;
            this.sidebarElement.style.removeProperty("width");
        }

        if (this._briefGrid) {
            this._dataGrid.element.removeStyleClass("full-grid-mode");
            this._dataGrid.element.addStyleClass("brief-grid-mode");

            this._dataGrid.hideColumn("method");
            this._dataGrid.hideColumn("status");
            this._dataGrid.hideColumn("type");
            this._dataGrid.hideColumn("size");
            this._dataGrid.hideColumn("time");

            var widths = {};
            widths.name = 20;
            widths.timeline = 80;
        } else {
            this._dataGrid.element.addStyleClass("full-grid-mode");
            this._dataGrid.element.removeStyleClass("brief-grid-mode");

            this._dataGrid.showColumn("method");
            this._dataGrid.showColumn("status");
            this._dataGrid.showColumn("type");
            this._dataGrid.showColumn("size");
            this._dataGrid.showColumn("time");

            var widths = {};
            widths.name = 20;
            widths.method = 7;
            widths.status = 8;
            widths.type = 7;
            widths.size = 10;
            widths.time = 10;
            widths.timeline = 40;
        }

        this._dataGrid.showColumn("timeline");
        this._dataGrid.applyColumnWidthsMap(widths);

    },

    _toggleViewingResourceMode: function()
    {
        if (this._viewingResourceMode)
            return;
        this._viewingResourceMode = true;
        this._preservedColumnWidths = this._dataGrid.columnWidthsMap();

        this.element.addStyleClass("viewing-resource");
        this._dataGrid.element.addStyleClass("viewing-resource-mode");
        this._dataGrid.element.removeStyleClass("full-grid-mode");
        this._dataGrid.element.removeStyleClass("brief-grid-mode");

        this._dataGrid.hideColumn("method");
        this._dataGrid.hideColumn("status");
        this._dataGrid.hideColumn("type");
        this._dataGrid.hideColumn("size");
        this._dataGrid.hideColumn("time");
        this._dataGrid.hideColumn("timeline");

        this._viewsContainerElement.removeStyleClass("hidden");
        this.updateSidebarWidth(200);

        var widths = {};
        widths.name = 100;
        this._dataGrid.applyColumnWidthsMap(widths);
    }
}

WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.getResourceContent = function(identifier, callback)
{
    InspectorBackend.getResourceContent(identifier, false, callback);
}

WebInspector.NetworkBaseCalculator = function()
{
}

WebInspector.NetworkBaseCalculator.prototype = {
    computeSummaryValues: function(items)
    {
        var total = 0;
        var categoryValues = {};

        var itemsLength = items.length;
        for (var i = 0; i < itemsLength; ++i) {
            var item = items[i];
            var value = this._value(item);
            if (typeof value === "undefined")
                continue;
            if (!(item.category.name in categoryValues))
                categoryValues[item.category.name] = 0;
            categoryValues[item.category.name] += value;
            total += value;
        }

        return {categoryValues: categoryValues, total: total};
    },

    computeBarGraphPercentages: function(item)
    {
        return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
    },

    computeBarGraphLabels: function(item)
    {
        const label = this.formatValue(this._value(item));
        return {left: label, right: label, tooltip: label};
    },

    get boundarySpan()
    {
        return this.maximumBoundary - this.minimumBoundary;
    },

    updateBoundaries: function(item)
    {
        this.minimumBoundary = 0;

        var value = this._value(item);
        if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
            this.maximumBoundary = value;
            return true;
        }
        return false;
    },

    reset: function()
    {
        delete this.minimumBoundary;
        delete this.maximumBoundary;
    },

    _value: function(item)
    {
        return 0;
    },

    formatValue: function(value)
    {
        return value.toString();
    }
}

WebInspector.NetworkTimeCalculator = function(startAtZero)
{
    WebInspector.NetworkBaseCalculator.call(this);
    this.startAtZero = startAtZero;
}

WebInspector.NetworkTimeCalculator.prototype = {
    computeSummaryValues: function(resources)
    {
        var resourcesByCategory = {};
        var resourcesLength = resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = resources[i];
            if (!(resource.category.name in resourcesByCategory))
                resourcesByCategory[resource.category.name] = [];
            resourcesByCategory[resource.category.name].push(resource);
        }

        var earliestStart;
        var latestEnd;
        var categoryValues = {};
        for (var category in resourcesByCategory) {
            resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
            categoryValues[category] = 0;

            var segment = {start: -1, end: -1};

            var categoryResources = resourcesByCategory[category];
            var resourcesLength = categoryResources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = categoryResources[i];
                if (resource.startTime === -1 || resource.endTime === -1)
                    continue;

                if (typeof earliestStart === "undefined")
                    earliestStart = resource.startTime;
                else
                    earliestStart = Math.min(earliestStart, resource.startTime);

                if (typeof latestEnd === "undefined")
                    latestEnd = resource.endTime;
                else
                    latestEnd = Math.max(latestEnd, resource.endTime);

                if (resource.startTime <= segment.end) {
                    segment.end = Math.max(segment.end, resource.endTime);
                    continue;
                }

                categoryValues[category] += segment.end - segment.start;

                segment.start = resource.startTime;
                segment.end = resource.endTime;
            }

            // Add the last segment
            categoryValues[category] += segment.end - segment.start;
        }

        return {categoryValues: categoryValues, total: latestEnd - earliestStart};
    },

    computeBarGraphPercentages: function(resource)
    {
        if (resource.startTime !== -1)
            var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var start = 0;

        if (resource.responseReceivedTime !== -1)
            var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var middle = (this.startAtZero ? start : 100);

        if (resource.endTime !== -1)
            var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var end = (this.startAtZero ? middle : 100);

        if (this.startAtZero) {
            end -= start;
            middle -= start;
            start = 0;
        }

        return {start: start, middle: middle, end: end};
    },
    
    computePercentageFromEventTime: function(eventTime)
    {
        // This function computes a percentage in terms of the total loading time
        // of a specific event. If startAtZero is set, then this is useless, and we
        // want to return 0.
        if (eventTime !== -1 && !this.startAtZero)
            return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;

        return 0;
    },

    computeBarGraphLabels: function(resource)
    {
        var rightLabel = "";
        if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
            rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);

        var hasLatency = resource.latency > 0;
        if (hasLatency)
            var leftLabel = this.formatValue(resource.latency);
        else
            var leftLabel = rightLabel;

        if (resource.timing)
            return {left: leftLabel, right: rightLabel};

        if (hasLatency && rightLabel) {
            var total = this.formatValue(resource.duration);
            var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
        } else if (hasLatency)
            var tooltip = WebInspector.UIString("%s latency", leftLabel);
        else if (rightLabel)
            var tooltip = WebInspector.UIString("%s download", rightLabel);

        if (resource.cached)
            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
        return {left: leftLabel, right: rightLabel, tooltip: tooltip};
    },

    updateBoundaries: function(resource)
    {
        var didChange = false;

        var lowerBound;
        if (this.startAtZero)
            lowerBound = 0;
        else
            lowerBound = this._lowerBound(resource);

        if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
            this.minimumBoundary = lowerBound;
            didChange = true;
        }

        var upperBound = this._upperBound(resource);
        if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
            this.maximumBoundary = upperBound;
            didChange = true;
        }

        return didChange;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _lowerBound: function(resource)
    {
        return 0;
    },

    _upperBound: function(resource)
    {
        return 0;
    }
}

WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;

WebInspector.NetworkTransferTimeCalculator = function()
{
    WebInspector.NetworkTimeCalculator.call(this, false);
}

WebInspector.NetworkTransferTimeCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _lowerBound: function(resource)
    {
        return resource.startTime;
    },

    _upperBound: function(resource)
    {
        return resource.endTime;
    }
}

WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;

WebInspector.NetworkTransferDurationCalculator = function()
{
    WebInspector.NetworkTimeCalculator.call(this, true);
}

WebInspector.NetworkTransferDurationCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _upperBound: function(resource)
    {
        return resource.duration;
    }
}

WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;

WebInspector.NetworkDataGridNode = function(panel, resource)
{
    WebInspector.DataGridNode.call(this, {});
    this._panel = panel;
    this._resource = resource;
}

WebInspector.NetworkDataGridNode.prototype = {
    createCells: function()
    {
        this._nameCell = this._createDivInTD("name");
        this._methodCell = this._createDivInTD("method");
        this._statusCell = this._createDivInTD("status");
        this._typeCell = this._createDivInTD("type");
        this._sizeCell = this._createDivInTD("size");
        this._timeCell = this._createDivInTD("time");
        this._createTimelineCell();
    },

    select: function()
    {
        WebInspector.DataGridNode.prototype.select.apply(this, arguments);
        this._panel._showResource(this._resource);
    },

    get selectable()
    {
        if (!this._panel._hiddenCategories.all)
            return true;
        if (this._panel._hiddenCategories[this._resource.category.name])
            return false;
        return true;
    },

    _createDivInTD: function(columnIdentifier)
    {
        var td = document.createElement("td");
        td.className = columnIdentifier + "-column";
        var div = document.createElement("div");
        td.appendChild(div);
        this._element.appendChild(td);
        return div;
    },

    _createTimelineCell: function()
    {
        this._graphElement = document.createElement("div");
        this._graphElement.className = "network-graph-side";
        this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);

        this._barAreaElement = document.createElement("div");
        //    this._barAreaElement.className = "network-graph-bar-area hidden";
        this._barAreaElement.className = "network-graph-bar-area";
        this._barAreaElement.resource = this._resource;
        this._graphElement.appendChild(this._barAreaElement);

        this._barLeftElement = document.createElement("div");
        this._barLeftElement.className = "network-graph-bar waiting";
        this._barAreaElement.appendChild(this._barLeftElement);

        this._barRightElement = document.createElement("div");
        this._barRightElement.className = "network-graph-bar";
        this._barAreaElement.appendChild(this._barRightElement);

        this._labelLeftElement = document.createElement("div");
        this._labelLeftElement.className = "network-graph-label waiting";
        this._barAreaElement.appendChild(this._labelLeftElement);

        this._labelRightElement = document.createElement("div");
        this._labelRightElement.className = "network-graph-label";
        this._barAreaElement.appendChild(this._labelRightElement);

        this._timelineCell = document.createElement("td");
        this._timelineCell.className = "timeline-column";
        this._element.appendChild(this._timelineCell);
        this._timelineCell.appendChild(this._graphElement);
    },

    refreshResource: function()
    {
        this._refreshNameCell();

        this._methodCell.textContent = this._resource.requestMethod;

        this._refreshStatusCell();

        if (this._resource.mimeType) {
            this._typeCell.removeStyleClass("network-dim-cell");
            this._typeCell.textContent = this._resource.mimeType;
        } else {
            this._typeCell.addStyleClass("network-dim-cell");
            this._typeCell.textContent = WebInspector.UIString("Pending");
        }

        this._refreshSizeCell();
        this._refreshTimeCell();

        if (this._resource.cached)
            this._graphElement.addStyleClass("resource-cached");

        if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
            this._element.removeMatchingStyleClasses("network-category-\\w+");
            this._element.addStyleClass("network-category-" + this._resource.category.name);
        }
    },

    _refreshNameCell: function()
    {
        this._nameCell.removeChildren();

        if (this._resource.category === WebInspector.resourceCategories.images) {
            var previewImage = document.createElement("img");
            previewImage.className = "image-network-icon-preview";
            previewImage.src = this._resource.url;

            var iconElement = document.createElement("div");
            iconElement.className = "icon";
            iconElement.appendChild(previewImage);
        } else {
            var iconElement = document.createElement("img");
            iconElement.className = "icon";
        }
        this._nameCell.appendChild(iconElement);
        this._nameCell.appendChild(document.createTextNode(this._fileName()));


        var subtitle = this._resource.displayDomain;

        if (this._resource.path && this._resource.lastPathComponent) {
            var lastPathComponentIndex = this._resource.path.lastIndexOf("/" + this._resource.lastPathComponent);
            if (lastPathComponentIndex != -1)
                subtitle += this._resource.path.substring(0, lastPathComponentIndex);
        }

        this._appendSubtitle(this._nameCell, subtitle);
        this._nameCell.title = this._resource.url;
    },

    _fileName: function()
    {
        var fileName = this._resource.displayName;
        if (this._resource.queryString)
            fileName += "?" + this._resource.queryString;
        return fileName;
    },

    _refreshStatusCell: function()
    {
        this._statusCell.removeChildren();

        if (this._resource.statusCode) {
            this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
            this._statusCell.removeStyleClass("network-dim-cell");
            this._appendSubtitle(this._statusCell, this._resource.statusText);
            this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
        } else {
            this._statusCell.addStyleClass("network-dim-cell");
            this._statusCell.textContent = WebInspector.UIString("Pending");
        }
    },

    _refreshSizeCell: function()
    {
        var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
        var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
        var fromCache = this._resource.cached;
        this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)");
        if (fromCache)
            this._sizeCell.addStyleClass("network-dim-cell");
        else
            this._sizeCell.removeStyleClass("network-dim-cell");
        if (!fromCache)
            this._appendSubtitle(this._sizeCell, transferSize);
    },

    _refreshTimeCell: function()
    {
        if (this._resource.duration > 0) {
            this._timeCell.removeStyleClass("network-dim-cell");
            this._timeCell.textContent = Number.secondsToString(this._resource.duration);
            this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
        } else {
            this._timeCell.addStyleClass("network-dim-cell");
            this._timeCell.textContent = WebInspector.UIString("Pending");
        }
    },

    _appendSubtitle: function(cellElement, subtitleText)
    {
        var subtitleElement = document.createElement("div");
        subtitleElement.className = "network-cell-subtitle";
        subtitleElement.textContent = subtitleText;
        cellElement.appendChild(subtitleElement);
    },

    refreshGraph: function(calculator)
    {
        var percentages = calculator.computeBarGraphPercentages(this._resource);
        var labels = calculator.computeBarGraphLabels(this._resource);

        this._percentages = percentages;

        this._barAreaElement.removeStyleClass("hidden");

        if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
            this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
            this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
        }

        this._barLeftElement.style.setProperty("left", percentages.start + "%");
        this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");

        this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
        this._barRightElement.style.setProperty("left", percentages.middle + "%");

        this._labelLeftElement.textContent = labels.left;
        this._labelRightElement.textContent = labels.right;

        var tooltip = (labels.tooltip || "");
        this._barLeftElement.title = tooltip;
        this._labelLeftElement.title = tooltip;
        this._labelRightElement.title = tooltip;
        this._barRightElement.title = tooltip;
    },

    _refreshLabelPositions: function()
    {
        this._labelLeftElement.style.removeProperty("left");
        this._labelLeftElement.style.removeProperty("right");
        this._labelLeftElement.removeStyleClass("before");
        this._labelLeftElement.removeStyleClass("hidden");

        this._labelRightElement.style.removeProperty("left");
        this._labelRightElement.style.removeProperty("right");
        this._labelRightElement.removeStyleClass("after");
        this._labelRightElement.removeStyleClass("hidden");

        const labelPadding = 10;
        const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
        const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;

        if (this._barLeftElement) {
            var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
            var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
        } else {
            var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
            var rightBarWidth = barRightElementOffsetWidth - labelPadding;
        }

        const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
        const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;

        const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
        const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
        const graphElementOffsetWidth = this._graphElement.offsetWidth;

        if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
            var leftHidden = true;

        if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
            var rightHidden = true;

        if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
            // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
            if (labelBefore && !labelAfter)
                leftHidden = true;
            else if (labelAfter && !labelBefore)
                rightHidden = true;
        }

        if (labelBefore) {
            if (leftHidden)
                this._labelLeftElement.addStyleClass("hidden");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
            this._labelLeftElement.addStyleClass("before");
        } else {
            this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
        }

        if (labelAfter) {
            if (rightHidden)
                this._labelRightElement.addStyleClass("hidden");
            this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
            this._labelRightElement.addStyleClass("after");
        } else {
            this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
            this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
        }
    }
}

WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
{
    var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
    var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
    if (aFileName > bFileName)
        return 1;
    if (bFileName > aFileName)
        return -1;
    return 0;
}

WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
{
    if (b._resource.cached && !a._resource.cached)
        return 1;
    if (a._resource.cached && !b._resource.cached)
        return -1;

    if (a._resource.resourceSize === b._resource.resourceSize)
        return 0;

    return a._resource.resourceSize - b._resource.resourceSize;
}

WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
{
    var aValue = a._resource[propertyName];
    var bValue = b._resource[propertyName];
    if (aValue > bValue)
        return revert ? -1 : 1;
    if (bValue > aValue)
        return revert ? 1 : -1;
    return 0;
}

WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.NetworkTotalGridNode = function(element)
{
    this._summaryBarElement = element;
    WebInspector.DataGridNode.call(this, {summaryRow: true});
}

WebInspector.NetworkTotalGridNode.prototype = {
    createCells: function()
    {
        var td = document.createElement("td");
        td.setAttribute("colspan", 7);
        td.className = "network-summary";
        td.appendChild(this._summaryBarElement);
        this._element.appendChild(td);
    }
}

WebInspector.NetworkTotalGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

/* ResourcesPanel.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourcesPanel = function()
{
    WebInspector.Panel.call(this, "resources");

    this._items = [];
    this._staleItems = [];

    this._createPanelEnabler();

    this.viewsContainerElement = document.createElement("div");
    this.viewsContainerElement.id = "resource-views";
    this.element.appendChild(this.viewsContainerElement);

    this.createFilterPanel();
    this.createInterface();

    this._createStatusbarButtons();
    this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);

    this.reset();
    this.filter(this.filterAllElement, false);
    this.graphsTreeElement.children[0].select();
    this._resourceTrackingEnabled = false;

    this.sidebarElement.addEventListener("contextmenu", this._contextMenu.bind(this), true);
}

WebInspector.ResourcesPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Resources");
    },

    get statusBarItems()
    {
        return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
    },

    get categories()
    {
        return WebInspector.resourceCategories;
    },

    createItemTreeElement: function(item)
    {
        return new WebInspector.ResourceSidebarTreeElement(item);
    },

    createItemGraph: function(item)
    {
        return new WebInspector.ResourceGraph(item);
    },

    isCategoryVisible: function(categoryName)
    {
        return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase()));
    },

    get items()
    {
        return this._items;
    },

    createInterface: function()
    {
        this.containerElement = document.createElement("div");
        this.containerElement.id = "resources-container";
        this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
        this.element.appendChild(this.containerElement);

        this.createSidebar(this.containerElement, this.element);
        this.sidebarElement.id = "resources-sidebar";
        this.populateSidebar();

        this._containerContentElement = document.createElement("div");
        this._containerContentElement.id = "resources-container-content";
        this.containerElement.appendChild(this._containerContentElement);

        this.summaryBar = new WebInspector.SummaryBar(this.categories);
        this.summaryBar.element.id = "resources-summary";
        this._containerContentElement.appendChild(this.summaryBar.element);

        this._timelineGrid = new WebInspector.TimelineGrid();
        this._containerContentElement.appendChild(this._timelineGrid.element);
        this.itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
    },

    createFilterPanel: function()
    {
        this.filterBarElement = document.createElement("div");
        this.filterBarElement.id = "resources-filter";
        this.filterBarElement.className = "scope-bar";
        this.element.appendChild(this.filterBarElement);

        function createFilterElement(category)
        {
            if (category === "all")
                var label = WebInspector.UIString("All");
            else if (this.categories[category])
                var label = this.categories[category].title;

            var categoryElement = document.createElement("li");
            categoryElement.category = category;
            categoryElement.addStyleClass(category);
            categoryElement.appendChild(document.createTextNode(label));
            categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
            this.filterBarElement.appendChild(categoryElement);

            return categoryElement;
        }

        this.filterAllElement = createFilterElement.call(this, "all");

        // Add a divider
        var dividerElement = document.createElement("div");
        dividerElement.addStyleClass("scope-bar-divider");
        this.filterBarElement.appendChild(dividerElement);

        for (var category in this.categories)
            createFilterElement.call(this, category);
    },

    showCategory: function(category)
    {
        var filterClass = "filter-" + category.toLowerCase();
        this.itemsGraphsElement.addStyleClass(filterClass);
        this.itemsTreeElement.childrenListElement.addStyleClass(filterClass);
    },

    hideCategory: function(category)
    {
        var filterClass = "filter-" + category.toLowerCase();
        this.itemsGraphsElement.removeStyleClass(filterClass);
        this.itemsTreeElement.childrenListElement.removeStyleClass(filterClass);
    },

    filter: function(target, selectMultiple)
    {
        function unselectAll()
        {
            for (var i = 0; i < this.filterBarElement.childNodes.length; ++i) {
                var child = this.filterBarElement.childNodes[i];
                if (!child.category)
                    continue;

                child.removeStyleClass("selected");
                this.hideCategory(child.category);
            }
        }

        if (target === this.filterAllElement) {
            if (target.hasStyleClass("selected")) {
                // We can't unselect All, so we break early here
                return;
            }

            // If All wasn't selected, and now is, unselect everything else.
            unselectAll.call(this);
        } else {
            // Something other than All is being selected, so we want to unselect All.
            if (this.filterAllElement.hasStyleClass("selected")) {
                this.filterAllElement.removeStyleClass("selected");
                this.hideCategory("all");
            }
        }

        if (!selectMultiple) {
            // If multiple selection is off, we want to unselect everything else
            // and just select ourselves.
            unselectAll.call(this);

            target.addStyleClass("selected");
            this.showCategory(target.category);
            return;
        }

        if (target.hasStyleClass("selected")) {
            // If selectMultiple is turned on, and we were selected, we just
            // want to unselect ourselves.
            target.removeStyleClass("selected");
            this.hideCategory(target.category);
        } else {
            // If selectMultiple is turned on, and we weren't selected, we just
            // want to select ourselves.
            target.addStyleClass("selected");
            this.showCategory(target.category);
        }
    },

    _updateFilter: function(e)
    {
        var isMac = WebInspector.isMac();
        var selectMultiple = false;
        if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;
        if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
            selectMultiple = true;

        this.filter(e.target, selectMultiple);

        // When we are updating our filtering, scroll to the top so we don't end up
        // in blank graph under all the resources.
        this.containerElement.scrollTop = 0;

        var searchField = document.getElementById("search");
        WebInspector.doPerformSearch(searchField.value, WebInspector.shortSearchWasForcedByKeyEvent, false, true);
    },

    _updateDividersLabelBarPosition: function()
    {
        const scrollTop = this.containerElement.scrollTop;
        const offsetHeight = this.summaryBar.element.offsetHeight;
        const dividersTop = (scrollTop < offsetHeight ? offsetHeight : scrollTop);
        this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
    },

    get needsRefresh()
    {
        return this._needsRefresh;
    },

    set needsRefresh(x)
    {
        if (this._needsRefresh === x)
            return;

        this._needsRefresh = x;

        if (x) {
            if (this.visible && !("_refreshTimeout" in this))
                this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
        } else {
            if ("_refreshTimeout" in this) {
                clearTimeout(this._refreshTimeout);
                delete this._refreshTimeout;
            }
        }
    },

    refreshIfNeeded: function()
    {
        if (this.needsRefresh)
            this.refresh();
    },

    resize: function()
    {
        WebInspector.Panel.prototype.resize.call(this);

        this.updateGraphDividersIfNeeded();
    },

    invalidateAllItems: function()
    {
        this._staleItems = this._items.slice();
    },

    get calculator()
    {
        return this._calculator;
    },

    set calculator(x)
    {
        if (!x || this._calculator === x)
            return;

        this._calculator = x;
        this._calculator.reset();

        this._staleItems = this._items.slice();
        this.refresh();
    },

    addItem: function(item)
    {
        this._items.push(item);
        this.refreshItem(item);
    },

    removeItem: function(item)
    {
        this._items.remove(item, true);

        if (item._itemsTreeElement) {
            this.itemsTreeElement.removeChild(item._itemsTreeElement);
            this.itemsGraphsElement.removeChild(item._itemsTreeElement._itemGraph.graphElement);
        }

        delete item._itemsTreeElement;
        this.adjustScrollPosition();
    },

    refreshItem: function(item)
    {
        this._staleItems.push(item);
        this.needsRefresh = true;
    },

    revealAndSelectItem: function(item)
    {
        if (item._itemsTreeElement) {
            item._itemsTreeElement.reveal();
            item._itemsTreeElement.select(true);
        }
    },

    sortItems: function(sortingFunction)
    {
        var sortedElements = [].concat(this.itemsTreeElement.children);
        sortedElements.sort(sortingFunction);

        var sortedElementsLength = sortedElements.length;
        for (var i = 0; i < sortedElementsLength; ++i) {
            var treeElement = sortedElements[i];
            if (treeElement === this.itemsTreeElement.children[i])
                continue;

            var wasSelected = treeElement.selected;
            this.itemsTreeElement.removeChild(treeElement);
            this.itemsTreeElement.insertChild(treeElement, i);
            if (wasSelected)
                treeElement.select(true);

            var graphElement = treeElement._itemGraph.graphElement;
            this.itemsGraphsElement.insertBefore(graphElement, this.itemsGraphsElement.children[i]);
        }
    },

    adjustScrollPosition: function()
    {
        // Prevent the container from being scrolled off the end.
        if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
            this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
    },

    addEventDivider: function(divider)
    {
        this._timelineGrid.addEventDivider(divider);
    },

    hideEventDividers: function()
    {
        this._timelineGrid.hideEventDividers();
    },

    showEventDividers: function()
    {
        this._timelineGrid.showEventDividers();
    },

    populateSidebar: function()
    {
        this.timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
        this.timeGraphItem.onselect = this._graphSelected.bind(this);

        var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
        var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();

        this.timeGraphItem.sortingOptions = [
            { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator, optionName: "startTime" },
            { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator, optionName: "responseTime" },
            { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator, optionName: "endTime" },
            { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator, optionName: "duration" },
            { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator, optionName: "latency" },
        ];

        this.timeGraphItem.isBarOpaqueAtLeft = false;
        this.timeGraphItem.selectedSortingOptionIndex = 1;

        this.sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
        this.sizeGraphItem.onselect = this._graphSelected.bind(this);

        var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
        this.sizeGraphItem.sortingOptions = [
            { name: WebInspector.UIString("Sort by Transfer Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize, calculator: transferSizeCalculator, optionName: "transferSize" },
            { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator, optionName: "size" },
        ];

        this.sizeGraphItem.isBarOpaqueAtLeft = true;
        this.sizeGraphItem.selectedSortingOptionIndex = 0;

        this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
        this.sidebarTree.appendChild(this.graphsTreeElement);

        this.graphsTreeElement.appendChild(this.timeGraphItem);
        this.graphsTreeElement.appendChild(this.sizeGraphItem);
        this.graphsTreeElement.expand();

        this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
        this.sidebarTree.appendChild(this.itemsTreeElement);

        this.itemsTreeElement.expand();
    },

    get resourceTrackingEnabled()
    {
        return this._resourceTrackingEnabled;
    },

    _createPanelEnabler: function()
    {
        var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
        var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
        var panelEnablerButton = WebInspector.UIString("Enable resource tracking");

        this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
        this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);

        this.element.appendChild(this.panelEnablerView.element);

        this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
        this.enableToggleButton.addEventListener("click", this.toggleResourceTracking.bind(this), false);
    },

    _createStatusbarButtons: function()
    {
        this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");

        WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
        this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
        this.sortingSelectElement = document.createElement("select");
        this.sortingSelectElement.className = "status-bar-item";
        this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
    },

    _settingsLoaded: function()
    {
        this.largerResourcesButton.toggled = WebInspector.applicationSettings.resourcesLargeRows;
        if (!WebInspector.applicationSettings.resourcesLargeRows)
            this._setLargerResources(WebInspector.applicationSettings.resourcesLargeRows);
        this._loadSortOptions();
    },

    _loadSortOptions: function()
    {
        var newOptions = WebInspector.applicationSettings.resourcesSortOptions;
        if (!newOptions)
            return;

        this._loadSortOptionForGraph(this.timeGraphItem, newOptions.timeOption || "responseTime");
        this._loadSortOptionForGraph(this.sizeGraphItem, newOptions.sizeOption || "transferSize");
    },

    _loadSortOptionForGraph: function(graphItem, newOptionName)
    {
        var sortingOptions = graphItem.sortingOptions;
        for (var i = 0; i < sortingOptions.length; ++i) {
            if (sortingOptions[i].optionName === newOptionName) {
                graphItem.selectedSortingOptionIndex = i;
                // Propagate the option change down to the currently selected option.
                if (this._lastSelectedGraphTreeElement === graphItem) {
                    this._lastSelectedGraphTreeElement = null;
                    this._graphSelected(graphItem);
                }
            }
        }
    },

    get mainResourceLoadTime()
    {
        return this._mainResourceLoadTime || -1;
    },
    
    set mainResourceLoadTime(x)
    {
        if (this._mainResourceLoadTime === x)
            return;
        
        this._mainResourceLoadTime = x;
        
        // Update the dividers to draw the new line
        this.updateGraphDividersIfNeeded(true);
    },
    
    get mainResourceDOMContentTime()
    {
        return this._mainResourceDOMContentTime || -1;
    },
    
    set mainResourceDOMContentTime(x)
    {
        if (this._mainResourceDOMContentTime === x)
            return;
        
        this._mainResourceDOMContentTime = x;
        
        this.updateGraphDividersIfNeeded(true);
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);

        this._updateDividersLabelBarPosition();
        this.refreshIfNeeded();

        var visibleView = this.visibleView;
        if (this.visibleResource) {
            this.visibleView.headersVisible = true;
            this.visibleView.show(this.viewsContainerElement);
        } else if (visibleView)
            visibleView.show();

        // Hide any views that are visible that are not this panel's current visible view.
        // This can happen when a ResourceView is visible in the Scripts panel then switched
        // to the this panel.
        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            var view = resource._resourcesView;
            if (!view || view === visibleView)
                continue;
            view.visible = false;
        }
    },

    get searchableViews()
    {
        var views = [];

        const visibleView = this.visibleView;
        if (visibleView && visibleView.performSearch)
            views.push(visibleView);

        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            if (!resource._itemsTreeElement || !resource._itemsTreeElement.selectable)
                continue;
            var resourceView = this.resourceViewForResource(resource);
            if (!resourceView.performSearch || resourceView === visibleView)
                continue;
            views.push(resourceView);
        }

        return views;
    },

    get searchResultsSortFunction()
    {
        const resourceTreeElementSortFunction = this.sortingFunction;

        function sortFuction(a, b)
        {
            return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement);
        }

        return sortFuction;
    },

    searchMatchFound: function(view, matches)
    {
        view.resource._itemsTreeElement.searchMatches = matches;
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);

        if (startingNewSearch || !this._resources)
            return;

        for (var i = 0; i < this._resources.length; ++i) {
            var resource = this._resources[i];
            if (resource._itemsTreeElement)
                resource._itemsTreeElement.updateErrorsAndWarnings();
        }
    },

    performSearch: function(query)
    {
        for (var i = 0; i < this._resources.length; ++i) {
            var resource = this._resources[i];
            if (resource._itemsTreeElement)
                resource._itemsTreeElement.resetBubble();
        }

        WebInspector.Panel.prototype.performSearch.call(this, query);
    },

    get visibleView()
    {
        if (this.visibleResource)
            return this.visibleResource._resourcesView;
        return this._resourceTrackingEnabled ? null : this.panelEnablerView;
    },

    get sortingFunction()
    {
        return this._sortingFunction;
    },

    set sortingFunction(x)
    {
        this._sortingFunction = x;
        this._sortResourcesIfNeeded();
    },

    refresh: function()
    {
        this.needsRefresh = false;

        var staleItemsLength = this._staleItems.length;

        var boundariesChanged = false;

        for (var i = 0; i < staleItemsLength; ++i) {
            var item = this._staleItems[i];
            if (!item._itemsTreeElement) {
                // Create the timeline tree element and graph.
                item._itemsTreeElement = this.createItemTreeElement(item);
                item._itemsTreeElement._itemGraph = this.createItemGraph(item);

                this.itemsTreeElement.appendChild(item._itemsTreeElement);
                this.itemsGraphsElement.appendChild(item._itemsTreeElement._itemGraph.graphElement);
            }

            if (item._itemsTreeElement.refresh)
                item._itemsTreeElement.refresh();

            if (this.calculator.updateBoundaries(item))
                boundariesChanged = true;
        }

        if (boundariesChanged) {
            // The boundaries changed, so all item graphs are stale.
            this._staleItems = this._items.slice();
            staleItemsLength = this._staleItems.length;
        }


        const isBarOpaqueAtLeft = this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.isBarOpaqueAtLeft;
        for (var i = 0; i < staleItemsLength; ++i)
            this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator, isBarOpaqueAtLeft);

        this._staleItems = [];

        this.updateGraphDividersIfNeeded();

        this._sortResourcesIfNeeded();
        this._updateSummaryGraph();
    },

    _updateSummaryGraph: function()
    {
        this.summaryBar.update(this._resources);
    },

    resourceTrackingWasEnabled: function()
    {
        this._resourceTrackingEnabled = true;
        this.reset();
        this.restoreSidebarWidth();
    },

    resourceTrackingWasDisabled: function()
    {
        this._resourceTrackingEnabled = false;
        this.reset();
    },

    reset: function()
    {
        this._popoverHelper.hidePopup();
        this.closeVisibleResource();

        delete this.currentQuery;
        this.searchCanceled();

        if (this._resources) {
            var resourcesLength = this._resources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = this._resources[i];

                resource.warnings = 0;
                resource.errors = 0;

                delete resource._resourcesView;
            }
        }

        // Begin reset timeline
        this.containerElement.scrollTop = 0;

        if (this._calculator)
            this._calculator.reset();

        if (this._items) {
            var itemsLength = this._items.length;
            for (var i = 0; i < itemsLength; ++i) {
                var item = this._items[i];
                delete item._itemsTreeElement;
            }
        }

        this._items = [];
        this._staleItems = [];

        this.itemsTreeElement.removeChildren();
        this.itemsGraphsElement.removeChildren();

        this.updateGraphDividersIfNeeded(true);
        // End reset timeline.
        
        this.mainResourceLoadTime = -1;
        this.mainResourceDOMContentTime = -1;
 
        this.viewsContainerElement.removeChildren();

        this.summaryBar.reset();

        if (this._resourceTrackingEnabled) {
            this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            this.largerResourcesButton.visible = true;
            this.sortingSelectElement.removeStyleClass("hidden");
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            this.largerResourcesButton.visible = false;
            this.sortingSelectElement.addStyleClass("hidden");
            this.panelEnablerView.visible = true;
        }
    },

    addResource: function(resource)
    {
        this._resources.push(resource);
        this.refreshResource(resource);
    },

    removeResource: function(resource)
    {
        if (this.visibleView === resource._resourcesView)
            this.closeVisibleResource();

        this.removeItem(resource);

        resource.warnings = 0;
        resource.errors = 0;

        delete resource._resourcesView;
    },

    addMessageToResource: function(resource, msg)
    {
        if (!resource)
            return;

        switch (msg.level) {
        case WebInspector.ConsoleMessage.MessageLevel.Warning:
            resource.warnings += msg.repeatDelta;
            break;
        case WebInspector.ConsoleMessage.MessageLevel.Error:
            resource.errors += msg.repeatDelta;
            break;
        }

        if (!this.currentQuery && resource._itemsTreeElement)
            resource._itemsTreeElement.updateErrorsAndWarnings();

        var view = this.resourceViewForResource(resource);
        if (view.addMessage)
            view.addMessage(msg);
    },

    clearMessages: function()
    {
        var resourcesLength = this._resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = this._resources[i];
            resource.warnings = 0;
            resource.errors = 0;

            if (!this.currentQuery && resource._itemsTreeElement)
                resource._itemsTreeElement.updateErrorsAndWarnings();

            var view = resource._resourcesView;
            if (!view || !view.clearMessages)
                continue;
            view.clearMessages();
        }
    },

    refreshResource: function(resource)
    {
        this.refreshItem(resource);
    },

    recreateViewForResourceIfNeeded: function(resource)
    {
        if (!resource || !resource._resourcesView)
            return;

        var newView = this._createResourceView(resource);
        if (newView.__proto__ === resource._resourcesView.__proto__)
            return;

        if (!this.currentQuery && resource._itemsTreeElement)
            resource._itemsTreeElement.updateErrorsAndWarnings();

        var oldView = resource._resourcesView;
        var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null;

        resource._resourcesView.detach();
        delete resource._resourcesView;

        resource._resourcesView = newView;

        newView.headersVisible = oldView.headersVisible;

        if (oldViewParentNode)
            newView.show(oldViewParentNode);

        WebInspector.panels.scripts.viewRecreated(oldView, newView);
    },

    canShowSourceLine: function(url, line)
    {
        return this._resourceTrackingEnabled && !!WebInspector.resourceForURL(url);
    },

    showSourceLine: function(url, line)
    {
        this.showResource(WebInspector.resourceForURL(url), line);
    },

    showResource: function(resource, line)
    {
        if (!resource)
            return;

        this._popoverHelper.hidePopup();

        this.containerElement.addStyleClass("viewing-resource");

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();

        var view = this.resourceViewForResource(resource);
        view.headersVisible = true;
        view.show(this.viewsContainerElement);

        if (line) {
            view.selectContentTab(true);
            if (view.revealLine)
                view.revealLine(line);
            if (view.highlightLine)
                view.highlightLine(line);
        }

        this.revealAndSelectItem(resource);

        this.visibleResource = resource;

        this.updateSidebarWidth();
    },

    showView: function(view)
    {
        if (!view)
            return;
        this.showResource(view.resource);
    },

    closeVisibleResource: function()
    {
        this.containerElement.removeStyleClass("viewing-resource");
        this._updateDividersLabelBarPosition();

        if (this.visibleResource && this.visibleResource._resourcesView)
            this.visibleResource._resourcesView.hide();
        delete this.visibleResource;

        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.select(true);

        this.updateSidebarWidth();
    },

    resourceViewForResource: function(resource)
    {
        if (!resource)
            return null;
        if (!resource._resourcesView)
            resource._resourcesView = this._createResourceView(resource);
        return resource._resourcesView;
    },

    sourceFrameForResource: function(resource)
    {
        var view = this.resourceViewForResource(resource);
        if (!view)
            return null;

        if (!view.setupSourceFrameIfNeeded)
            return null;

        // Setting up the source frame requires that we be attached.
        if (!this.element.parentNode)
            this.attach();

        view.setupSourceFrameIfNeeded();
        return view.sourceFrame;
    },

    _sortResourcesIfNeeded: function()
    {
        this.sortItems(this.sortingFunction);
    },

    updateGraphDividersIfNeeded: function(force)
    {
        var proceed = true;
        if (!this.visible) {
            this.needsRefresh = true;
            proceed = false;
        } else
            proceed = this._timelineGrid.updateDividers(force, this.calculator);
        
        if (!proceed)
            return;

        if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
            // If our current sorting method starts at zero, that means it shows all
            // resources starting at the same point, and so onLoad event and DOMContent
            // event lines really wouldn't make much sense here, so don't render them.
            // Additionally, if the calculator doesn't have the computePercentageFromEventTime
            // function defined, we are probably sorting by size, and event times aren't relevant
            // in this case.
            return;
        }

        this._timelineGrid.removeEventDividers();
        if (this.mainResourceLoadTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime);

            var loadDivider = document.createElement("div");
            loadDivider.className = "resources-event-divider resources-red-divider";

            var loadDividerPadding = document.createElement("div");
            loadDividerPadding.className = "resources-event-divider-padding";
            loadDividerPadding.style.left = percent + "%";
            loadDividerPadding.title = WebInspector.UIString("Load event fired");
            loadDividerPadding.appendChild(loadDivider);

            this.addEventDivider(loadDividerPadding);
        }
        
        if (this.mainResourceDOMContentTime !== -1) {
            var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime);

            var domContentDivider = document.createElement("div");
            domContentDivider.className = "resources-event-divider resources-blue-divider";
            
            var domContentDividerPadding = document.createElement("div");
            domContentDividerPadding.className = "resources-event-divider-padding";
            domContentDividerPadding.style.left = percent + "%";
            domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
            domContentDividerPadding.appendChild(domContentDivider);

            this.addEventDivider(domContentDividerPadding);
        }
    },

    _graphSelected: function(treeElement)
    {
        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;

        this._lastSelectedGraphTreeElement = treeElement;

        this.sortingSelectElement.removeChildren();
        for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
            var sortingOption = treeElement.sortingOptions[i];
            var option = document.createElement("option");
            option.label = sortingOption.name;
            option.sortingFunction = sortingOption.sortingFunction;
            option.calculator = sortingOption.calculator;
            option.optionName = sortingOption.optionName;
            this.sortingSelectElement.appendChild(option);
        }

        this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
        this._doChangeSortingFunction();

        this.closeVisibleResource();
        this.containerElement.scrollTop = 0;

        if (treeElement === this.sizeGraphItem)
            this.hideEventDividers();
        else
            this.showEventDividers();
    },

    _toggleLargerResources: function()
    {
        if (!this.itemsTreeElement._childrenListNode)
            return;

        WebInspector.applicationSettings.resourcesLargeRows = !WebInspector.applicationSettings.resourcesLargeRows;
        this._setLargerResources(this.itemsTreeElement.smallChildren);
    },

    _setLargerResources: function(enabled)
    {
        this.largerResourcesButton.toggled = enabled;
        this.itemsTreeElement.smallChildren = !enabled;
        if (!enabled) {
            this.itemsGraphsElement.addStyleClass("small");
            this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
            this.adjustScrollPosition();
        } else {
            this.itemsGraphsElement.removeStyleClass("small");
            this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
        }
    },

    _changeSortingFunction: function()
    {
        this._doChangeSortingFunction();
        WebInspector.applicationSettings.resourcesSortOptions = {timeOption: this._selectedOptionNameForGraph(this.timeGraphItem), sizeOption: this._selectedOptionNameForGraph(this.sizeGraphItem)};
    },

    _selectedOptionNameForGraph: function(graphItem)
    {
        return graphItem.sortingOptions[graphItem.selectedSortingOptionIndex].optionName;
    },

    _doChangeSortingFunction: function()
    {
        var selectedIndex = this.sortingSelectElement.selectedIndex;
        if (this._lastSelectedGraphTreeElement)
            this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = selectedIndex;
        var selectedOption = this.sortingSelectElement[selectedIndex];
        this.sortingFunction = selectedOption.sortingFunction;
        this.calculator = this.summaryBar.calculator = selectedOption.calculator;
    },

    _createResourceView: function(resource)
    {
        switch (resource.category) {
            case WebInspector.resourceCategories.documents:
            case WebInspector.resourceCategories.stylesheets:
            case WebInspector.resourceCategories.scripts:
            case WebInspector.resourceCategories.xhr:
                return new WebInspector.SourceView(resource);
            case WebInspector.resourceCategories.images:
                return new WebInspector.ImageView(resource);
            case WebInspector.resourceCategories.fonts:
                return new WebInspector.FontView(resource);
            default:
                return new WebInspector.ResourceView(resource);
        }
    },

    setSidebarWidth: function(width)
    {
        if (this.visibleResource) {
            this.containerElement.style.width = width + "px";
            this.sidebarElement.style.removeProperty("width");
        } else {
            this.sidebarElement.style.width = width + "px";
            this.containerElement.style.removeProperty("width");
        }

        this.sidebarResizeElement.style.left = (width - 3) + "px";
    },

    updateMainViewWidth: function(width)
    {
        this.viewsContainerElement.style.left = width + "px";
        this._containerContentElement.style.left = width + "px";
        this.resize();
    },

    _enableResourceTracking: function()
    {
        if (this._resourceTrackingEnabled)
            return;
        this.toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
    },

    toggleResourceTracking: function(optionalAlways)
    {
        function callback(newState) {
            if (newState)
                WebInspector.panels.resources.resourceTrackingWasEnabled();
            else
                WebInspector.panels.resources.resourceTrackingWasDisabled();
        }

        if (this._resourceTrackingEnabled) {
            this.largerResourcesButton.visible = false;
            this.sortingSelectElement.visible = false;
            WebInspector.resources = {};
            WebInspector.resourceURLMap = {};
            InspectorBackend.setResourceTrackingEnabled(false, true, callback);
        } else {
            this.largerResourcesButton.visible = true;
            this.sortingSelectElement.visible = true;
            InspectorBackend.setResourceTrackingEnabled(true, !!optionalAlways, callback);
        }
    },

    get _resources()
    {
        return this.items;
    },

    elementsToRestoreScrollPositionsFor: function()
    {
        return [ this.containerElement ];
    },

    _getPopoverAnchor: function(element)
    {
        var anchor = element.enclosingNodeOrSelfWithClass("resources-graph-bar") || element.enclosingNodeOrSelfWithClass("resources-graph-label");
        if (!anchor)
            return null;
        var resource = anchor.parentElement.resource;
        return resource && resource.timing ? anchor : null;
    },

    _showPopover: function(anchor)
    {
        var tableElement = document.createElement("table");
        var resource = anchor.parentElement.resource;
        var rows = [];

        function addRow(title, start, end, color)
        {
            var row = {};
            row.title = title;
            row.start = start;
            row.end = end;
            rows.push(row);
        }

        if (resource.timing.proxyStart !== -1)
            addRow(WebInspector.UIString("Proxy"), resource.timing.proxyStart, resource.timing.proxyEnd);

        if (resource.timing.dnsStart !== -1) {
            addRow(WebInspector.UIString("DNS Lookup"), resource.timing.dnsStart, resource.timing.dnsEnd);
        }

        if (resource.timing.connectStart !== -1) {
            if (resource.connectionReused)
                addRow(WebInspector.UIString("Blocking"), resource.timing.connectStart, resource.timing.connectEnd);
            else {
                var connectStart = resource.timing.connectStart;
                // Connection includes DNS, subtract it here.
                if (resource.timing.dnsStart !== -1)
                    connectStart += resource.timing.dnsEnd - resource.timing.dnsStart;
                addRow(WebInspector.UIString("Connecting"), connectStart, resource.timing.connectEnd);
            }
        }

        if (resource.timing.sslStart !== -1)
            addRow(WebInspector.UIString("SSL"), resource.timing.sslStart, resource.timing.sslEnd);

        var sendStart = resource.timing.sendStart;
        if (resource.timing.sslStart !== -1)
            sendStart += resource.timing.sslEnd - resource.timing.sslStart;
        
        addRow(WebInspector.UIString("Sending"), resource.timing.sendStart, resource.timing.sendEnd);
        addRow(WebInspector.UIString("Waiting"), resource.timing.sendEnd, resource.timing.receiveHeadersEnd);
        addRow(WebInspector.UIString("Receiving"), (resource.responseReceivedTime - resource.timing.requestTime) * 1000, (resource.endTime - resource.timing.requestTime) * 1000);

        const chartWidth = 200;
        var total = (resource.endTime - resource.timing.requestTime) * 1000;
        var scale = chartWidth / total;

        for (var i = 0; i < rows.length; ++i) {
            var tr = document.createElement("tr");
            tableElement.appendChild(tr);

            var td = document.createElement("td");
            td.textContent = rows[i].title;
            tr.appendChild(td);

            td = document.createElement("td");
            td.width = chartWidth + "px";

            var row = document.createElement("div");
            row.className = "resource-timing-row";
            td.appendChild(row);

            var bar = document.createElement("span");
            bar.className = "resource-timing-bar";
            bar.style.left = scale * rows[i].start + "px";
            bar.style.right = scale * (total - rows[i].end) + "px";
            bar.style.backgroundColor = rows[i].color;
            bar.textContent = "\u200B"; // Important for 0-time items to have 0 width.
            row.appendChild(bar);

            var title = document.createElement("span");
            title.className = "resource-timing-bar-title";
            if (total - rows[i].end < rows[i].start)
                title.style.right = (scale * (total - rows[i].end) + 3) + "px";
            else
                title.style.left = (scale * rows[i].start + 3) + "px";
            title.textContent = Number.millisToString(rows[i].end - rows[i].start);
            row.appendChild(title);

            tr.appendChild(td);
        }

        var popover = new WebInspector.Popover(tableElement);
        popover.show(anchor);
        return popover;
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);
        this._popoverHelper.hidePopup();
    },

    _contextMenu: function(event)
    {
        var contextMenu = new WebInspector.ContextMenu();
        var resourceTreeItem = event.target.enclosingNodeOrSelfWithClass("resource-sidebar-tree-item");
        var resource;
        if (resourceTreeItem && resourceTreeItem.treeElement)
            resource = resourceTreeItem.treeElement.representedObject;

        var needSeparator = false;
        // createObjectURL is enabled conditionally, do not expose resource export if it's not available.
        if (typeof window.createObjectURL === "function" && Preferences.resourceExportEnabled) {
            if (resource)
                contextMenu.appendItem(WebInspector.UIString("Export to HAR"), this._exportResource.bind(this, resource));
            contextMenu.appendItem(WebInspector.UIString("Export all to HAR"), this._exportAll.bind(this));
            needSeparator = true;
        }

        if (resource && resource.category === WebInspector.resourceCategories.xhr) {
            if (needSeparator)
                contextMenu.appendSeparator();
            contextMenu.appendItem(WebInspector.UIString("Set XHR Breakpoint"), WebInspector.breakpointManager.createXHRBreakpoint.bind(WebInspector.breakpointManager, resource.url));
        }

        contextMenu.show(event);
    },

    _exportAll: function()
    {
        var harArchive = {
            log: (new WebInspector.HARLog()).build()
        }
        offerFileForDownload(JSON.stringify(harArchive));
    },

    _exportResource: function(resource)
    {
        var har = (new WebInspector.HAREntry(resource)).build();
        offerFileForDownload(JSON.stringify(har));
    }
}

WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.getResourceContent = function(identifier, callback)
{
    InspectorBackend.getResourceContent(identifier, false, callback);
}

WebInspector.ResourceBaseCalculator = function()
{
}

WebInspector.ResourceBaseCalculator.prototype = {
    computeSummaryValues: function(items)
    {
        var total = 0;
        var categoryValues = {};

        var itemsLength = items.length;
        for (var i = 0; i < itemsLength; ++i) {
            var item = items[i];
            var value = this._value(item);
            if (typeof value === "undefined")
                continue;
            if (!(item.category.name in categoryValues))
                categoryValues[item.category.name] = 0;
            categoryValues[item.category.name] += value;
            total += value;
        }

        return {categoryValues: categoryValues, total: total};
    },

    computeBarGraphPercentages: function(item)
    {
        return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
    },

    computeBarGraphLabels: function(item)
    {
        const label = this.formatValue(this._value(item));
        return {left: label, right: label, tooltip: label};
    },

    get boundarySpan()
    {
        return this.maximumBoundary - this.minimumBoundary;
    },

    updateBoundaries: function(item)
    {
        this.minimumBoundary = 0;

        var value = this._value(item);
        if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
            this.maximumBoundary = value;
            return true;
        }
        return false;
    },

    reset: function()
    {
        delete this.minimumBoundary;
        delete this.maximumBoundary;
    },

    _value: function(item)
    {
        return 0;
    },

    formatValue: function(value)
    {
        return value.toString();
    }
}

WebInspector.ResourceTimeCalculator = function(startAtZero)
{
    WebInspector.ResourceBaseCalculator.call(this);
    this.startAtZero = startAtZero;
}

WebInspector.ResourceTimeCalculator.prototype = {
    computeSummaryValues: function(resources)
    {
        var resourcesByCategory = {};
        var resourcesLength = resources.length;
        for (var i = 0; i < resourcesLength; ++i) {
            var resource = resources[i];
            if (!(resource.category.name in resourcesByCategory))
                resourcesByCategory[resource.category.name] = [];
            resourcesByCategory[resource.category.name].push(resource);
        }

        var earliestStart;
        var latestEnd;
        var categoryValues = {};
        for (var category in resourcesByCategory) {
            resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
            categoryValues[category] = 0;

            var segment = {start: -1, end: -1};

            var categoryResources = resourcesByCategory[category];
            var resourcesLength = categoryResources.length;
            for (var i = 0; i < resourcesLength; ++i) {
                var resource = categoryResources[i];
                if (resource.startTime === -1 || resource.endTime === -1)
                    continue;

                if (typeof earliestStart === "undefined")
                    earliestStart = resource.startTime;
                else
                    earliestStart = Math.min(earliestStart, resource.startTime);

                if (typeof latestEnd === "undefined")
                    latestEnd = resource.endTime;
                else
                    latestEnd = Math.max(latestEnd, resource.endTime);

                if (resource.startTime <= segment.end) {
                    segment.end = Math.max(segment.end, resource.endTime);
                    continue;
                }

                categoryValues[category] += segment.end - segment.start;

                segment.start = resource.startTime;
                segment.end = resource.endTime;
            }

            // Add the last segment
            categoryValues[category] += segment.end - segment.start;
        }

        return {categoryValues: categoryValues, total: latestEnd - earliestStart};
    },

    computeBarGraphPercentages: function(resource)
    {
        if (resource.startTime !== -1)
            var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var start = 0;

        if (resource.responseReceivedTime !== -1)
            var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var middle = (this.startAtZero ? start : 100);

        if (resource.endTime !== -1)
            var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
        else
            var end = (this.startAtZero ? middle : 100);

        if (this.startAtZero) {
            end -= start;
            middle -= start;
            start = 0;
        }

        return {start: start, middle: middle, end: end};
    },
    
    computePercentageFromEventTime: function(eventTime)
    {
        // This function computes a percentage in terms of the total loading time
        // of a specific event. If startAtZero is set, then this is useless, and we
        // want to return 0.
        if (eventTime !== -1 && !this.startAtZero)
            return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;

        return 0;
    },

    computeBarGraphLabels: function(resource)
    {
        var rightLabel = "";
        if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
            rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);

        var hasLatency = resource.latency > 0;
        if (hasLatency)
            var leftLabel = this.formatValue(resource.latency);
        else
            var leftLabel = rightLabel;

        if (resource.timing)
            return {left: leftLabel, right: rightLabel};

        if (hasLatency && rightLabel) {
            var total = this.formatValue(resource.duration);
            var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
        } else if (hasLatency)
            var tooltip = WebInspector.UIString("%s latency", leftLabel);
        else if (rightLabel)
            var tooltip = WebInspector.UIString("%s download", rightLabel);

        if (resource.cached)
            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
        return {left: leftLabel, right: rightLabel, tooltip: tooltip};
    },

    updateBoundaries: function(resource)
    {
        var didChange = false;

        var lowerBound;
        if (this.startAtZero)
            lowerBound = 0;
        else
            lowerBound = this._lowerBound(resource);

        if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
            this.minimumBoundary = lowerBound;
            didChange = true;
        }

        var upperBound = this._upperBound(resource);
        if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
            this.maximumBoundary = upperBound;
            didChange = true;
        }

        return didChange;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _lowerBound: function(resource)
    {
        return 0;
    },

    _upperBound: function(resource)
    {
        return 0;
    }
}

WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceBaseCalculator.prototype;

WebInspector.ResourceTransferTimeCalculator = function()
{
    WebInspector.ResourceTimeCalculator.call(this, false);
}

WebInspector.ResourceTransferTimeCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _lowerBound: function(resource)
    {
        return resource.startTime;
    },

    _upperBound: function(resource)
    {
        return resource.endTime;
    }
}

WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;

WebInspector.ResourceTransferDurationCalculator = function()
{
    WebInspector.ResourceTimeCalculator.call(this, true);
}

WebInspector.ResourceTransferDurationCalculator.prototype = {
    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    },

    _upperBound: function(resource)
    {
        return resource.duration;
    }
}

WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;

WebInspector.ResourceTransferSizeCalculator = function()
{
    WebInspector.ResourceBaseCalculator.call(this);
}

WebInspector.ResourceTransferSizeCalculator.prototype = {
    computeBarGraphLabels: function(resource)
    {
        var networkBytes = this._networkBytes(resource);
        var resourceBytes = this._value(resource);
        if (networkBytes && networkBytes !== resourceBytes) {
            // Transferred size is not the same as reported resource length.
            var networkBytesString = this.formatValue(networkBytes);
            var left = networkBytesString;
            var right = this.formatValue(resourceBytes);
            var tooltip = right ? WebInspector.UIString("%s (%s transferred)", right, networkBytesString) : right;
        } else {
            var left = this.formatValue(resourceBytes);
            var right = left;
            var tooltip = left;
        }
        if (resource.cached)
            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
        return {left: left, right: right, tooltip: tooltip};
    },

    computeBarGraphPercentages: function(item)
    {
        const resourceBytesAsPercent = (this._value(item) / this.boundarySpan) * 100;
        const networkBytesAsPercent = this._networkBytes(item) ? (this._networkBytes(item) / this.boundarySpan) * 100 : resourceBytesAsPercent;
        return {start: 0, middle: networkBytesAsPercent, end: resourceBytesAsPercent};
    },

    _value: function(resource)
    {
        return resource.resourceSize;
    },

    _networkBytes: function(resource)
    {
        return resource.transferSize;
    },

    formatValue: function(value)
    {
        return Number.bytesToString(value, WebInspector.UIString);
    }
}

WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceBaseCalculator.prototype;

WebInspector.ResourceSidebarTreeElement = function(resource)
{
    this.resource = resource;

    this.createIconElement();

    WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);

    this.refreshTitles();
}

WebInspector.ResourceSidebarTreeElement.prototype = {
    onattach: function()
    {
        WebInspector.SidebarTreeElement.prototype.onattach.call(this);

        this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
        this._listItemNode.draggable = true;
        
        // FIXME: should actually add handler to parent, to be resolved via
        // https://bugs.webkit.org/show_bug.cgi?id=30227
        this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false);
        this.updateErrorsAndWarnings();
    },

    onselect: function()
    {
        WebInspector.panels.resources.showResource(this.resource);
    },
    
    ondblclick: function(event)
    {
        InspectorBackend.openInInspectedWindow(this.resource.url);
    },

    ondragstart: function(event) {
        event.dataTransfer.setData("text/plain", this.resource.url);
        event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n");
        event.dataTransfer.effectAllowed = "copy";
        return true;
    },

    get mainTitle()
    {
        return this.resource.displayName;
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        var subtitle = this.resource.displayDomain;

        if (this.resource.path && this.resource.lastPathComponent) {
            var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
            if (lastPathComponentIndex != -1)
                subtitle += this.resource.path.substring(0, lastPathComponentIndex);
        }

        return subtitle;
    },

    set subtitle(x)
    {
        // Do nothing.
    },

    get selectable()
    {
        return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name);
    },

    createIconElement: function()
    {
        var previousIconElement = this.iconElement;

        if (this.resource.category === WebInspector.resourceCategories.images) {
            var previewImage = document.createElement("img");
            previewImage.className = "image-resource-icon-preview";
            previewImage.src = this.resource.url;

            this.iconElement = document.createElement("div");
            this.iconElement.className = "icon";
            this.iconElement.appendChild(previewImage);
        } else {
            this.iconElement = document.createElement("img");
            this.iconElement.className = "icon";
        }

        if (previousIconElement)
            previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
    },

    refresh: function()
    {
        this.refreshTitles();

        if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
            this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
            this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);

            this.createIconElement();
        }

        this.tooltip = this.resource.url;
    },

    resetBubble: function()
    {
        this.bubbleText = "";
        this.bubbleElement.removeStyleClass("search-matches");
        this.bubbleElement.removeStyleClass("warning");
        this.bubbleElement.removeStyleClass("error");
    },

    set searchMatches(matches)
    {
        this.resetBubble();

        if (!matches)
            return;

        this.bubbleText = matches;
        this.bubbleElement.addStyleClass("search-matches");
    },

    updateErrorsAndWarnings: function()
    {
        this.resetBubble();

        if (this.resource.warnings || this.resource.errors)
            this.bubbleText = (this.resource.warnings + this.resource.errors);

        if (this.resource.warnings)
            this.bubbleElement.addStyleClass("warning");

        if (this.resource.errors)
            this.bubbleElement.addStyleClass("error");
    }
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
{
    return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
{
    return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
{
    return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
{
    return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
{
    return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
{
    return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize = function(a, b)
{
    return -1 * WebInspector.Resource.CompareByTransferSize(a.resource, b.resource);
}

WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.ResourceGraph = function(resource)
{
    this.resource = resource;

    this._graphElement = document.createElement("div");
    this._graphElement.className = "resources-graph-side";
    this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);

    this._cachedChanged();

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "resources-graph-bar-area hidden";
    this._barAreaElement.resource = resource;
    this._graphElement.appendChild(this._barAreaElement);

    this._barLeftElement = document.createElement("div");
    this._barLeftElement.className = "resources-graph-bar waiting";
    this._barAreaElement.appendChild(this._barLeftElement);

    this._barRightElement = document.createElement("div");
    this._barRightElement.className = "resources-graph-bar";
    this._barAreaElement.appendChild(this._barRightElement);

    this._labelLeftElement = document.createElement("div");
    this._labelLeftElement.className = "resources-graph-label waiting";
    this._barAreaElement.appendChild(this._labelLeftElement);

    this._labelRightElement = document.createElement("div");
    this._labelRightElement.className = "resources-graph-label";
    this._barAreaElement.appendChild(this._labelRightElement);

    this._graphElement.addStyleClass("resources-category-" + resource.category.name);

    resource.addEventListener("cached changed", this._cachedChanged, this);
}

WebInspector.ResourceGraph.prototype = {
    get graphElement()
    {
        return this._graphElement;
    },

    refreshLabelPositions: function()
    {
        this._labelLeftElement.style.removeProperty("left");
        this._labelLeftElement.style.removeProperty("right");
        this._labelLeftElement.removeStyleClass("before");
        this._labelLeftElement.removeStyleClass("hidden");

        this._labelRightElement.style.removeProperty("left");
        this._labelRightElement.style.removeProperty("right");
        this._labelRightElement.removeStyleClass("after");
        this._labelRightElement.removeStyleClass("hidden");

        const labelPadding = 10;
        const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
        const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;

        if (this._isBarOpaqueAtLeft) {
            var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
            var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
        } else {
            var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
            var rightBarWidth = barRightElementOffsetWidth - labelPadding;
        }

        const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
        const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;

        const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
        const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
        const graphElementOffsetWidth = this._graphElement.offsetWidth;

        if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
            var leftHidden = true;

        if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
            var rightHidden = true;

        if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
            // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
            if (labelBefore && !labelAfter)
                leftHidden = true;
            else if (labelAfter && !labelBefore)
                rightHidden = true;
        }

        if (labelBefore) {
            if (leftHidden)
                this._labelLeftElement.addStyleClass("hidden");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
            this._labelLeftElement.addStyleClass("before");
        } else {
            this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
        }

        if (labelAfter) {
            if (rightHidden)
                this._labelRightElement.addStyleClass("hidden");
            this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
            this._labelRightElement.addStyleClass("after");
        } else {
            this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
            this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
        }
    },

    refresh: function(calculator, isBarOpaqueAtLeft)
    {
        var percentages = calculator.computeBarGraphPercentages(this.resource);
        var labels = calculator.computeBarGraphLabels(this.resource);

        this._percentages = percentages;

        this._barAreaElement.removeStyleClass("hidden");

        if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
            this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
            this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
        }

        this._barLeftElement.style.setProperty("left", percentages.start + "%");
        this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");

        if (!isBarOpaqueAtLeft) {
            this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
            this._barRightElement.style.setProperty("left", percentages.middle + "%");

            if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
                this._barLeftElement.addStyleClass("waiting");
                this._barRightElement.removeStyleClass("waiting-right");
                this._labelLeftElement.addStyleClass("waiting");
                this._labelRightElement.removeStyleClass("waiting-right");
            }
        } else {
            this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%");
            this._barRightElement.style.setProperty("left", percentages.start + "%");

            if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
                this._barLeftElement.removeStyleClass("waiting");
                this._barRightElement.addStyleClass("waiting-right");
                this._labelLeftElement.removeStyleClass("waiting");
                this._labelRightElement.addStyleClass("waiting-right");
            }
        }

        this._isBarOpaqueAtLeft = isBarOpaqueAtLeft;

        this._labelLeftElement.textContent = labels.left;
        this._labelRightElement.textContent = labels.right;

        var tooltip = (labels.tooltip || "");
        this._barLeftElement.title = tooltip;
        this._labelLeftElement.title = tooltip;
        this._labelRightElement.title = tooltip;
        this._barRightElement.title = tooltip;
    },

    _cachedChanged: function()
    {
        if (this.resource.cached)
            this._graphElement.addStyleClass("resource-cached");
    }
}

/* InjectedFakeWorker.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
{

Worker = function(url)
{
    var impl = new FakeWorker(this, url);
    if (impl === null)
        return null;

    this.isFake = true;
    this.postMessage = bind(impl.postMessage, impl);
    this.terminate = bind(impl.terminate, impl);

    function onmessageGetter()
    {
        return impl.channel.port1.onmessage;
    }
    function onmessageSetter(callback)
    {
        impl.channel.port1.onmessage = callback;
    }
    this.__defineGetter__("onmessage", onmessageGetter);
    this.__defineSetter__("onmessage", onmessageSetter);
    this.addEventListener = bind(impl.channel.port1.addEventListener, impl.channel.port1);
    this.removeEventListener = bind(impl.channel.port1.removeEventListener, impl.channel.port1);
    this.dispatchEvent = bind(impl.channel.port1.dispatchEvent, impl.channel.port1);
}

function FakeWorker(worker, url)
{
    var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url);

    this._worker = worker;
    this._id = InjectedScriptHost.nextWorkerId();
    this.channel = new MessageChannel();
    this._listeners = [];
    this._buildWorker(scriptURL);

    InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false);
}

FakeWorker.prototype = {
    postMessage: function(msg, opt_ports)
    {
        if (this._frame != null)
            this.channel.port1.postMessage.apply(this.channel.port1, arguments);
        else if (this._pendingMessages)
            this._pendingMessages.push(arguments)
        else
            this._pendingMessages = [ arguments ];
    },

    terminate: function()
    {
        InjectedScriptHost.didDestroyWorker(this._id);

        this.channel.port1.close();
        this.channel.port2.close();
        if (this._frame != null)
            this._frame.frameElement.parentNode.removeChild(this._frame.frameElement);
        this._frame = null;
        this._worker = null; // Break reference loop.
    },

    _buildWorker: function(url)
    {
        var code = this._loadScript(url.url);
        var iframeElement = document.createElement("iframe");
        iframeElement.style.display = "none";

        this._document = document;
        iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code);

        if (document.body)
            this._attachWorkerFrameToDocument(iframeElement, url, code);
        else
            window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false);
    },

    _attachWorkerFrameToDocument: function(iframeElement)
    {
        document.body.appendChild(iframeElement);
    },

    _onWorkerFrameLoaded: function(iframeElement, url, code)
    {
        var frame = iframeElement.contentWindow;
        this._frame = frame;
        this._setupWorkerContext(frame, url);

        var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url;

        frame.eval(frameContents);
        if (this._pendingMessages) {
            for (var msg = 0; msg < this._pendingMessages.length; ++msg)
                this.postMessage.apply(this, this._pendingMessages[msg]);
            delete this._pendingMessages;
        }
    },

    _setupWorkerContext: function(workerFrame, url)
    {
        workerFrame.__devtools = {
            handleException: bind(this._handleException, this),
            location: url.mockLocation()
        };

        var self = this;

        function onmessageGetter()
        {
            return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null;
        }

        function onmessageSetter(callback)
        {
            var wrappedCallback = bind(self._callbackWrapper, self, callback);
            wrappedCallback.originalCallback = callback;
            self.channel.port2.onmessage = wrappedCallback;
        }

        workerFrame.__defineGetter__("onmessage", onmessageGetter);
        workerFrame.__defineSetter__("onmessage", onmessageSetter);
        workerFrame.addEventListener = bind(this._addEventListener, this);
        workerFrame.removeEventListener = bind(this._removeEventListener, this);
        workerFrame.dispatchEvent = bind(this.channel.port2.dispatchEvent, this.channel.port2);
        workerFrame.postMessage = bind(this.channel.port2.postMessage, this.channel.port2);
        workerFrame.importScripts = bind(this._importScripts, this, workerFrame);
        workerFrame.close = bind(this.terminate, this);
    },

    _addEventListener: function(type, callback, useCapture)
    {
        var wrappedCallback = bind(this._callbackWrapper, this, callback);
        wrappedCallback.originalCallback = callback;
        wrappedCallback.type = type;
        wrappedCallback.useCapture = Boolean(useCapture);

        this.channel.port2.addEventListener(type, wrappedCallback, useCapture);
        this._listeners.push(wrappedCallback);
    },

    _removeEventListener: function(type, callback, useCapture)
    {
        var listeners = this._listeners;
        for (var i = 0; i < listeners.length; ++i) {
            if (listeners[i].originalCallback === callback &&
                listeners[i].type === type && 
                listeners[i].useCapture === Boolean(useCapture)) {
                this.channel.port2.removeEventListener(type, listeners[i], useCapture);
                listeners[i] = listeners[listeners.length - 1];
                listeners.pop();
                break;
            }
        }
    },

    _callbackWrapper: function(callback, msg)
    {
        // Shortcut -- if no exception handlers installed, avoid try/catch so as not to obscure line number.
        if (!this._frame.onerror && !this._worker.onerror) {
            callback(msg);
            return;
        }

        try {
            callback(msg);
        } catch (e) {
            this._handleException(e, this._frame.onerror, this._worker.onerror);
        }
    },

    _handleException: function(e)
    {
        // NB: it should be an ErrorEvent, but creating it from script is not
        // currently supported, so emulate it on top of plain vanilla Event.
        var errorEvent = this._document.createEvent("Event");
        errorEvent.initEvent("Event", false, false);
        errorEvent.message = "Uncaught exception";

        for (var i = 1; i < arguments.length; ++i) {
            if (arguments[i] && arguments[i](errorEvent))
                return;
        }

        throw e;
    },

    _importScripts: function(targetFrame)
    {
        for (var i = 1; i < arguments.length; ++i) {
            var workerOrigin = targetFrame.__devtools.location.href;
            var url = this._expandURLAndCheckOrigin(workerOrigin, workerOrigin, arguments[i]);
            targetFrame.eval(this._loadScript(url.url) + "\n//@ sourceURL= " + url.url);
        }
    },

    _loadScript: function(url)
    {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);
        xhr.send(null);

        var text = xhr.responseText;
        if (xhr.status != 0 && xhr.status/100 !== 2) { // We're getting status === 0 when using file://.
            console.error("Failed to load worker: " + url + "[" + xhr.status + "]");
            text = ""; // We've got error message, not worker code.
        }
        return text;
    },

    _expandURLAndCheckOrigin: function(baseURL, origin, url)
    {
        var scriptURL = new URL(baseURL).completeWith(url);

        if (!scriptURL.sameOrigin(origin))
            throw new DOMCoreException("SECURITY_ERR",18);
        return scriptURL;
    }
};

function URL(url)
{
    this.url = url;
    this.split();
}

URL.prototype = {
    urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i),

    split: function()
    {
        function emptyIfNull(str)
        {
            return str == null ? "" : str;
        }
        var parts = this.urlRegEx.exec(this.url);

        this.schema = parts[1];
        this.host = parts[2];
        this.port = emptyIfNull(parts[3]);
        this.path = emptyIfNull(parts[4]);
        this.query = emptyIfNull(parts[5]);
        this.fragment = emptyIfNull(parts[6]);
    },

    mockLocation: function()
    {
        var host = this.host.replace(/^[^@]*@/, "");

        return {
            href:     this.url,
            protocol: this.schema + ":",
            host:     host,
            hostname: host,
            port:     this.port,
            pathname: this.path,
            search:   this.query,
            hash:     this.fragment
        };
    },

    completeWith: function(url)
    {
        if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now.
            return new URL(url);

        var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ]

        var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1];
        path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, "");

        return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]);
    },

    sameOrigin: function(url)
    {
        function normalizePort(schema, port)
        {
            var portNo = port.slice(1);
            return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port;
        }

        var other = new URL(url);

        return this.schema === other.schema &&
            this.host === other.host &&
            normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port);
    }
};

function DOMCoreException(name, code)
{
    function formatError()
    {
        return "Error: " + this.message;
    }

    this.name = name;
    this.message = name + ": DOM Exception " + code;
    this.code = code;
    this.toString = bind(formatError, this);
}

function bind(func, thisObject)
{
    var args = Array.prototype.slice.call(arguments, 2);
    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };
}

function noop()
{
}

}

/* ScriptsPanel.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScriptsPanel = function()
{
    WebInspector.Panel.call(this, "scripts");

    this.topStatusBar = document.createElement("div");
    this.topStatusBar.className = "status-bar";
    this.topStatusBar.id = "scripts-status-bar";
    this.element.appendChild(this.topStatusBar);

    this.backButton = document.createElement("button");
    this.backButton.className = "status-bar-item";
    this.backButton.id = "scripts-back";
    this.backButton.title = WebInspector.UIString("Show the previous script resource.");
    this.backButton.disabled = true;
    this.backButton.appendChild(document.createElement("img"));
    this.backButton.addEventListener("click", this._goBack.bind(this), false);
    this.topStatusBar.appendChild(this.backButton);

    this.forwardButton = document.createElement("button");
    this.forwardButton.className = "status-bar-item";
    this.forwardButton.id = "scripts-forward";
    this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
    this.forwardButton.disabled = true;
    this.forwardButton.appendChild(document.createElement("img"));
    this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
    this.topStatusBar.appendChild(this.forwardButton);

    this.filesSelectElement = document.createElement("select");
    this.filesSelectElement.className = "status-bar-item";
    this.filesSelectElement.id = "scripts-files";
    this.filesSelectElement.addEventListener("change", this._changeVisibleFile.bind(this), false);
    this.topStatusBar.appendChild(this.filesSelectElement);

    this.functionsSelectElement = document.createElement("select");
    this.functionsSelectElement.className = "status-bar-item";
    this.functionsSelectElement.id = "scripts-functions";

    // FIXME: append the functions select element to the top status bar when it is implemented.
    // this.topStatusBar.appendChild(this.functionsSelectElement);

    this.sidebarButtonsElement = document.createElement("div");
    this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
    this.topStatusBar.appendChild(this.sidebarButtonsElement);

    this.pauseButton = document.createElement("button");
    this.pauseButton.className = "status-bar-item";
    this.pauseButton.id = "scripts-pause";
    this.pauseButton.title = WebInspector.UIString("Pause script execution.");
    this.pauseButton.disabled = true;
    this.pauseButton.appendChild(document.createElement("img"));
    this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
    this.sidebarButtonsElement.appendChild(this.pauseButton);

    this.stepOverButton = document.createElement("button");
    this.stepOverButton.className = "status-bar-item";
    this.stepOverButton.id = "scripts-step-over";
    this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
    this.stepOverButton.disabled = true;
    this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
    this.stepOverButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepOverButton);

    this.stepIntoButton = document.createElement("button");
    this.stepIntoButton.className = "status-bar-item";
    this.stepIntoButton.id = "scripts-step-into";
    this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
    this.stepIntoButton.disabled = true;
    this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
    this.stepIntoButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepIntoButton);

    this.stepOutButton = document.createElement("button");
    this.stepOutButton.className = "status-bar-item";
    this.stepOutButton.id = "scripts-step-out";
    this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
    this.stepOutButton.disabled = true;
    this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
    this.stepOutButton.appendChild(document.createElement("img"));
    this.sidebarButtonsElement.appendChild(this.stepOutButton);

    this.toggleBreakpointsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Deactivate all breakpoints."), "toggle-breakpoints");
    this.toggleBreakpointsButton.toggled = true;
    this.toggleBreakpointsButton.addEventListener("click", this.toggleBreakpointsClicked.bind(this), false);
    this.sidebarButtonsElement.appendChild(this.toggleBreakpointsButton.element);

    this.debuggerStatusElement = document.createElement("div");
    this.debuggerStatusElement.id = "scripts-debugger-status";
    this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);

    this.viewsContainerElement = document.createElement("div");
    this.viewsContainerElement.id = "script-resource-views";

    this.sidebarElement = document.createElement("div");
    this.sidebarElement.id = "scripts-sidebar";

    this.sidebarResizeElement = document.createElement("div");
    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);

    this.sidebarResizeWidgetElement = document.createElement("div");
    this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
    this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
    this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);

    this.sidebarPanes = {};
    this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
    this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
    this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
    this.sidebarPanes.jsBreakpoints = WebInspector.createJSBreakpointsSidebarPane();
    if (Preferences.nativeInstrumentationEnabled) {
        this.sidebarPanes.domBreakpoints = WebInspector.createDOMBreakpointsSidebarPane();
        this.sidebarPanes.xhrBreakpoints = WebInspector.createXHRBreakpointsSidebarPane();
    }
    this.sidebarPanes.eventListenerBreakpoints = new WebInspector.EventListenerBreakpointsSidebarPane();

    this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane();

    for (var pane in this.sidebarPanes)
        this.sidebarElement.appendChild(this.sidebarPanes[pane].element);

    this.sidebarPanes.callstack.expanded = true;
    this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);

    this.sidebarPanes.scopechain.expanded = true;
    this.sidebarPanes.jsBreakpoints.expanded = true;

    var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
    var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
    var panelEnablerButton = WebInspector.UIString("Enable Debugging");

    this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);

    this.element.appendChild(this.panelEnablerView.element);
    this.element.appendChild(this.viewsContainerElement);
    this.element.appendChild(this.sidebarElement);
    this.element.appendChild(this.sidebarResizeElement);

    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
    if (Preferences.debuggerAlwaysEnabled)
        this.enableToggleButton.element.addStyleClass("hidden");

    this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3);
    this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
    this._pauseOnExceptionButton.state = WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions;
    this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");

    this._registerShortcuts();

    this._debuggerEnabled = Preferences.debuggerAlwaysEnabled;

    WebInspector.breakpointManager.addEventListener("breakpoint-added", this._breakpointAdded, this);

    this.reset();
}

// Keep these in sync with WebCore::ScriptDebugServer
WebInspector.ScriptsPanel.PauseOnExceptionsState = {
    DontPauseOnExceptions : 0,
    PauseOnAllExceptions : 1,
    PauseOnUncaughtExceptions: 2
};

WebInspector.ScriptsPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Scripts");
    },

    get statusBarItems()
    {
        return [this.enableToggleButton.element, this._pauseOnExceptionButton.element];
    },

    get defaultFocusedElement()
    {
        return this.filesSelectElement;
    },

    get paused()
    {
        return this._paused;
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";

        if (this.visibleView) {
            if (this.visibleView instanceof WebInspector.ResourceView)
                this.visibleView.headersVisible = false;
            this.visibleView.show(this.viewsContainerElement);
        }
        if (this._attachDebuggerWhenShown) {
            InspectorBackend.enableDebugger(false);
            delete this._attachDebuggerWhenShown;
        }
    },

    hide: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        WebInspector.Panel.prototype.hide.call(this);
    },

    get breakpointsActivated()
    {
        return this.toggleBreakpointsButton.toggled;
    },

    addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage, scriptWorldType)
    {
        var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage, scriptWorldType);
        this._sourceIDMap[sourceID] = script;

        var resource = WebInspector.resourceURLMap[sourceURL];
        if (resource) {
            if (resource.finished) {
                // Resource is finished, bind the script right away.
                resource.addScript(script);
                this._sourceIDMap[sourceID] = resource;
            } else {
                // Resource is not finished, bind the script later.
                if (!resource._scriptsPendingResourceLoad) {
                    resource._scriptsPendingResourceLoad = [];
                    resource.addEventListener("finished", this._resourceLoadingFinished, this);
                }
                resource._scriptsPendingResourceLoad.push(script);
            }
        }
        this._addScriptToFilesMenu(script);
    },

    continueToLine: function(sourceID, line)
    {
        WebInspector.breakpointManager.setOneTimeBreakpoint(sourceID, line);
        if (this.paused)
            this._togglePause();
    },

    _resourceLoadingFinished: function(e)
    {
        var resource = e.target;
        for (var i = 0; i < resource._scriptsPendingResourceLoad.length; ++i) {
            // Bind script to resource.
            var script = resource._scriptsPendingResourceLoad[i];
            resource.addScript(script);
            this._sourceIDMap[script.sourceID] = resource;

            // Remove script from the files list.
            script.filesSelectOption.parentElement.removeChild(script.filesSelectOption);
        }
        // Adding first script will add resource.
        this._addScriptToFilesMenu(resource._scriptsPendingResourceLoad[0]);
        delete resource._scriptsPendingResourceLoad;
    },

    _breakpointAdded: function(event)
    {
        var breakpoint = event.data;

        var sourceFrame;
        if (breakpoint.url) {
            var resource = WebInspector.resourceURLMap[breakpoint.url];
            if (resource && resource.finished)
                sourceFrame = this._sourceFrameForScriptOrResource(resource);
        }

        if (breakpoint.sourceID && !sourceFrame) {
            var object = this._sourceIDMap[breakpoint.sourceID]
            sourceFrame = this._sourceFrameForScriptOrResource(object);
        }

        if (sourceFrame)
            sourceFrame.addBreakpoint(breakpoint);
    },

    canEditScripts: function()
    {
        return Preferences.canEditScriptSource;
    },

    editScriptSource: function(sourceID, newContent, line, linesCountToShift, commitEditingCallback, cancelEditingCallback)
    {
        if (!this.canEditScripts())
            return;

        // Need to clear breakpoints and re-create them later when editing source.
        var breakpoints = WebInspector.breakpointManager.breakpointsForSourceID(sourceID);
        for (var i = 0; i < breakpoints.length; ++i)
            breakpoints[i].remove();

        function mycallback(success, newBodyOrErrorMessage, callFrames)
        {
            if (success) {
                commitEditingCallback(newBodyOrErrorMessage);
                if (callFrames && callFrames.length)
                    this.debuggerPaused(callFrames);
            } else {
                cancelEditingCallback();
                WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning);
            }
            for (var i = 0; i < breakpoints.length; ++i) {
                var breakpoint = breakpoints[i];
                var newLine = breakpoint.line;
                if (success && breakpoint.line >= line)
                    newLine += linesCountToShift;
                WebInspector.breakpointManager.setBreakpoint(sourceID, breakpoint.url, newLine, breakpoint.enabled, breakpoint.condition);
            }
        };
        InspectorBackend.editScriptSource(sourceID, newContent, mycallback.bind(this));
    },

    selectedCallFrameId: function()
    {
        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
        if (!selectedCallFrame)
            return null;
        return selectedCallFrame.id;
    },

    evaluateInSelectedCallFrame: function(code, updateInterface, objectGroup, callback)
    {
        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
        if (!this._paused || !selectedCallFrame)
            return;

        if (typeof updateInterface === "undefined")
            updateInterface = true;

        var self = this;
        function updatingCallbackWrapper(result)
        {
            callback(result);
            if (updateInterface)
                self.sidebarPanes.scopechain.update(selectedCallFrame);
        }
        this.doEvalInCallFrame(selectedCallFrame, code, objectGroup, updatingCallbackWrapper);
    },

    doEvalInCallFrame: function(callFrame, code, objectGroup, callback)
    {
        function evalCallback(result)
        {
            if (result)
                callback(WebInspector.RemoteObject.fromPayload(result));
        }
        InjectedScriptAccess.get(callFrame.worldId).evaluateInCallFrame(callFrame.id, code, objectGroup, evalCallback);
    },

    debuggerPaused: function(details)
    {
        WebInspector.breakpointManager.removeOneTimeBreakpoint();
        this._paused = true;
        this._waitingToPause = false;
        this._stepping = false;

        this._updateDebuggerButtons();

        this.sidebarPanes.callstack.update(details.callFrames, this._sourceIDMap);
        this.sidebarPanes.callstack.selectedCallFrame = details.callFrames[0];

        if ("eventType" in details)
            this.sidebarPanes.callstack.updateStatus(details.eventType, details.eventData);

        WebInspector.currentPanel = this;
        window.focus();
    },

    debuggerResumed: function()
    {
        this._paused = false;
        this._waitingToPause = false;
        this._stepping = false;

        this._clearInterface();
    },

    attachDebuggerWhenShown: function()
    {
        if (this.element.parentElement) {
            InspectorBackend.enableDebugger(false);
        } else {
            this._attachDebuggerWhenShown = true;
        }
    },

    debuggerWasEnabled: function()
    {
        if (this._debuggerEnabled)
            return;

        this._debuggerEnabled = true;
        this.reset(true);
    },

    debuggerWasDisabled: function()
    {
        if (!this._debuggerEnabled)
            return;

        this._debuggerEnabled = false;
        this.reset(true);
    },

    reset: function(preserveItems)
    {
        this.visibleView = null;

        delete this.currentQuery;
        this.searchCanceled();

        this.debuggerResumed();

        this._backForwardList = [];
        this._currentBackForwardIndex = -1;
        this._updateBackAndForwardButtons();

        this._resourceForURLInFilesSelect = {};
        this.filesSelectElement.removeChildren();
        this.functionsSelectElement.removeChildren();
        this.viewsContainerElement.removeChildren();

        if (this._sourceIDMap) {
            for (var sourceID in this._sourceIDMap) {
                var object = this._sourceIDMap[sourceID];
                if (object instanceof WebInspector.Resource)
                    object.removeAllScripts();
            }
        }

        this._sourceIDMap = {};

        this.sidebarPanes.watchExpressions.refreshExpressions();
        if (!preserveItems) {
            this.sidebarPanes.jsBreakpoints.reset();
            if (Preferences.nativeInstrumentationEnabled) {
                this.sidebarPanes.domBreakpoints.reset();
                this.sidebarPanes.xhrBreakpoints.reset();
            }
            this.sidebarPanes.eventListenerBreakpoints.reset();
            this.sidebarPanes.workers.reset();
        }
    },

    get visibleView()
    {
        return this._visibleView;
    },

    set visibleView(x)
    {
        if (this._visibleView === x)
            return;

        if (this._visibleView)
            this._visibleView.hide();

        this._visibleView = x;

        if (x)
            x.show(this.viewsContainerElement);
    },

    viewRecreated: function(oldView, newView)
    {
        if (this._visibleView === oldView)
            this._visibleView = newView;
    },

    canShowSourceLine: function(url, line)
    {
        if (!this._debuggerEnabled)
            return false;
        return !!this._scriptOrResourceForURLAndLine(url, line);
    },

    showSourceLine: function(url, line)
    {
        var scriptOrResource = this._scriptOrResourceForURLAndLine(url, line);
        this._showScriptOrResource(scriptOrResource, {line: line, shouldHighlightLine: true});
    },

    _scriptOrResourceForURLAndLine: function(url, line)
    {
        var scriptWithMatchingUrl = null;
        for (var sourceID in this._sourceIDMap) {
            var scriptOrResource = this._sourceIDMap[sourceID];
            if (scriptOrResource instanceof WebInspector.Script) {
                if (scriptOrResource.sourceURL !== url)
                    continue;
                scriptWithMatchingUrl = scriptOrResource;
                if (scriptWithMatchingUrl.startingLine <= line && scriptWithMatchingUrl.startingLine + scriptWithMatchingUrl.linesCount > line)
                    return scriptWithMatchingUrl;
            } else {
                var resource = scriptOrResource;
                if (resource.url === url)
                    return resource;
            }
        }
        return scriptWithMatchingUrl;
    },

    showView: function(view)
    {
        if (!view)
            return;
        this._showScriptOrResource(view.resource || view.script);
    },

    handleShortcut: function(event)
    {
        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcut];
        if (handler) {
            handler(event);
            event.handled = true;
        } else
            this.sidebarPanes.callstack.handleShortcut(event);
    },

    scriptViewForScript: function(script)
    {
        if (!script)
            return null;
        if (!script._scriptView)
            script._scriptView = new WebInspector.ScriptView(script);
        return script._scriptView;
    },

    sourceFrameForScript: function(script)
    {
        var view = this.scriptViewForScript(script);
        if (!view)
            return null;

        // Setting up the source frame requires that we be attached.
        if (!this.element.parentNode)
            this.attach();

        view.setupSourceFrameIfNeeded();
        return view.sourceFrame;
    },

    _sourceFrameForScriptOrResource: function(scriptOrResource)
    {
        if (scriptOrResource instanceof WebInspector.Resource)
            return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
        if (scriptOrResource instanceof WebInspector.Script)
            return this.sourceFrameForScript(scriptOrResource);
    },

    _showScriptOrResource: function(scriptOrResource, options)
    {
        // options = {line:, shouldHighlightLine:, fromBackForwardAction:, initialLoad:}
        options = options || {};

        if (!scriptOrResource)
            return;

        var view;
        if (scriptOrResource instanceof WebInspector.Resource) {
            if (!WebInspector.panels.resources)
                return null;
            view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
            view.headersVisible = false;
        } else if (scriptOrResource instanceof WebInspector.Script)
            view = this.scriptViewForScript(scriptOrResource);

        if (!view)
            return;

        var url = scriptOrResource.url || scriptOrResource.sourceURL;
        if (url && !options.initialLoad)
            WebInspector.applicationSettings.lastViewedScriptFile = url;

        if (!options.fromBackForwardAction) {
            var oldIndex = this._currentBackForwardIndex;
            if (oldIndex >= 0)
                this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);

            // Check for a previous entry of the same object in _backForwardList.
            // If one is found, remove it and update _currentBackForwardIndex to match.
            var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource);
            if (previousEntryIndex !== -1) {
                this._backForwardList.splice(previousEntryIndex, 1);
                --this._currentBackForwardIndex;
            }

            this._backForwardList.push(scriptOrResource);
            ++this._currentBackForwardIndex;

            this._updateBackAndForwardButtons();
        }

        this.visibleView = view;

        if (options.line) {
            if (view.revealLine)
                view.revealLine(options.line);
            if (view.highlightLine && options.shouldHighlightLine)
                view.highlightLine(options.line);
        }

        var option;
        if (scriptOrResource instanceof WebInspector.Script) {
            option = scriptOrResource.filesSelectOption;

            // hasn't been added yet - happens for stepping in evals,
            // so use the force option to force the script into the menu.
            if (!option) {
                this._addScriptToFilesMenu(scriptOrResource, true);
                option = scriptOrResource.filesSelectOption;
            }

            console.assert(option);
        } else
            option = scriptOrResource.filesSelectOption;

        if (option)
            this.filesSelectElement.selectedIndex = option.index;
    },

    _addScriptToFilesMenu: function(script, force)
    {
        if (!script.sourceURL && !force)
            return;

        if (script.resource) {
            if (this._resourceForURLInFilesSelect[script.resource.url])
                return;
            this._resourceForURLInFilesSelect[script.resource.url] = script.resource;
        }

        var displayName = script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)");

        var select = this.filesSelectElement;
        var option = document.createElement("option");
        option.representedObject = script.resource || script;
        option.url = displayName;
        option.startingLine = script.startingLine;
        option.text = script.resource || script.startingLine === 1 ? displayName : String.sprintf("%s:%d", displayName, script.startingLine);

        function optionCompare(a, b)
        {
            if (a.url < b.url)
                return -1;
            else if (a.url > b.url)
                return 1;

            if (typeof a.startingLine !== "number")
                return -1;
            if (typeof b.startingLine !== "number")
                return -1;
            return a.startingLine - b.startingLine;
        }

        var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
        if (insertionIndex < 0)
            select.appendChild(option);
        else
            select.insertBefore(option, select.childNodes.item(insertionIndex));

        if (script.resource)
            script.resource.filesSelectOption = option;
        else
            script.filesSelectOption = option;

        // Call _showScriptOrResource if the option we just appended ended up being selected.
        // This will happen for the first item added to the menu.
        if (select.options[select.selectedIndex] === option)
            this._showScriptOrResource(option.representedObject, {initialLoad: true});
        else {
            // if not first item, check to see if this was the last viewed
            var url = option.representedObject.url || option.representedObject.sourceURL;
            var lastURL = WebInspector.applicationSettings.lastViewedScriptFile;
            if (url && url === lastURL)
                this._showScriptOrResource(option.representedObject, {initialLoad: true});
        }

        if (script.worldType === WebInspector.Script.WorldType.EXTENSIONS_WORLD)
            script.filesSelectOption.addStyleClass("extension-script");
    },

    _clearCurrentExecutionLine: function()
    {
        if (this._executionSourceFrame)
            this._executionSourceFrame.executionLine = 0;
        delete this._executionSourceFrame;
    },

    _callFrameSelected: function()
    {
        this._clearCurrentExecutionLine();

        var callStackPane = this.sidebarPanes.callstack;
        var currentFrame = callStackPane.selectedCallFrame;
        if (!currentFrame)
            return;

        this.sidebarPanes.scopechain.update(currentFrame);
        this.sidebarPanes.watchExpressions.refreshExpressions();

        var scriptOrResource = this._sourceIDMap[currentFrame.sourceID];
        this._showScriptOrResource(scriptOrResource, {line: currentFrame.line});

        this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
        if (this._executionSourceFrame)
            this._executionSourceFrame.executionLine = currentFrame.line;
    },

    _changeVisibleFile: function(event)
    {
        var select = this.filesSelectElement;
        this._showScriptOrResource(select.options[select.selectedIndex].representedObject);
    },

    _startSidebarResizeDrag: function(event)
    {
        WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");

        if (event.target === this.sidebarResizeWidgetElement)
            this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
        else
            this._dragOffset = 0;
    },

    _endSidebarResizeDrag: function(event)
    {
        WebInspector.elementDragEnd(event);
        delete this._dragOffset;
        this.saveSidebarWidth();
    },

    _sidebarResizeDrag: function(event)
    {
        var x = event.pageX + this._dragOffset;
        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
        this.setSidebarWidth(newWidth);
        event.preventDefault();
    },

    setSidebarWidth: function(newWidth)
    {
        this.sidebarElement.style.width = newWidth + "px";
        this.sidebarButtonsElement.style.width = newWidth + "px";
        this.viewsContainerElement.style.right = newWidth + "px";
        this.sidebarResizeWidgetElement.style.right = newWidth + "px";
        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";

        this.resize();
    },

    updatePauseOnExceptionsState: function(pauseOnExceptionsState)
    {
        if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions)
            this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");
        else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions)
            this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions.");
        else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions)
            this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions.");

        this._pauseOnExceptionButton.state = pauseOnExceptionsState;
    },

    _updateDebuggerButtons: function()
    {
        if (this._debuggerEnabled) {
            this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            this._pauseOnExceptionButton.visible = true;
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            this._pauseOnExceptionButton.visible = false;
            this.panelEnablerView.visible = true;
        }

        if (this._paused) {
            this.pauseButton.addStyleClass("paused");

            this.pauseButton.disabled = false;
            this.stepOverButton.disabled = false;
            this.stepIntoButton.disabled = false;
            this.stepOutButton.disabled = false;

            this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
        } else {
            this.pauseButton.removeStyleClass("paused");

            this.pauseButton.disabled = this._waitingToPause;
            this.stepOverButton.disabled = true;
            this.stepIntoButton.disabled = true;
            this.stepOutButton.disabled = true;

            if (this._waitingToPause)
                this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
            else if (this._stepping)
                this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
            else
                this.debuggerStatusElement.textContent = "";
        }
    },

    _updateBackAndForwardButtons: function()
    {
        this.backButton.disabled = this._currentBackForwardIndex <= 0;
        this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
    },

    _clearInterface: function()
    {
        this.sidebarPanes.callstack.update(null);
        this.sidebarPanes.scopechain.update(null);

        this._clearCurrentExecutionLine();
        this._updateDebuggerButtons();
    },

    _goBack: function()
    {
        if (this._currentBackForwardIndex <= 0) {
            console.error("Can't go back from index " + this._currentBackForwardIndex);
            return;
        }

        this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], {fromBackForwardAction: true});
        this._updateBackAndForwardButtons();
    },

    _goForward: function()
    {
        if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
            console.error("Can't go forward from index " + this._currentBackForwardIndex);
            return;
        }

        this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], {fromBackForwardAction: true});
        this._updateBackAndForwardButtons();
    },

    _enableDebugging: function()
    {
        if (this._debuggerEnabled)
            return;
        this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
    },

    _toggleDebugging: function(optionalAlways)
    {
        this._paused = false;
        this._waitingToPause = false;
        this._stepping = false;

        if (this._debuggerEnabled)
            InspectorBackend.disableDebugger(true);
        else
            InspectorBackend.enableDebugger(!!optionalAlways);
    },

    _togglePauseOnExceptions: function()
    {
        InspectorBackend.setPauseOnExceptionsState((this._pauseOnExceptionButton.state + 1) % this._pauseOnExceptionButton.states, this.updatePauseOnExceptionsState.bind(this));
    },

    _togglePause: function()
    {
        if (this._paused) {
            this._paused = false;
            this._waitingToPause = false;
            InspectorBackend.resume();
        } else {
            this._stepping = false;
            this._waitingToPause = true;
            InspectorBackend.pause();
        }

        this._clearInterface();
    },

    _stepOverClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorBackend.stepOverStatement();
    },

    _stepIntoClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorBackend.stepIntoStatement();
    },

    _stepOutClicked: function()
    {
        this._paused = false;
        this._stepping = true;

        this._clearInterface();

        InspectorBackend.stepOutOfFunction();
    },

    toggleBreakpointsClicked: function()
    {
        this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled;
        if (this.toggleBreakpointsButton.toggled) {
            InspectorBackend.activateBreakpoints();
            this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints.");
            document.getElementById("main-panels").removeStyleClass("breakpoints-deactivated");
        } else {
            InspectorBackend.deactivateBreakpoints();
            this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints.");
            document.getElementById("main-panels").addStyleClass("breakpoints-deactivated");
        }
    },

    elementsToRestoreScrollPositionsFor: function()
    {
        return [ this.sidebarElement ];
    },

    _registerShortcuts: function()
    {
        var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Scripts Panel"));
        var handler, shortcut1, shortcut2;
        var platformSpecificModifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta;

        this._shortcuts = {};

        // Continue.
        handler = this.pauseButton.click.bind(this.pauseButton);
        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F8);
        this._shortcuts[shortcut1.key] = handler;
        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Slash, platformSpecificModifier);
        this._shortcuts[shortcut2.key] = handler;
        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Continue"));

        // Step over.
        handler = this.stepOverButton.click.bind(this.stepOverButton);
        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F10);
        this._shortcuts[shortcut1.key] = handler;
        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.SingleQuote, platformSpecificModifier);
        this._shortcuts[shortcut2.key] = handler;
        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step over"));

        // Step into.
        handler = this.stepIntoButton.click.bind(this.stepIntoButton);
        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11);
        this._shortcuts[shortcut1.key] = handler;
        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, platformSpecificModifier);
        this._shortcuts[shortcut2.key] = handler;
        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step into"));

        // Step out.
        handler = this.stepOutButton.click.bind(this.stepOutButton);
        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
        this._shortcuts[shortcut1.key] = handler;
        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
        this._shortcuts[shortcut2.key] = handler;
        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step out"));

        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor("g", platformSpecificModifier);
        this._shortcuts[shortcut1.key] = this.showGoToLineDialog.bind(this);
        section.addAlternateKeys([ shortcut1.name ], WebInspector.UIString("Go to Line"));
        this.sidebarPanes.callstack.registerShortcuts(section);
    },

    searchCanceled: function()
    {
        WebInspector.updateSearchMatchesCount(0, this);

        if (this._searchView)
            this._searchView.searchCanceled();

        delete this._searchView;
        delete this._searchQuery;
    },

    performSearch: function(query)
    {
        if (!this.visibleView)
            return;

        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        this._searchView = this.visibleView;
        this._searchQuery = query;

        function finishedCallback(view, searchMatches)
        {
            if (!searchMatches)
                return;

            WebInspector.updateSearchMatchesCount(searchMatches, this);
            view.jumpToFirstSearchResult();
        }

        this._searchView.performSearch(query, finishedCallback.bind(this));
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchView)
            return;

        if (this._searchView !== this.visibleView) {
            this.performSearch(this._searchQuery);
            return;
        }

        if (this._searchView.showingLastSearchResult())
            this._searchView.jumpToFirstSearchResult();
        else
            this._searchView.jumpToNextSearchResult();
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchView)
            return;

        if (this._searchView !== this.visibleView) {
            this.performSearch(this._searchQuery);
            if (this._searchView)
                this._searchView.jumpToLastSearchResult();
            return;
        }

        if (this._searchView.showingFirstSearchResult())
            this._searchView.jumpToLastSearchResult();
        else
            this._searchView.jumpToPreviousSearchResult();
    },

    showGoToLineDialog: function(e)
    {
         var view = this.visibleView;
         if (view)
             WebInspector.GoToLineDialog.show(view);
    }
}

WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;


/* StoragePanel.js */

/*
 * Copyright (C) 2007, 2008, 2010 Apple Inc.  All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.StoragePanel = function(database)
{
    WebInspector.Panel.call(this, "storage");

    this.createSidebar();

    this.databasesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("DATABASES"), {}, true);
    this.sidebarTree.appendChild(this.databasesListTreeElement);
    this.databasesListTreeElement.expand();

    this.localStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("LOCAL STORAGE"), {}, true);
    this.sidebarTree.appendChild(this.localStorageListTreeElement);
    this.localStorageListTreeElement.expand();

    this.sessionStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("SESSION STORAGE"), {}, true);
    this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
    this.sessionStorageListTreeElement.expand();

    this.cookieListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("COOKIES"), {}, true);
    this.sidebarTree.appendChild(this.cookieListTreeElement);
    this.cookieListTreeElement.expand();

    
    this.applicationCacheListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("APPLICATION CACHE"), {}, true);
    this.sidebarTree.appendChild(this.applicationCacheListTreeElement);
    this.applicationCacheListTreeElement.expand();
    
    this.storageViews = document.createElement("div");
    this.storageViews.id = "storage-views";
    this.element.appendChild(this.storageViews);

    this.storageViewStatusBarItemsContainer = document.createElement("div");
    this.storageViewStatusBarItemsContainer.className = "status-bar-items";

    this.reset();
}

WebInspector.StoragePanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Storage");
    },

    get statusBarItems()
    {
        return [this.storageViewStatusBarItemsContainer];
    },

    reset: function()
    {
        if (this._databases) {
            var databasesLength = this._databases.length;
            for (var i = 0; i < databasesLength; ++i) {
                var database = this._databases[i];

                delete database._tableViews;
                delete database._queryView;
            }
        }

        this._databases = [];

        if (this._domStorage) {
            var domStorageLength = this._domStorage.length;
            for (var i = 0; i < domStorageLength; ++i) {
                var domStorage = this._domStorage[i];

                delete domStorage._domStorageView;
            }
        }

        this._domStorage = [];

        this._cookieViews = {};

        this._applicationCacheView = null;
        delete this._cachedApplicationCacheViewStatus;

        this.databasesListTreeElement.removeChildren();
        this.localStorageListTreeElement.removeChildren();
        this.sessionStorageListTreeElement.removeChildren();
        this.cookieListTreeElement.removeChildren();

        this.applicationCacheListTreeElement.removeChildren();

        this.storageViews.removeChildren();

        this.storageViewStatusBarItemsContainer.removeChildren();
        
        if (this.sidebarTree.selectedTreeElement)
            this.sidebarTree.selectedTreeElement.deselect();
    },

    addDatabase: function(database)
    {
        this._databases.push(database);

        var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database);
        database._databasesTreeElement = databaseTreeElement;
        this.databasesListTreeElement.appendChild(databaseTreeElement);
    },
    
    addCookieDomain: function(domain)
    {
        var cookieDomainTreeElement = new WebInspector.CookieSidebarTreeElement(domain);
        this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
    },

    addDOMStorage: function(domStorage)
    {
        this._domStorage.push(domStorage);
        var domStorageTreeElement = new WebInspector.DOMStorageSidebarTreeElement(domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
        domStorage._domStorageTreeElement = domStorageTreeElement;
        if (domStorage.isLocalStorage)
            this.localStorageListTreeElement.appendChild(domStorageTreeElement);
        else
            this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
    },

    addApplicationCache: function(domain)
    {
        var applicationCacheTreeElement = new WebInspector.ApplicationCacheSidebarTreeElement(domain);
        this.applicationCacheListTreeElement.appendChild(applicationCacheTreeElement);
    },

    selectDatabase: function(databaseId)
    {
        var database;
        for (var i = 0, len = this._databases.length; i < len; ++i) {
            database = this._databases[i];
            if (database.id === databaseId) {
                this.showDatabase(database);
                database._databasesTreeElement.select();
                return;
            }
        }
    },

    selectDOMStorage: function(storageId)
    {
        var domStorage = this._domStorageForId(storageId);
        if (domStorage) {
            this.showDOMStorage(domStorage);
            domStorage._domStorageTreeElement.select();
        }
    },

    showDatabase: function(database, tableName)
    {
        if (!database)
            return;

        if (this.visibleView)
            this.visibleView.hide();

        var view;
        if (tableName) {
            if (!("_tableViews" in database))
                database._tableViews = {};
            view = database._tableViews[tableName];
            if (!view) {
                view = new WebInspector.DatabaseTableView(database, tableName);
                database._tableViews[tableName] = view;
            }
        } else {
            view = database._queryView;
            if (!view) {
                view = new WebInspector.DatabaseQueryView(database);
                database._queryView = view;
            }
        }

        this._genericViewSetup(view);
    },

    showDOMStorage: function(domStorage)
    {
        if (!domStorage)
            return;

        if (this.visibleView)
            this.visibleView.hide();

        var view;
        view = domStorage._domStorageView;
        if (!view) {
            view = new WebInspector.DOMStorageItemsView(domStorage);
            domStorage._domStorageView = view;
        }

        this._genericViewSetup(view);
    },

    showCookies: function(treeElement, cookieDomain)
    {
        if (this.visibleView)
            this.visibleView.hide();

        var view = this._cookieViews[cookieDomain];
        if (!view) {
            view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
            this._cookieViews[cookieDomain] = view;
        }

        this._genericViewSetup(view);
    },

    showApplicationCache: function(treeElement, appcacheDomain)
    {
        if (this.visibleView)
            this.visibleView.hide();

        var view = this._applicationCacheView;
        if (!view) {
            view = new WebInspector.ApplicationCacheItemsView(treeElement, appcacheDomain);
            this._applicationCacheView = view;
        }

        this._genericViewSetup(view);

        if ("_cachedApplicationCacheViewStatus" in this)
            this._applicationCacheView.updateStatus(this._cachedApplicationCacheViewStatus);
    },

    _genericViewSetup: function(view)
    {
        view.show(this.storageViews);
        this.visibleView = view;

        this.storageViewStatusBarItemsContainer.removeChildren();
        var statusBarItems = view.statusBarItems || [];
        for (var i = 0; i < statusBarItems.length; ++i)
            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    closeVisibleView: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        delete this.visibleView;
    },

    updateDatabaseTables: function(database)
    {
        if (!database || !database._databasesTreeElement)
            return;

        database._databasesTreeElement.shouldRefreshChildren = true;

        if (!("_tableViews" in database))
            return;

        var tableNamesHash = {};
        var self = this;
        function tableNamesCallback(tableNames)
        {
            var tableNamesLength = tableNames.length;
            for (var i = 0; i < tableNamesLength; ++i)
                tableNamesHash[tableNames[i]] = true;

            for (var tableName in database._tableViews) {
                if (!(tableName in tableNamesHash)) {
                    if (self.visibleView === database._tableViews[tableName])
                        self.closeVisibleView();
                    delete database._tableViews[tableName];
                }
            }
        }
        database.getTableNames(tableNamesCallback);
    },

    dataGridForResult: function(columnNames, values)
    {
        var numColumns = columnNames.length;
        if (!numColumns)
            return null;

        var columns = {};

        for (var i = 0; i < columnNames.length; ++i) {
            var column = {};
            column.width = columnNames[i].length;
            column.title = columnNames[i];
            column.sortable = true;

            columns[columnNames[i]] = column;
        }

        var nodes = [];
        for (var i = 0; i < values.length / numColumns; ++i) {
            var data = {};
            for (var j = 0; j < columnNames.length; ++j)
                data[columnNames[j]] = values[numColumns * i + j];

            var node = new WebInspector.DataGridNode(data, false);
            node.selectable = false;
            nodes.push(node);
        }

        var dataGrid = new WebInspector.DataGrid(columns);
        var length = nodes.length;
        for (var i = 0; i < length; ++i)
            dataGrid.appendChild(nodes[i]);

        dataGrid.addEventListener("sorting changed", this._sortDataGrid.bind(this, dataGrid), this);
        return dataGrid;
    },

    _sortDataGrid: function(dataGrid)
    {
        var nodes = dataGrid.children.slice();
        var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
        var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
        var columnIsNumeric = true;

        for (var i = 0; i < nodes.length; i++) {
            if (isNaN(Number(nodes[i].data[sortColumnIdentifier])))
                columnIsNumeric = false;
        }

        function comparator(dataGridNode1, dataGridNode2)
        {
            var item1 = dataGridNode1.data[sortColumnIdentifier];
            var item2 = dataGridNode2.data[sortColumnIdentifier];

            var comparison;
            if (columnIsNumeric) {
                // Sort numbers based on comparing their values rather than a lexicographical comparison.
                var number1 = parseFloat(item1);
                var number2 = parseFloat(item2);
                comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
            } else
                comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);

            return sortDirection * comparison;
        }

        nodes.sort(comparator);
        dataGrid.removeChildren();
        for (var i = 0; i < nodes.length; i++)
            dataGrid.appendChild(nodes[i]);
    },

    updateDOMStorage: function(storageId)
    {
        var domStorage = this._domStorageForId(storageId);
        if (!domStorage)
            return;

        var view = domStorage._domStorageView;
        if (this.visibleView && view === this.visibleView)
            domStorage._domStorageView.update();
    },

    updateApplicationCacheStatus: function(status)
    {
        this._cachedApplicationCacheViewStatus = status;
        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
            this._applicationCacheView.updateStatus(status);
    },

    updateNetworkState: function(isNowOnline)
    {
        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
            this._applicationCacheView.updateNetworkState(isNowOnline);
    },

    updateManifest: function(manifest)
    {
        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
            this._applicationCacheView.updateManifest(manifest);
    },

    _domStorageForId: function(storageId)
    {
        if (!this._domStorage)
            return null;
        var domStorageLength = this._domStorage.length;
        for (var i = 0; i < domStorageLength; ++i) {
            var domStorage = this._domStorage[i];
            if (domStorage.id == storageId)
                return domStorage;
        }
        return null;
    },

    updateMainViewWidth: function(width)
    {
        this.storageViews.style.left = width + "px";
        this.storageViewStatusBarItemsContainer.style.left = width + "px";
        this.resize();
    }
}

WebInspector.StoragePanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.DatabaseSidebarTreeElement = function(database)
{
    this.database = database;

    WebInspector.SidebarTreeElement.call(this, "database-sidebar-tree-item", "", "", database, true);

    this.refreshTitles();
}

WebInspector.DatabaseSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDatabase(this.database);
    },

    oncollapse: function()
    {
        // Request a refresh after every collapse so the next
        // expand will have an updated table list.
        this.shouldRefreshChildren = true;
    },

    onpopulate: function()
    {
        this.removeChildren();

        var self = this;
        function tableNamesCallback(tableNames)
        {
            var tableNamesLength = tableNames.length;
            for (var i = 0; i < tableNamesLength; ++i)
                self.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(self.database, tableNames[i]));
        }
        this.database.getTableNames(tableNamesCallback);
    },

    get mainTitle()
    {
        return this.database.name;
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return this.database.displayDomain;
    },

    set subtitle(x)
    {
        // Do nothing.
    }
}

WebInspector.DatabaseSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.SidebarDatabaseTableTreeElement = function(database, tableName)
{
    this.database = database;
    this.tableName = tableName;

    WebInspector.SidebarTreeElement.call(this, "database-table-sidebar-tree-item small", tableName, "", null, false);
}

WebInspector.SidebarDatabaseTableTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDatabase(this.database, this.tableName);
    }
}

WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.DOMStorageSidebarTreeElement = function(domStorage, className)
{

    this.domStorage = domStorage;

    WebInspector.SidebarTreeElement.call(this, "domstorage-sidebar-tree-item " + className, domStorage, "", null, false);

    this.refreshTitles();
}

WebInspector.DOMStorageSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showDOMStorage(this.domStorage);
    },

    get mainTitle()
    {
        return this.domStorage.domain ? this.domStorage.domain : WebInspector.UIString("Local Files");
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return ""; //this.database.displayDomain;
    },

    set subtitle(x)
    {
        // Do nothing.
    }
}

WebInspector.DOMStorageSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.CookieSidebarTreeElement = function(cookieDomain)
{
    WebInspector.SidebarTreeElement.call(this, "cookie-sidebar-tree-item", cookieDomain, "", null, false);
    this._cookieDomain = cookieDomain;
    this._subtitle = "";

    this.refreshTitles();
}

WebInspector.CookieSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showCookies(this, this._cookieDomain);
    },

    get mainTitle()
    {
        return this._cookieDomain ? this._cookieDomain : WebInspector.UIString("Local Files");
    },

    set mainTitle(x)
    {
        // Do nothing.
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        this._subtitle = x;
        this.refreshTitles();
    }
}

WebInspector.CookieSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.ApplicationCacheSidebarTreeElement = function(appcacheDomain)
{
    WebInspector.SidebarTreeElement.call(this, "application-cache-sidebar-tree-item", appcacheDomain, "", null, false);
    this._appcacheDomain = appcacheDomain;
    this._subtitle = "";
    this._mainTitle = this._appcacheDomain;
    this.refreshTitles();
}

WebInspector.ApplicationCacheSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.storage.showApplicationCache(this, this._appcacheDomain);
    },

    get mainTitle()
    {
        return this._mainTitle;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        return this._subtitle;
    },

    set subtitle(x)
    {
        this._subtitle = x;
        this.refreshTitles();
    }
}

WebInspector.ApplicationCacheSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

/* ProfilesPanel.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";

WebInspector.ProfileType = function(id, name)
{
    this._id = id;
    this._name = name;
}

WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;

WebInspector.ProfileType.prototype = {
    get buttonTooltip()
    {
        return "";
    },

    get buttonStyle()
    {
        return undefined;
    },

    get buttonCaption()
    {
        return this.name;
    },

    get id()
    {
        return this._id;
    },

    get name()
    {
        return this._name;
    },

    buttonClicked: function()
    {
    },

    viewForProfile: function(profile)
    {
        if (!profile._profileView)
            profile._profileView = this.createView(profile);
        return profile._profileView;
    },

    get welcomeMessage()
    {
        return "";
    },

    // Must be implemented by subclasses.
    createView: function(profile)
    {
        throw new Error("Needs implemented.");
    },

    // Must be implemented by subclasses.
    createSidebarTreeElementForProfile: function(profile)
    {
        throw new Error("Needs implemented.");
    }
}

WebInspector.ProfilesPanel = function()
{
    WebInspector.Panel.call(this, "profiles");

    this.createSidebar();

    this._profileTypesByIdMap = {};
    this._profileTypeButtonsByIdMap = {};

    var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
    var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
    var panelEnablerButton = WebInspector.UIString("Enable Profiling");
    this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);

    this.element.appendChild(this.panelEnablerView.element);

    this.profileViews = document.createElement("div");
    this.profileViews.id = "profile-views";
    this.element.appendChild(this.profileViews);

    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);

    this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear CPU profiles."), "clear-status-bar-item");
    this.clearResultsButton.addEventListener("click", this._clearProfiles.bind(this), false);

    this.profileViewStatusBarItemsContainer = document.createElement("div");
    this.profileViewStatusBarItemsContainer.className = "status-bar-items";

    this.welcomeView = new WebInspector.WelcomeView("profiles", WebInspector.UIString("Welcome to the Profiles panel"));
    this.element.appendChild(this.welcomeView.element);

    this._profiles = [];
    this._profilerEnabled = Preferences.profilerAlwaysEnabled;
    this._reset();
}

WebInspector.ProfilesPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Profiles");
    },

    get statusBarItems()
    {
        function clickHandler(profileType, buttonElement)
        {
            profileType.buttonClicked.call(profileType);
            this.updateProfileTypeButtons();
        }

        var items = [this.enableToggleButton.element];
        // FIXME: Generate a single "combo-button".
        for (var typeId in this._profileTypesByIdMap) {
            var profileType = this.getProfileType(typeId);
            if (profileType.buttonStyle) {
                var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
                this._profileTypeButtonsByIdMap[typeId] = button.element;
                button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
                items.push(button.element);
            }
        }
        items.push(this.clearResultsButton.element, this.profileViewStatusBarItemsContainer);
        return items;
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this._populateProfiles();
    },

    profilerWasEnabled: function()
    {
        if (this._profilerEnabled)
            return;

        this._profilerEnabled = true;

        this._reset();
        if (this.visible)
            this._populateProfiles();
    },

    profilerWasDisabled: function()
    {
        if (!this._profilerEnabled)
            return;

        this._profilerEnabled = false;
        this._reset();
    },

    resetProfiles: function()
    {
        this._reset();
    },

    _reset: function()
    {
        for (var i = 0; i < this._profiles.length; ++i)
            delete this._profiles[i]._profileView;
        delete this.visibleView;

        delete this.currentQuery;
        this.searchCanceled();

        this._profiles = [];
        this._profilesIdMap = {};
        this._profileGroups = {};
        this._profileGroupsForLinks = {}
        this._profilesWereRequested = false;

        this.sidebarTreeElement.removeStyleClass("some-expandable");

        for (var typeId in this._profileTypesByIdMap)
            this.getProfileType(typeId).treeElement.removeChildren();

        this.profileViews.removeChildren();

        this.profileViewStatusBarItemsContainer.removeChildren();

        this._updateInterface();
        this.welcomeView.show();
    },

    _clearProfiles: function()
    {
        InspectorBackend.clearProfiles();
        this._reset();
    },

    registerProfileType: function(profileType)
    {
        this._profileTypesByIdMap[profileType.id] = profileType;
        profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
        this.sidebarTree.appendChild(profileType.treeElement);
        profileType.treeElement.expand();
        this._addWelcomeMessage(profileType);
    },

    _addWelcomeMessage: function(profileType)
    {
        var message = profileType.welcomeMessage;
        // Message text is supposed to have a '%s' substring as a placeholder
        // for a status bar button. If it is there, we split the message in two
        // parts, and insert the button between them.
        var buttonPos = message.indexOf("%s");
        if (buttonPos > -1) {
            var container = document.createDocumentFragment();
            var part1 = document.createElement("span");
            part1.innerHTML = message.substr(0, buttonPos);
            container.appendChild(part1);
     
            var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
            container.appendChild(button.element);
       
            var part2 = document.createElement("span");
            part2.innerHTML = message.substr(buttonPos + 2);
            container.appendChild(part2);
            this.welcomeView.addMessage(container);
        } else
            this.welcomeView.addMessage(message);
    },

    _makeKey: function(text, profileTypeId)
    {
        return escape(text) + '/' + escape(profileTypeId);
    },

    addProfileHeader: function(profile)
    {
        var typeId = profile.typeId;
        var profileType = this.getProfileType(typeId);
        var sidebarParent = profileType.treeElement;
        var small = false;
        var alternateTitle;

        profile.__profilesPanelProfileType = profileType;
        this._profiles.push(profile);
        this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;

        if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
            var profileTitleKey = this._makeKey(profile.title, typeId);
            if (!(profileTitleKey in this._profileGroups))
                this._profileGroups[profileTitleKey] = [];

            var group = this._profileGroups[profileTitleKey];
            group.push(profile);

            if (group.length === 2) {
                // Make a group TreeElement now that there are 2 profiles.
                group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);

                // Insert at the same index for the first profile of the group.
                var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
                sidebarParent.insertChild(group._profilesTreeElement, index);

                // Move the first profile to the group.
                var selected = group[0]._profilesTreeElement.selected;
                sidebarParent.removeChild(group[0]._profilesTreeElement);
                group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
                if (selected) {
                    group[0]._profilesTreeElement.select();
                    group[0]._profilesTreeElement.reveal();
                }

                group[0]._profilesTreeElement.small = true;
                group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);

                this.sidebarTreeElement.addStyleClass("some-expandable");
            }

            if (group.length >= 2) {
                sidebarParent = group._profilesTreeElement;
                alternateTitle = WebInspector.UIString("Run %d", group.length);
                small = true;
            }
        }

        var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
        profileTreeElement.small = small;
        if (alternateTitle)
            profileTreeElement.mainTitle = alternateTitle;
        profile._profilesTreeElement = profileTreeElement;

        sidebarParent.appendChild(profileTreeElement);
        if (!profile.isTemporary) {
            this.welcomeView.hide();
            if (!this.visibleView)
                this.showProfile(profile);
            this.dispatchEventToListeners("profile added");
        }
    },

    removeProfileHeader: function(profile)
    {
        var typeId = profile.typeId;
        var profileType = this.getProfileType(typeId);
        var sidebarParent = profileType.treeElement;

        for (var i = 0; i < this._profiles.length; ++i) {
            if (this._profiles[i].uid === profile.uid) {
                profile = this._profiles[i];
                this._profiles.splice(i, 1);
                break;
            }
        }
        delete this._profilesIdMap[this._makeKey(profile.uid, typeId)];

        var profileTitleKey = this._makeKey(profile.title, typeId);
        delete this._profileGroups[profileTitleKey];

        sidebarParent.removeChild(profile._profilesTreeElement);

        if (!profile.isTemporary)
            InspectorBackend.removeProfile(profile.typeId, profile.uid);

        // No other item will be selected if there aren't any other profiles, so
        // make sure that view gets cleared when the last profile is removed.
        if (!this._profiles.length)
            this.closeVisibleView();
    },

    showProfile: function(profile)
    {
        if (!profile || profile.isTemporary)
            return;

        this.closeVisibleView();

        var view = profile.__profilesPanelProfileType.viewForProfile(profile);

        view.show(this.profileViews);

        profile._profilesTreeElement.select(true);
        profile._profilesTreeElement.reveal();

        this.visibleView = view;

        this.profileViewStatusBarItemsContainer.removeChildren();

        var statusBarItems = view.statusBarItems;
        for (var i = 0; i < statusBarItems.length; ++i)
            this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    },

    getProfiles: function(typeId)
    {
        var result = [];
        var profilesCount = this._profiles.length;
        for (var i = 0; i < profilesCount; ++i)
            if (this._profiles[i].typeId === typeId)
                result.push(this._profiles[i]);
        return result;
    },

    updateProfile: function(profile)
    {
        var profilesCount = this._profiles.length;
        for (var i = 0; i < profilesCount; ++i)
            if (this._profiles[i].typeId === profile.typeId
                && this._profiles[i].uid === profile.uid) {
                this._profiles[i] = profile;
                break;
            }
    },

    showView: function(view)
    {
        this.showProfile(view.profile);
    },

    getProfileType: function(typeId)
    {
        return this._profileTypesByIdMap[typeId];
    },

    showProfileForURL: function(url)
    {
        var match = url.match(WebInspector.ProfileType.URLRegExp);
        if (!match)
            return;
        this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
    },

    updateProfileTypeButtons: function()
    {
        for (var typeId in this._profileTypeButtonsByIdMap) {
            var buttonElement = this._profileTypeButtonsByIdMap[typeId];
            var profileType = this.getProfileType(typeId);
            buttonElement.className = profileType.buttonStyle;
            buttonElement.title = profileType.buttonTooltip;
            // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
        }
    },

    closeVisibleView: function()
    {
        if (this.visibleView)
            this.visibleView.hide();
        delete this.visibleView;
    },

    displayTitleForProfileLink: function(title, typeId)
    {
        title = unescape(title);
        if (title.indexOf(UserInitiatedProfileName) === 0) {
            title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
        } else {
            var titleKey = this._makeKey(title, typeId);
            if (!(titleKey in this._profileGroupsForLinks))
                this._profileGroupsForLinks[titleKey] = 0;

            groupNumber = ++this._profileGroupsForLinks[titleKey];

            if (groupNumber > 2)
                // The title is used in the console message announcing that a profile has started so it gets
                // incremented twice as often as it's displayed
                title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
        }
        
        return title;
    },

    get searchableViews()
    {
        var views = [];

        const visibleView = this.visibleView;
        if (visibleView && visibleView.performSearch)
            views.push(visibleView);

        var profilesLength = this._profiles.length;
        for (var i = 0; i < profilesLength; ++i) {
            var profile = this._profiles[i];
            var view = profile.__profilesPanelProfileType.viewForProfile(profile);
            if (!view.performSearch || view === visibleView)
                continue;
            views.push(view);
        }

        return views;
    },

    searchMatchFound: function(view, matches)
    {
        view.profile._profilesTreeElement.searchMatches = matches;
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);

        if (!this._profiles)
            return;

        for (var i = 0; i < this._profiles.length; ++i) {
            var profile = this._profiles[i];
            profile._profilesTreeElement.searchMatches = 0;
        }
    },

    _updateInterface: function()
    {
        // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
        if (this._profilerEnabled) {
            this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
            this.enableToggleButton.toggled = true;
            for (var typeId in this._profileTypeButtonsByIdMap)
                this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
            this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
            this.clearResultsButton.element.removeStyleClass("hidden");
            this.panelEnablerView.visible = false;
        } else {
            this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
            this.enableToggleButton.toggled = false;
            for (var typeId in this._profileTypeButtonsByIdMap)
                this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
            this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
            this.clearResultsButton.element.addStyleClass("hidden");
            this.panelEnablerView.visible = true;
        }
    },

    _enableProfiling: function()
    {
        if (this._profilerEnabled)
            return;
        this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
    },

    _toggleProfiling: function(optionalAlways)
    {
        if (this._profilerEnabled)
            InspectorBackend.disableProfiler(true);
        else
            InspectorBackend.enableProfiler(!!optionalAlways);
    },

    _populateProfiles: function()
    {
        if (!this._profilerEnabled || this._profilesWereRequested)
            return;

        function populateCallback(profileHeaders) {
            profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
            var profileHeadersLength = profileHeaders.length;
            for (var i = 0; i < profileHeadersLength; ++i)
                WebInspector.addProfileHeader(profileHeaders[i]);
        }

        InspectorBackend.getProfileHeaders(populateCallback);

        this._profilesWereRequested = true;
    },

    updateMainViewWidth: function(width)
    {
        this.welcomeView.element.style.left = width + "px";
        this.profileViews.style.left = width + "px";
        this.profileViewStatusBarItemsContainer.style.left = Math.max(155, width) + "px";
        this.resize();
    }
}

WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className)
{
    this.profile = profile;
    this._titleFormat = titleFormat;

    if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
        this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);

    WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);

    this.refreshTitles();
}

WebInspector.ProfileSidebarTreeElement.prototype = {
    onselect: function()
    {
        this.treeOutline.panel.showProfile(this.profile);
    },

    ondelete: function()
    {
        this.treeOutline.panel.removeProfileHeader(this.profile);
        return true;
    },

    get mainTitle()
    {
        if (this._mainTitle)
            return this._mainTitle;
        if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
            return WebInspector.UIString(this._titleFormat, this._profileNumber);
        return this.profile.title;
    },

    set mainTitle(x)
    {
        this._mainTitle = x;
        this.refreshTitles();
    },

    get subtitle()
    {
        // There is no subtitle.
    },

    set subtitle(x)
    {
        // Can't change subtitle.
    },

    set searchMatches(matches)
    {
        if (!matches) {
            if (!this.bubbleElement)
                return;
            this.bubbleElement.removeStyleClass("search-matches");
            this.bubbleText = "";
            return;
        }

        this.bubbleText = matches;
        this.bubbleElement.addStyleClass("search-matches");
    }
}

WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
{
    WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
}

WebInspector.ProfileGroupSidebarTreeElement.prototype = {
    onselect: function()
    {
        if (this.children.length > 0)
            WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
    }
}

WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;


/* ConsolePanel.js */

/*
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ConsolePanel = function()
{
    WebInspector.Panel.call(this, "console");
}

WebInspector.ConsolePanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Console");
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);

        this._previousConsoleState = WebInspector.drawer.state;
        WebInspector.drawer.enterPanelMode();
        WebInspector.showConsole();
        
        // Move the scope bar to the top of the messages, like the resources filter.
        var scopeBar = document.getElementById("console-filter");
        var consoleMessages = document.getElementById("console-messages");

        scopeBar.parentNode.removeChild(scopeBar);
        document.getElementById("console-view").insertBefore(scopeBar, consoleMessages);
        
        // Update styles, and give console-messages a top margin so it doesn't overwrite the scope bar.
        scopeBar.addStyleClass("console-filter-top");
        scopeBar.removeStyleClass("status-bar-item");

        consoleMessages.addStyleClass("console-filter-top");
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);

        if (this._previousConsoleState === WebInspector.Drawer.State.Hidden)
            WebInspector.drawer.immediatelyExitPanelMode();
        else
            WebInspector.drawer.exitPanelMode();
        delete this._previousConsoleState;
        
        // Move the scope bar back to the bottom bar, next to Clear Console.
        var scopeBar = document.getElementById("console-filter");

        scopeBar.parentNode.removeChild(scopeBar);
        document.getElementById("other-drawer-status-bar-items").appendChild(scopeBar);
        
        // Update styles, and remove the top margin on console-messages.
        scopeBar.removeStyleClass("console-filter-top");
        scopeBar.addStyleClass("status-bar-item");

        document.getElementById("console-messages").removeStyleClass("console-filter-top");
    }
}

WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;

/* ExtensionAPI.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.injectedExtensionAPI = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
{

// Here and below, all constructors are private to API implementation.
// For a public type Foo, if internal fields are present, these are on
// a private FooImpl type, an instance of FooImpl is used in a closure
// by Foo consutrctor to re-bind publicly exported members to an instance
// of Foo.

function EventSinkImpl(type, customDispatch)
{
    this._type = type;
    this._listeners = [];
    this._customDispatch = customDispatch;
}

EventSinkImpl.prototype = {
    addListener: function(callback)
    {
        if (typeof callback != "function")
            throw new "addListener: callback is not a function";
        if (this._listeners.length === 0)
            extensionServer.sendRequest({ command: "subscribe", type: this._type });
        this._listeners.push(callback);
        extensionServer.registerHandler("notify-" + this._type, bind(this._dispatch, this));
    },

    removeListener: function(callback)
    {
        var listeners = this._listeners;

        for (var i = 0; i < listeners.length; ++i) {
            if (listeners[i] === callback) {
                listeners.splice(i, 1);
                break;
            }
        }
        if (this._listeners.length === 0)
            extensionServer.sendRequest({ command: "unsubscribe", type: this._type });
    },

    _fire: function()
    {
        var listeners = this._listeners.slice();
        for (var i = 0; i < listeners.length; ++i)
            listeners[i].apply(null, arguments);
    },

    _dispatch: function(request)
    {
         if (this._customDispatch)
             this._customDispatch.call(this, request);
         else
             this._fire.apply(this, request.arguments);
    }
}

function EventSink(type, customDispatch)
{
    var impl = new EventSinkImpl(type, customDispatch);
    this.addListener = bind(impl.addListener, impl);
    this.removeListener = bind(impl.removeListener, impl);
}

function InspectorExtensionAPI()
{
    this.audits = new Audits();
    this.inspectedWindow = new InspectedWindow();
    this.panels = new Panels();
    this.resources = new Resources();

    this.onReset = new EventSink("reset");
}

InspectorExtensionAPI.prototype = {
    log: function(message)
    {
        extensionServer.sendRequest({ command: "log", message: message });
    }
}

function Resources()
{
    this.onFinished = new EventSink("resource-finished");
}

Resources.prototype = {
    getAll: function(callback)
    {
        return extensionServer.sendRequest({ command: "getResources" }, callback);
    },

    get: function(id, callback)
    {
        return extensionServer.sendRequest({ command: "getResources", id: id }, callback);
    },

    getPageTimings: function(callback)
    {
        return extensionServer.sendRequest({ command: "getPageTimings" }, callback);
    },

    getContent: function(ids, callback)
    {
        return extensionServer.sendRequest({ command: "getResourceContent", ids: ids }, callback);
    }
}

var wellKnownPanelNames = [
    "elements",
    "scripts"
];

function Panels()
{
    var panels = [];
    function panelGetter(name)
    {
        return panels[name];
    }

    for (var i = 0; i < wellKnownPanelNames.length; ++i) {
        var name = wellKnownPanelNames[i];
        panels[name] = new Panel(name);
        this.__defineGetter__(name, bind(panelGetter, null, name));
    }
}

Panels.prototype = {
    create: function(label, pageURL, iconURL, callback)
    {
        var id = "extension-panel-" + extensionServer.nextObjectId();
        function callbackWrapper(result)
        {
            if (result.isError)
                callback(result);
            else {
                panel = new ExtensionPanel(id);
                callback(panel);
            }
        }
        var request = {
            command: "createPanel",
            id: id,
            label: label,
            url: expandURL(pageURL),
            icon: expandURL(iconURL)
        };
        extensionServer.sendRequest(request, callback && bind(callbackWrapper, this));
    }
}

function PanelImpl(id)
{
    this._id = id;
}

PanelImpl.prototype = {
    createSidebarPane: function(title, url, callback)
    {
        var id = "extension-sidebar-" + extensionServer.nextObjectId();
        function callbackWrapper(result)
        {
            if (result.isError)
                callback(result);
            else
                callback(new ExtensionSidebarPane(id));
        }
        extensionServer.sendRequest({ command: "createSidebarPane", panel: this._id, id: id, title: title, url: expandURL(url) }, callback && callbackWrapper);
    }
}

function Panel(id)
{
    var impl = new PanelImpl(id);
    this.createSidebarPane = bind(impl.createSidebarPane, impl);
    this.onSelectionChanged = new EventSink("panel-objectSelected-" + id);
}

function ExtensionPanel(id)
{
    Panel.call(this, id);
    this.onSearch = new EventSink("panel-search-" + id);
}

ExtensionPanel.prototype = {
}

ExtensionPanel.prototype.__proto__ = Panel.prototype;

function ExtensionSidebarPaneImpl(id)
{
    this._id = id;
}

ExtensionSidebarPaneImpl.prototype = {
    setHeight: function(height)
    {
        extensionServer.sendRequest({ command: "setSidebarHeight", id: this._id, height: height });
    },

    setExpanded: function(expanded)
    {
        extensionServer.sendRequest({ command: "setSidebarExpanded", id: this._id, expanded: expanded });
    }
}

function ExtensionSidebarPane(id)
{
    var impl = new ExtensionSidebarPaneImpl(id);
    this.setHeight = bind(impl.setHeight, impl);
    this.setExpanded = bind(impl.setExpanded, impl);
}

function Audits()
{
}

Audits.prototype = {
    addCategory: function(displayName, ruleCount)
    {
        var id = "extension-audit-category-" + extensionServer.nextObjectId();
        extensionServer.sendRequest({ command: "addAuditCategory", id: id, displayName: displayName, ruleCount: ruleCount });
        return new AuditCategory(id);
    }
}

function AuditCategory(id)
{
    function customDispatch(request)
    {
        var auditResult = new AuditResult(request.arguments[0]);
        try {
            this._fire(auditResult);
        } catch (e) {
            console.error("Uncaught exception in extension audit event handler: " + e);
            auditResult.done();
        }
    }
    var impl = new AuditCategoryImpl(id);
    this.onAuditStarted = new EventSink("audit-started-" + id, customDispatch);
}

function AuditCategoryImpl(id)
{
    this._id = id;
}

function AuditResult(id)
{
    var impl = new AuditResultImpl(id);

    this.addResult = bind(impl.addResult, impl);
    this.createResult = bind(impl.createResult, impl);
    this.done = bind(impl.done, impl);

    var formatterTypes = [
        "url",
        "snippet",
        "text"
    ];
    for (var i = 0; i < formatterTypes.length; ++i)
        this[formatterTypes[i]] = bind(impl._nodeFactory, null, formatterTypes[i]);
}

AuditResult.prototype = {
    get Severity()
    {
        return private.audits.Severity;
    }
}

function AuditResultImpl(id)
{
    this._id = id;
}

AuditResultImpl.prototype = {
    addResult: function(displayName, description, severity, details)
    {
        // shorthand for specifying details directly in addResult().
        if (details && !(details instanceof AuditResultNode))
            details = details instanceof Array ? this.createNode.apply(this, details) : this.createNode(details);

        var request = {
            command: "addAuditResult",
            resultId: this._id,
            displayName: displayName,
            description: description,
            severity: severity,
            details: details
        };
        extensionServer.sendRequest(request);
    },

    createResult: function()
    {
        var node = new AuditResultNode();
        node.contents = Array.prototype.slice.call(arguments);
        return node;
    },

    done: function()
    {
        extensionServer.sendRequest({ command: "stopAuditCategoryRun", resultId: this._id });
    },

    _nodeFactory: function(type)
    {
        return {
            type: type,
            arguments: Array.prototype.slice.call(arguments, 1)
        };
    }
}

function AuditResultNode(contents)
{
    this.contents = contents;
    this.children = [];
    this.expanded = false;
}

AuditResultNode.prototype = {
    addChild: function()
    {
        var node = AuditResultImpl.prototype.createResult.apply(null, arguments);
        this.children.push(node);
        return node;
    }
};

function InspectedWindow()
{
    this.onLoaded = new EventSink("inspectedPageLoaded");
    this.onNavigated = new EventSink("inspectedURLChanged");
    this.onDOMContentLoaded = new EventSink("DOMContentLoaded");
}

InspectedWindow.prototype = {
    reload: function()
    {
        return extensionServer.sendRequest({ command: "reload" });
    },

    evaluate: function(expression, callback)
    {
        function callbackWrapper(result)
        {
            if (result && !result.isException)
                result.value = result.value === "undefined" ? undefined : JSON.parse(result.value);
            callback(result);
        }
        return extensionServer.sendRequest({ command: "evaluateOnInspectedPage", expression: expression }, callback && callbackWrapper);
    }
}

function ExtensionServerClient()
{
    this._callbacks = {};
    this._handlers = {};
    this._lastRequestId = 0;
    this._lastObjectId = 0;

    this.registerHandler("callback", bind(this._onCallback, this));

    var channel = new MessageChannel();
    this._port = channel.port1;
    this._port.addEventListener("message", bind(this._onMessage, this), false);
    this._port.start();

    top.postMessage("registerExtension", [ channel.port2 ], "*");
}

ExtensionServerClient.prototype = {
    sendRequest: function(message, callback)
    {
        if (typeof callback === "function")
            message.requestId = this._registerCallback(callback);
        return this._port.postMessage(message);
    },

    registerHandler: function(command, handler)
    {
        this._handlers[command] = handler;
    },

    nextObjectId: function()
    {
        return injectedScriptId + "_" + ++this._lastObjectId;
    },

    _registerCallback: function(callback)
    {
        var id = ++this._lastRequestId;
        this._callbacks[id] = callback;
        return id;
    },

    _onCallback: function(request)
    {
        if (request.requestId in this._callbacks) {
            this._callbacks[request.requestId](request.result);
            delete this._callbacks[request.requestId];
        }
    },

    _onMessage: function(event)
    {
        var request = event.data;
        var handler = this._handlers[request.command];
        if (handler)
            handler.call(this, request);
    }
}

function expandURL(url)
{
    if (!url)
        return url;
    if (/^[^/]+:/.exec(url)) // See if url has schema.
        return url;
    var baseURL = location.protocol + "//" + location.hostname + location.port;
    if (/^\//.exec(url))
        return baseURL + url;
    return baseURL + location.pathname.replace(/\/[^/]*$/,"/") + url;
}

function bind(func, thisObject)
{
    var args = Array.prototype.slice.call(arguments, 2);
    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };
}

var extensionServer = new ExtensionServerClient();

webInspector = new InspectorExtensionAPI();

}

/* ExtensionAuditCategory.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ExtensionAuditCategory = function(id, displayName, ruleCount)
{
    this._id = id;
    this._displayName = displayName;
    this._ruleCount  = ruleCount;
}

WebInspector.ExtensionAuditCategory.prototype = {
    // AuditCategory interface
    get id()
    {
        return this._id;
    },

    get displayName()
    {
        return this._displayName;
    },

    get ruleCount()
    {
        return this._ruleCount;
    },

    run: function(resources, callback)
    {
        new WebInspector.ExtensionAuditCategoryResults(this, callback);
    }
}

WebInspector.ExtensionAuditCategoryResults = function(category, callback)
{
    this._category = category;
    this._pendingRules = category.ruleCount;
    this._ruleCompletionCallback = callback;

    this.id = category.id + "-" + ++WebInspector.ExtensionAuditCategoryResults._lastId;
    WebInspector.extensionServer.startAuditRun(category, this);
}

WebInspector.ExtensionAuditCategoryResults.prototype = {
    get complete()
    {
        return !this._pendingRules;
    },

    cancel: function()
    {
        while (!this.complete)
            this._addResult(null);
    },

    addResult: function(displayName, description, severity, details)
    {
        var result = new WebInspector.AuditRuleResult(displayName);
        result.addChild(description);
        result.severity = severity;
        if (details)
            this._addNode(result, details);
        this._addResult(result);
    },

    _addNode: function(parent, node)
    {
        var addedNode = parent.addChild(node.contents, node.expanded);
        if (node.children) {
            for (var i = 0; i < node.children.length; ++i)
                this._addNode(addedNode, node.children[i]);
        }
    },

    _addResult: function(result)
    {
        this._ruleCompletionCallback(result);
        this._pendingRules--;
        if (!this._pendingRules)
            WebInspector.extensionServer.stopAuditRun(this);
    }
}

WebInspector.ExtensionAuditCategoryResults._lastId = 0;

/* ExtensionCommon.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.commonExtensionSymbols = function(private)
{
    
    if (!private.audits)
        private.audits = {};

    private.audits.Severity = {
        Info: "info",
        Warning: "warning",
        Severe: "severe"
    };
}

WebInspector.extensionAPI = {};

WebInspector.commonExtensionSymbols(WebInspector.extensionAPI);

/* ExtensionServer.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ExtensionServer = function()
{
    this._clientObjects = {};
    this._handlers = {};
    this._subscribers = {};
    this._status = new WebInspector.ExtensionStatus();

    this._registerHandler("subscribe", this._onSubscribe.bind(this));
    this._registerHandler("unsubscribe", this._onUnsubscribe.bind(this));
    this._registerHandler("getResources", this._onGetResources.bind(this));
    this._registerHandler("getResourceContent", this._onGetResourceContent.bind(this));
    this._registerHandler("getPageTimings", this._onGetPageTimings.bind(this));
    this._registerHandler("createPanel", this._onCreatePanel.bind(this));
    this._registerHandler("createSidebarPane", this._onCreateSidebar.bind(this));
    this._registerHandler("log", this._onLog.bind(this));
    this._registerHandler("evaluateOnInspectedPage", this._onEvaluateOnInspectedPage.bind(this));
    this._registerHandler("setSidebarHeight", this._onSetSidebarHeight.bind(this));
    this._registerHandler("setSidebarExpanded", this._onSetSidebarExpansion.bind(this));

    this._registerHandler("addAuditCategory", this._onAddAuditCategory.bind(this));
    this._registerHandler("addAuditResult", this._onAddAuditResult.bind(this));
    this._registerHandler("stopAuditCategoryRun", this._onStopAuditCategoryRun.bind(this));

    window.addEventListener("message", this._onWindowMessage.bind(this), false);
}

WebInspector.ExtensionServer.prototype = {
    notifyPanelShown: function(panelName)
    {
        this._postNotification("panel-shown-" + panelName);
    },

    notifyObjectSelected: function(panelId, objectType, objectId)
    {
        this._postNotification("panel-objectSelected-" + panelId, objectType, objectId);
    },

    notifyResourceFinished: function(resource)
    {
        this._postNotification("resource-finished", this._convertResource(resource));
    },

    notifySearchAction: function(panelId, action, searchString)
    {
        this._postNotification("panel-search-" + panelId, action, searchString);
    },

    notifyInspectedPageLoaded: function()
    {
        this._postNotification("inspectedPageLoaded");
    },

    notifyInspectedURLChanged: function()
    {
        this._postNotification("inspectedURLChanged");
    },

    notifyInspectorReset: function()
    {
        this._postNotification("reset");
    },

    startAuditRun: function(category, auditRun)
    {
        this._clientObjects[auditRun.id] = auditRun;
        this._postNotification("audit-started-" + category.id, auditRun.id);
    },

    stopAuditRun: function(auditRun)
    {
        delete this._clientObjects[auditRun.id];
    },

    _convertResource: function(resource)
    {
        return {
            id: resource.identifier,
            type: WebInspector.Resource.Type.toString(resource.type),
            har: (new WebInspector.HAREntry(resource)).build(),
        };
    },

    _postNotification: function(type, details)
    {
        var subscribers = this._subscribers[type];
        if (!subscribers)
            return;
        var message = {
            command: "notify-" + type,
            arguments: Array.prototype.slice.call(arguments, 1)
        };
        for (var i = 0; i < subscribers.length; ++i)
            subscribers[i].postMessage(message);
    },

    _onSubscribe: function(message, port)
    {
        var subscribers = this._subscribers[message.type];
        if (subscribers)
            subscribers.push(port);
        else
            this._subscribers[message.type] = [ port ];
    },

    _onUnsubscribe: function(message, port)
    {
        var subscribers = this._subscribers[message.type];
        if (!subscribers)
            return;
        subscribers.remove(port);
        if (!subscribers.length)
            delete this._subscribers[message.type];
    },

    _onCreatePanel: function(message, port)
    {
        var id = message.id;
        // The ids are generated on the client API side and must be unique, so the check below
        // shouldn't be hit unless someone is bypassing the API.
        if (id in this._clientObjects || id in WebInspector.panels)
            return this._status.E_EXISTS(id);
        var panel = new WebInspector.ExtensionPanel(id, message.label, message.icon);
        this._clientObjects[id] = panel;

        var toolbarElement = document.getElementById("toolbar");
        var lastToolbarItem = WebInspector.panelOrder[WebInspector.panelOrder.length - 1].toolbarItem;
        WebInspector.addPanelToolbarIcon(toolbarElement, panel, lastToolbarItem);
        WebInspector.panels[id] = panel;
        var iframe = this._createClientIframe(panel.element, message.url);
        iframe.style.height = "100%";
        return this._status.OK();
    },

    _onCreateSidebar: function(message, port)
    {
        var panel = WebInspector.panels[message.panel];
        if (!panel)
            return this._status.E_NOTFOUND(message.panel);
        if (!panel.sidebarElement || !panel.sidebarPanes)
            return this._status.E_NOTSUPPORTED();
        var id = message.id;
        var sidebar = new WebInspector.SidebarPane(message.title);
        this._clientObjects[id] = sidebar;
        panel.sidebarPanes[id] = sidebar;
        panel.sidebarElement.appendChild(sidebar.element);
        this._createClientIframe(sidebar.bodyElement, message.url);
        return this._status.OK();
    },

    _createClientIframe: function(parent, url, requestId, port)
    {
        var iframe = document.createElement("iframe");
        iframe.src = url;
        iframe.style.width = "100%";
        parent.appendChild(iframe);
        return iframe;
    },

    _onSetSidebarHeight: function(message)
    {
        var sidebar = this._clientObjects[message.id];
        if (!sidebar)
            return this._status.E_NOTFOUND(message.id);
        sidebar.bodyElement.firstChild.style.height = message.height;
    },

    _onSetSidebarExpansion: function(message)
    {
        var sidebar = this._clientObjects[message.id];
        if (!sidebar)
            return this._status.E_NOTFOUND(message.id);
        if (message.expanded)
            sidebar.expand();
        else
            sidebar.collapse();
    },

    _onLog: function(message)
    {
        WebInspector.log(message.message);
    },

    _onEvaluateOnInspectedPage: function(message, port)
    {
        function callback(resultPayload)
        {
            var resultObject = WebInspector.RemoteObject.fromPayload(resultPayload);
            var result = {};
            if (resultObject.isError())
                result.isException = true;
            result.value = resultObject.description;
            this._dispatchCallback(message.requestId, port, result);
        }
        var evalExpression = "JSON.stringify(eval('" +
            "with (window.console._commandLineAPI) with (window) {' + unescape('" + escape(message.expression) +
            "') + '}'));";
        InjectedScriptAccess.getDefault().evaluate(evalExpression, callback.bind(this));
    },

    _onRevealAndSelect: function(message)
    {
        if (message.panelId === "resources" && type === "resource")
            return this._onRevealAndSelectResource(message);
        else
            return this._status.E_NOTSUPPORTED(message.panelId, message.type);
    },

    _onRevealAndSelectResource: function(message)
    {
        var id = message.id;
        var resource = null;

        resource = typeof id === "number" ? WebInspector.resources[id] : WebInspector.resourceForURL(id);
        if (!resource)
            return this._status.E_NOTFOUND(typeof id + ": " + id);
        WebInspector.panels.resources.showResource(resource, message.line);
        WebInspector.showPanel("resources");
    },

    _dispatchCallback: function(requestId, port, result)
    {
        port.postMessage({ command: "callback", requestId: requestId, result: result });
    },

    _onGetResources: function(request)
    {
        function resourceWrapper(id)
        {
            return WebInspector.extensionServer._convertResource(WebInspector.resources[id]);
        }

        var response;
        if (request.id)
            response = WebInspector.resources[request.id] ? resourceWrapper(request.id) : this._status.E_NOTFOUND(request.id);
        else
            response = Object.properties(WebInspector.resources).map(resourceWrapper);
        return response;
    },

    _onGetResourceContent: function(message, port)
    {
        var ids;
        var response = [];

        function onContentAvailable(id, encoded, content)
        {
            var resourceContent = {
                id: id,
                encoding: encoded ? "base64" : "",
                content: content
            };
            response.push(resourceContent);
            if (response.length === ids.length)
                this._dispatchCallback(message.requestId, port, response);
        }

        if (typeof message.ids === "number") {
            ids = [ message.ids ];
        } else if (message.ids instanceof Array) {
            ids = message.ids;
        } else {
            return this._status.E_BADARGTYPE("message.ids", "Array", typeof message.ids);
        }

        for (var i = 0; i < ids.length; ++i) {
            var id = ids[i];
            var resource = WebInspector.resources[id];
            if (!resource)
                response.push(this._status.E_NOTFOUND(id));
            else {
                var encode = !WebInspector.Resource.Type.isTextType(resource.type);
                WebInspector.getEncodedResourceContent(id, encode, onContentAvailable.bind(this, id, encode));
            }
        }
        if (response.length === ids.length)
            this._dispatchCallback(message.requestId, port, response);
    },

    _onGetPageTimings: function()
    {
        return (new WebInspector.HARLog()).buildMainResourceTimings();
    },

    _onAddAuditCategory: function(request)
    {
        var category = new WebInspector.ExtensionAuditCategory(request.id, request.displayName, request.ruleCount);
        if (WebInspector.panels.audits.getCategory(category.id))
            return this._status.E_EXISTS(category.id);
        this._clientObjects[request.id] = category;
        WebInspector.panels.audits.addCategory(category);
    },

    _onAddAuditResult: function(request)
    {
        var auditResult = this._clientObjects[request.resultId];
        if (!auditResult)
            return this._status.E_NOTFOUND(request.resultId);
        try {
            auditResult.addResult(request.displayName, request.description, request.severity, request.details);
        } catch (e) {
            return e;
        }
        return this._status.OK();
    },

    _onStopAuditCategoryRun: function(request)
    {
        var auditRun = this._clientObjects[request.resultId];
        if (!auditRun)
            return this._status.E_NOTFOUND(request.resultId);
        auditRun.cancel();
    },

    initExtensions: function()
    {
        InspectorExtensionRegistry.getExtensionsAsync();
    },

    _addExtensions: function(extensions)
    {
        // See ExtensionAPI.js and ExtensionCommon.js for details.
        InspectorFrontendHost.setExtensionAPI(this._buildExtensionAPIInjectedScript());
        for (var i = 0; i < extensions.length; ++i) {
            var extension = extensions[i];
            try {
                if (!extension.startPage)
                    return;
                var iframe = document.createElement("iframe");
                iframe.src = extension.startPage;
                iframe.style.display = "none";
                document.body.appendChild(iframe);
            } catch (e) {
                console.error("Failed to initialize extension " + extension.startPage + ":" + e);
            }
        }
    },

    _buildExtensionAPIInjectedScript: function()
    {
        var resourceTypes = {};
        var resourceTypeProperties = Object.getOwnPropertyNames(WebInspector.Resource.Type);
        for (var i = 0; i < resourceTypeProperties.length; ++i) {
             var propName = resourceTypeProperties[i];
             var propValue = WebInspector.Resource.Type[propName];
             if (typeof propValue === "number")
                 resourceTypes[propName] = WebInspector.Resource.Type.toString(propValue);
        }
        var platformAPI = WebInspector.buildPlatformExtensionAPI ? WebInspector.buildPlatformExtensionAPI() : "";
        return "(function(){ " +
            "var private = {};" +
            "(" + WebInspector.commonExtensionSymbols.toString() + ")(private);" +
            "(" + WebInspector.injectedExtensionAPI.toString() + ").apply(this, arguments);" +
            "webInspector.resources.Types = " + JSON.stringify(resourceTypes) + ";" +
            platformAPI +
            "})";
    },

    _onWindowMessage: function(event)
    {
        if (event.data !== "registerExtension")
            return;
        var port = event.ports[0];
        port.addEventListener("message", this._onmessage.bind(this), false);
        port.start();
    },

    _onmessage: function(event)
    {
        var request = event.data;
        var result;

        if (request.command in this._handlers)
            result = this._handlers[request.command](request, event.target);
        else
            result = this._status.E_NOTSUPPORTED(request.command);

        if (result && request.requestId)
            this._dispatchCallback(request.requestId, event.target, result);
    },

    _registerHandler: function(command, callback)
    {
        this._handlers[command] = callback;
    }
}

WebInspector.ExtensionServer._statuses =
{
    OK: "",
    E_EXISTS: "Object already exists: %s",
    E_BADARG: "Invalid argument %s: %s",
    E_BADARGTYPE: "Invalid type for argument %s: got %s, expected %s",
    E_NOTFOUND: "Object not found: %s",
    E_NOTSUPPORTED: "Object does not support requested operation: %s",
}

WebInspector.ExtensionStatus = function()
{
    function makeStatus(code)
    {
        var description = WebInspector.ExtensionServer._statuses[code] || code;
        var details = Array.prototype.slice.call(arguments, 1);
        var status = { code: code, description: description, details: details };
        if (code !== "OK") {
            status.isError = true;
            console.log("Extension server error: " + String.vsprintf(description, details));
        }
        return status;
    }
    for (status in WebInspector.ExtensionServer._statuses)
        this[status] = makeStatus.bind(null, status);
}

WebInspector.addExtensions = function(extensions)
{
    WebInspector.extensionServer._addExtensions(extensions);
}

WebInspector.extensionServer = new WebInspector.ExtensionServer();

WebInspector.getEncodedResourceContent = function(identifier, encode, callback)
{
    InspectorBackend.getResourceContent(identifier, encode, callback);
}

/* ExtensionPanel.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ExtensionPanel = function(id, label, iconURL, options)
{
    this.toolbarItemLabel = label;
    this._addStyleRule(".toolbar-item." + id + " .toolbar-icon", "background-image: url(" + iconURL + ");");
    WebInspector.Panel.call(this, id);
}

WebInspector.ExtensionPanel.prototype = {
    get defaultFocusedElement()
    {
        return this.sidebarTreeElement || this.element;
    },

    updateMainViewWidth: function(width)
    {
        this.bodyElement.style.left = width + "px";
        this.resize();
    },

    searchCanceled: function(startingNewSearch)
    {
        WebInspector.extensionServer.notifySearchAction(this._id, "cancelSearch");
        WebInspector.Panel.prototype.searchCanceled.apply(this, arguments);
    },

    performSearch: function(query)
    {
        WebInspector.extensionServer.notifySearchAction(this._id, "performSearch", query);
        WebInspector.Panel.prototype.performSearch.apply(this, arguments);
    },

    jumpToNextSearchResult: function()
    {
        WebInspector.extensionServer.notifySearchAction(this._id, "nextSearchResult");
        WebInspector.Panel.prototype.jumpToNextSearchResult.call(this);
    },

    jumpToPreviousSearchResult: function()
    {
        WebInspector.extensionServer.notifySearchAction(this._id, "previousSearchResult");
        WebInspector.Panel.prototype.jumpToPreviousSearchResult.call(this);
    },

    _addStyleRule: function(selector, body)
    {
        var style = document.createElement("style");
        style.textContent = selector + " { " + body + " }";
        document.head.appendChild(style);
    }
}

WebInspector.ExtensionPanel.prototype.__proto__ = WebInspector.Panel.prototype;

/* AuditsPanel.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AuditsPanel = function()
{
    WebInspector.Panel.call(this, "audits");

    this.createSidebar();
    this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true);
    this.sidebarTree.appendChild(this.auditsTreeElement);
    this.auditsTreeElement.listItemElement.addStyleClass("hidden");
    this.auditsTreeElement.expand();

    this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement();
    this.auditsTreeElement.appendChild(this.auditsItemTreeElement);

    this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true);
    this.sidebarTree.appendChild(this.auditResultsTreeElement);
    this.auditResultsTreeElement.expand();

    this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear audit results."), "clear-status-bar-item");
    this.clearResultsButton.addEventListener("click", this._clearButtonClicked.bind(this), false);

    this.viewsContainerElement = document.createElement("div");
    this.viewsContainerElement.id = "audit-views";
    this.element.appendChild(this.viewsContainerElement);

    this._constructCategories();

    this._launcherView = new WebInspector.AuditLauncherView(this.initiateAudit.bind(this));
    for (id in this.categoriesById)
        this._launcherView.addCategory(this.categoriesById[id]);
}

WebInspector.AuditsPanel.prototype = {
    get toolbarItemLabel()
    {
        return WebInspector.UIString("Audits");
    },

    get statusBarItems()
    {
        return [this.clearResultsButton.element];
    },

    get mainResourceLoadTime()
    {
        return this._mainResourceLoadTime;
    },

    set mainResourceLoadTime(x)
    {
        this._mainResourceLoadTime = x;
        this._didMainResourceLoad();
    },

    get mainResourceDOMContentTime()
    {
        return this._mainResourceDOMContentTime;
    },

    set mainResourceDOMContentTime(x)
    {
        this._mainResourceDOMContentTime = x;
    },

    get categoriesById()
    {
        return this._auditCategoriesById;
    },

    resourceStarted: function(resource)
    {
        this._launcherView.resourceStarted(resource);
    },

    resourceFinished: function(resource)
    {
        this._launcherView.resourceFinished(resource);
    },

    addCategory: function(category)
    {
        this.categoriesById[category.id] = category;
        this._launcherView.addCategory(category);
    },

    getCategory: function(id)
    {
        return this.categoriesById[id];
    },

    _constructCategories: function()
    {
        this._auditCategoriesById = {};
        for (var categoryCtorID in WebInspector.AuditCategories) {
            var auditCategory = new WebInspector.AuditCategories[categoryCtorID]();
            auditCategory._id = categoryCtorID;
            this.categoriesById[categoryCtorID] = auditCategory;
        }
    },

    _executeAudit: function(categories, resultCallback)
    {
        var resources = [];
        for (var id in WebInspector.resources)
            resources.push(WebInspector.resources[id]);

        var rulesRemaining = 0;
        for (var i = 0; i < categories.length; ++i)
            rulesRemaining += categories[i].ruleCount;

        var results = [];
        var mainResourceURL = WebInspector.mainResource.url;

        function ruleResultReadyCallback(categoryResult, ruleResult)
        {
            if (ruleResult && ruleResult.children)
                categoryResult.addRuleResult(ruleResult);

            --rulesRemaining;

            if (!rulesRemaining && resultCallback)
                resultCallback(mainResourceURL, results);
        }

        if (!rulesRemaining) {
            resultCallback(mainResourceURL, results);
            return;
        }

        for (var i = 0; i < categories.length; ++i) {
            var category = categories[i];
            var result = new WebInspector.AuditCategoryResult(category);
            results.push(result);
            category.run(resources, ruleResultReadyCallback.bind(null, result));
        }
    },

    _auditFinishedCallback: function(launcherCallback, mainResourceURL, results)
    {
        var children = this.auditResultsTreeElement.children;
        var ordinal = 1;
        for (var i = 0; i < children.length; ++i) {
            if (children[i].mainResourceURL === mainResourceURL)
                ordinal++;
        }

        var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(results, mainResourceURL, ordinal);
        this.auditResultsTreeElement.appendChild(resultTreeElement);
        resultTreeElement.reveal();
        resultTreeElement.select();
        if (launcherCallback)
            launcherCallback();
    },

    initiateAudit: function(categoryIds, runImmediately, launcherCallback)
    {
        if (!categoryIds || !categoryIds.length)
            return;

        var categories = [];
        for (var i = 0; i < categoryIds.length; ++i)
            categories.push(this.categoriesById[categoryIds[i]]);

        function initiateAuditCallback(categories, launcherCallback)
        {
            this._executeAudit(categories, this._auditFinishedCallback.bind(this, launcherCallback));
        }

        if (runImmediately)
            initiateAuditCallback.call(this, categories, launcherCallback);
        else
            this._reloadResources(initiateAuditCallback.bind(this, categories, launcherCallback));
    },

    _reloadResources: function(callback)
    {
        this._resourceTrackingCallback = callback;

        if (!WebInspector.panels.resources.resourceTrackingEnabled) {
            WebInspector.panels.resources.toggleResourceTracking(false);
            this._updateLauncherViewControls(true);
        } else
            InspectorBackend.reloadPage();
    },

    _didMainResourceLoad: function()
    {
        if (this._resourceTrackingCallback) {
            var callback = this._resourceTrackingCallback;
            delete this._resourceTrackingCallback;
            callback();
        }
    },

    showResults: function(categoryResults)
    {
        if (!categoryResults._resultView)
            categoryResults._resultView = new WebInspector.AuditResultView(categoryResults);

        this.visibleView = categoryResults._resultView;
    },

    showLauncherView: function()
    {
        this.visibleView = this._launcherView;
    },

    get visibleView()
    {
        return this._visibleView;
    },

    set visibleView(x)
    {
        if (this._visibleView === x)
            return;

        if (this._visibleView)
            this._visibleView.hide();

        this._visibleView = x;

        if (x)
            x.show(this.viewsContainerElement);
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        this._updateLauncherViewControls(WebInspector.panels.resources.resourceTrackingEnabled);
    },

    reset: function()
    {
        this._launcherView.reset();
    },

    attach: function()
    {
        WebInspector.Panel.prototype.attach.call(this);

        this.auditsItemTreeElement.select();
    },

    updateMainViewWidth: function(width)
    {
        this.viewsContainerElement.style.left = width + "px";
    },

    _updateLauncherViewControls: function(isTracking)
    {
        if (this._launcherView)
            this._launcherView.updateResourceTrackingState(isTracking);
    },

    _clearButtonClicked: function()
    {
        this.auditsItemTreeElement.reveal();
        this.auditsItemTreeElement.select();
        this.auditResultsTreeElement.removeChildren();
    }
}

WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype;



WebInspector.AuditCategory = function(displayName)
{
    this._displayName = displayName;
    this._rules = [];
}

WebInspector.AuditCategory.prototype = {
    get id()
    {
        // this._id value is injected at construction time.
        return this._id;
    },

    get displayName()
    {
        return this._displayName;
    },

    get ruleCount()
    {
        this._ensureInitialized();
        return this._rules.length;
    },

    addRule: function(rule, severity)
    {
        rule.severity = severity;
        this._rules.push(rule);
    },

    run: function(resources, callback)
    {
        this._ensureInitialized();
        for (var i = 0; i < this._rules.length; ++i)
            this._rules[i].run(resources, callback);
    },

    _ensureInitialized: function()
    {
        if (!this._initialized) {
            if ("initialize" in this)
                this.initialize();
            this._initialized = true;
        }
    }
}


WebInspector.AuditRule = function(id, displayName)
{
    this._id = id;
    this._displayName = displayName;
}

WebInspector.AuditRule.Severity = {
    Info: "info",
    Warning: "warning",
    Severe: "severe"
}

WebInspector.AuditRule.SeverityOrder = {
    "info": 3,
    "warning": 2,
    "severe": 1
}

WebInspector.AuditRule.prototype = {
    get id()
    {
        return this._id;
    },

    get displayName()
    {
        return this._displayName;
    },

    set severity(severity)
    {
        this._severity = severity;
    },

    run: function(resources, callback)
    {
        var result = new WebInspector.AuditRuleResult(this.displayName);
        result.severity = this._severity;
        this.doRun(resources, result, callback);
    },

    doRun: function(resources, result, callback)
    {
        throw new Error("doRun() not implemented");
    }
}

WebInspector.AuditCategoryResult = function(category)
{
    this.title = category.displayName;
    this.ruleResults = [];
}

WebInspector.AuditCategoryResult.prototype = {
    addRuleResult: function(ruleResult)
    {
        this.ruleResults.push(ruleResult);
    }
}

WebInspector.AuditRuleResult = function(value, expanded, className)
{
    this.value = value;
    this.className = className;
    this.expanded = expanded;
    this.violationCount = 0;
}

WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
{
    return WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url));
}

WebInspector.AuditRuleResult.resourceDomain = function(domain)
{
    return domain || WebInspector.UIString("[empty domain]");
}

WebInspector.AuditRuleResult.prototype = {
    addChild: function(value, expanded, className)
    {
        if (!this.children)
            this.children = [];
        var entry = new WebInspector.AuditRuleResult(value, expanded, className);
        this.children.push(entry);
        return entry;
    },

    addURL: function(url)
    {
        return this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url));
    },

    addURLs: function(urls)
    {
        for (var i = 0; i < urls.length; ++i)
            this.addURL(urls[i]);
    },

    addSnippet: function(snippet)
    {
        return this.addChild(snippet, false, "source-code");
    }
}

WebInspector.AuditsSidebarTreeElement = function()
{
    this.small = false;

    WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false);
}

WebInspector.AuditsSidebarTreeElement.prototype = {
    onattach: function()
    {
        WebInspector.SidebarTreeElement.prototype.onattach.call(this);
    },

    onselect: function()
    {
        WebInspector.panels.audits.showLauncherView();
    },

    get selectable()
    {
        return true;
    },

    refresh: function()
    {
        this.refreshTitles();
    }
}

WebInspector.AuditsSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;


WebInspector.AuditResultSidebarTreeElement = function(results, mainResourceURL, ordinal)
{
    this.results = results;
    this.mainResourceURL = mainResourceURL;

    WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false);
}

WebInspector.AuditResultSidebarTreeElement.prototype = {
    onselect: function()
    {
        WebInspector.panels.audits.showResults(this.results);
    },

    get selectable()
    {
        return true;
    }
}

WebInspector.AuditResultSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;

// Contributed audit rules should go into this namespace.
WebInspector.AuditRules = {};

// Contributed audit categories should go into this namespace.
WebInspector.AuditCategories = {};

/* AuditResultView.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AuditResultView = function(categoryResults)
{
    WebInspector.View.call(this);
    this.element.className = "audit-result-view";

    function categorySorter(a, b) {
        return (a.title || "").localeCompare(b.title || "");
    }
    categoryResults.sort(categorySorter);
    for (var i = 0; i < categoryResults.length; ++i)
        this.element.appendChild(new WebInspector.AuditCategoryResultPane(categoryResults[i]).element);
}

WebInspector.AuditResultView.prototype.__proto__ = WebInspector.View.prototype;


WebInspector.AuditCategoryResultPane = function(categoryResult)
{
    WebInspector.SidebarPane.call(this, categoryResult.title);
    var treeOutlineElement = document.createElement("ol");
    this.bodyElement.addStyleClass("audit-result-tree");
    this.bodyElement.appendChild(treeOutlineElement);

    this._treeOutline = new TreeOutline(treeOutlineElement);
    this._treeOutline.expandTreeElementsWhenArrowing = true;

    function ruleSorter(a, b)
    {
        var result = WebInspector.AuditRule.SeverityOrder[a.severity || 0] - WebInspector.AuditRule.SeverityOrder[b.severity || 0];
        if (!result)
            result = (a.value || "").localeCompare(b.value || "");
        return result;
    }

    categoryResult.ruleResults.sort(ruleSorter);

    for (var i = 0; i < categoryResult.ruleResults.length; ++i) {
        var ruleResult = categoryResult.ruleResults[i];
        var treeElement = this._appendResult(this._treeOutline, ruleResult);
        treeElement.listItemElement.addStyleClass("audit-result");

        if (ruleResult.severity) {
            var severityElement = document.createElement("img");
            severityElement.className = "severity-" + ruleResult.severity;
            treeElement.listItemElement.appendChild(severityElement);
        }
    }
    this.expand();
}

WebInspector.AuditCategoryResultPane.prototype = {
    _appendResult: function(parentTreeElement, result)
    {
        var title = "";
        
        if (typeof result.value === "string") {
            title = result.value;
            if (result.violationCount)
                title = String.sprintf("%s (%d)", title, result.violationCount);
        }

        var treeElement = new TreeElement(title, null, !!result.children);
        parentTreeElement.appendChild(treeElement);

        if (result.className)
            treeElement.listItemElement.addStyleClass(result.className);
        if (typeof result.value !== "string")
            treeElement.listItemElement.appendChild(WebInspector.applyFormatters(result.value));

        if (result.children) {
            for (var i = 0; i < result.children.length; ++i)
                this._appendResult(treeElement, result.children[i]);
        }
        if (result.expanded) {
            treeElement.listItemElement.removeStyleClass("parent");
            treeElement.listItemElement.addStyleClass("parent-expanded");
            treeElement.expand();
        }
        return treeElement;
    }
}

WebInspector.AuditCategoryResultPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;

/* AuditLauncherView.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AuditLauncherView = function(runnerCallback)
{
    WebInspector.View.call(this);
    this._runnerCallback = runnerCallback;
    this._categoryIdPrefix = "audit-category-item-";
    this._auditRunning = false;

    this.element.addStyleClass("audit-launcher-view");

    this._contentElement = document.createElement("div");
    this._contentElement.className = "audit-launcher-view-content";
    this.element.appendChild(this._contentElement);
    this._boundCategoryClickListener = this._categoryClicked.bind(this);

    this._resetResourceCount();

    this._sortedCategories = [];

    this._headerElement = document.createElement("h1");
    this._headerElement.className = "no-audits";
    this._headerElement.textContent = WebInspector.UIString("No audits to run");
    this._contentElement.appendChild(this._headerElement);
}

WebInspector.AuditLauncherView.prototype = {
    updateResourceTrackingState: function(isTracking)
    {
        if (!this._auditPresentStateLabelElement)
            return;

        if (isTracking) {
            this._auditPresentStateLabelElement.nodeValue = WebInspector.UIString("Audit Present State");
            this._auditPresentStateElement.disabled = false;
            this._auditPresentStateElement.parentElement.removeStyleClass("disabled");
        } else {
            this._resetResourceCount();
            this._auditPresentStateLabelElement.nodeValue = WebInspector.UIString("Audit Present State (Resource Tracking must be enabled)");
            this._auditPresentStateElement.disabled = true;
            this._auditPresentStateElement.parentElement.addStyleClass("disabled");
            this.auditReloadedStateElement.checked = true;
        }
    },

    get totalResources()
    {
        return this._totalResources;
    },

    set totalResources(x)
    {
        if (this._totalResources === x)
            return;
        this._totalResources = x;
        this._updateResourceProgress();
    },

    get loadedResources()
    {
        return this._loadedResources;
    },

    set loadedResources(x)
    {
        if (this._loadedResources === x)
            return;
        this._loadedResources = x;
        this._updateResourceProgress();
    },

    _resetResourceCount: function()
    {
        this.loadedResources = 0;

        // We never receive a resourceStarted notification for the main resource
        // (see InspectorController.willSendRequest())
        this.totalResources = 1;
    },

    resourceStarted: function(resource)
    {
        ++this.totalResources;
    },

    resourceFinished: function(resource)
    {
        ++this.loadedResources;
    },

    reset: function()
    {
        this._resetResourceCount();
    },

    addCategory: function(category)
    {
        if (!this._sortedCategories.length)
            this._createLauncherUI();

        var categoryElement = this._createCategoryElement(category.displayName, category.id);
        category._checkboxElement = categoryElement.firstChild;
        if (this._selectAllCheckboxElement.checked) {
            category._checkboxElement.checked = true;
            ++this._currentCategoriesCount;
        }

        function compareCategories(a, b)
        {
            var aTitle = a.displayName || "";
            var bTitle = b.displayName || "";
            return aTitle.localeCompare(bTitle);
        }
        var insertBefore = insertionIndexForObjectInListSortedByFunction(category, this._sortedCategories, compareCategories);
        this._categoriesElement.insertBefore(categoryElement, this._categoriesElement.children[insertBefore]);
        this._sortedCategories.splice(insertBefore, 0, category);
        this._updateButton();
    },

    _setAuditRunning: function(auditRunning)
    {
        if (this._auditRunning === auditRunning)
            return;
        this._auditRunning = auditRunning;
        this._updateButton();
        this._updateResourceProgress();
    },

    _launchButtonClicked: function(event)
    {
        var catIds = [];
        var childNodes = this._categoriesElement.childNodes;
        for (var category = 0; category < this._sortedCategories.length; ++category) {
            if (this._sortedCategories[category]._checkboxElement.checked)
                catIds.push(this._sortedCategories[category].id);
        }

        this._setAuditRunning(true);
        this._runnerCallback(catIds, this._auditPresentStateElement.checked, this._setAuditRunning.bind(this, false));
    },

    _selectAllClicked: function(checkCategories)
    {
        var childNodes = this._categoriesElement.childNodes;
        for (var i = 0, length = childNodes.length; i < length; ++i)
            childNodes[i].firstChild.checked = checkCategories;
        this._currentCategoriesCount = checkCategories ? this._sortedCategories.length : 0;
        this._updateButton();
    },

    _categoryClicked: function(event)
    {
        this._currentCategoriesCount += event.target.checked ? 1 : -1;
        this._selectAllCheckboxElement.checked = this._currentCategoriesCount === this._sortedCategories.length;
        this._updateButton();
    },

    _createCategoryElement: function(title, id)
    {
        var labelElement = document.createElement("label");
        labelElement.id = this._categoryIdPrefix + id;

        var element = document.createElement("input");
        element.type = "checkbox";
        if (id !== "")
            element.addEventListener("click", this._boundCategoryClickListener, false);
        labelElement.appendChild(element);
        labelElement.appendChild(document.createTextNode(title));

        return labelElement;
    },

    _createLauncherUI: function()
    {
        this._headerElement = document.createElement("h1");
        this._headerElement.textContent = WebInspector.UIString("Select audits to run");

        for (var child = 0; child < this._contentElement.children.length; ++child)
            this._contentElement.removeChild(this._contentElement.children[child]);

        this._contentElement.appendChild(this._headerElement);

        function handleSelectAllClick(event)
        {
            this._selectAllClicked(event.target.checked);
        }
        var categoryElement = this._createCategoryElement(WebInspector.UIString("Select All"), "");
        categoryElement.id = "audit-launcher-selectall";
        this._selectAllCheckboxElement = categoryElement.firstChild;
        this._selectAllCheckboxElement.checked = true;
        this._selectAllCheckboxElement.addEventListener("click", handleSelectAllClick.bind(this), false);
        this._contentElement.appendChild(categoryElement);

        this._categoriesElement = document.createElement("div");
        this._categoriesElement.className = "audit-categories-container";
        this._contentElement.appendChild(this._categoriesElement);

        this._currentCategoriesCount = 0;

        var flexibleSpaceElement = document.createElement("div");
        flexibleSpaceElement.className = "flexible-space";
        this._contentElement.appendChild(flexibleSpaceElement);

        this._buttonContainerElement = document.createElement("div");
        this._buttonContainerElement.className = "button-container";

        var labelElement = document.createElement("label");
        this._auditPresentStateElement = document.createElement("input");
        this._auditPresentStateElement.name = "audit-mode";
        this._auditPresentStateElement.type = "radio";
        this._auditPresentStateElement.checked = true;
        this._auditPresentStateLabelElement = document.createTextNode("");
        labelElement.appendChild(this._auditPresentStateElement);
        labelElement.appendChild(this._auditPresentStateLabelElement);
        this._buttonContainerElement.appendChild(labelElement);

        labelElement = document.createElement("label");
        this.auditReloadedStateElement = document.createElement("input");
        this.auditReloadedStateElement.name = "audit-mode";
        this.auditReloadedStateElement.type = "radio";
        labelElement.appendChild(this.auditReloadedStateElement);
        labelElement.appendChild(document.createTextNode("Reload Page and Audit on Load"));
        this._buttonContainerElement.appendChild(labelElement);

        this._launchButton = document.createElement("button");
        this._launchButton.type = "button";
        this._launchButton.textContent = WebInspector.UIString("Run");
        this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false);
        this._buttonContainerElement.appendChild(this._launchButton);

        this._resourceProgressContainer = document.createElement("span");
        this._resourceProgressContainer.className = "resource-progress";
        var resourceProgressImage = document.createElement("img");
        this._resourceProgressContainer.appendChild(resourceProgressImage);
        this._resourceProgressTextElement = document.createElement("span");
        this._resourceProgressContainer.appendChild(this._resourceProgressTextElement);
        this._buttonContainerElement.appendChild(this._resourceProgressContainer);

        this._contentElement.appendChild(this._buttonContainerElement);

        this._selectAllClicked(this._selectAllCheckboxElement.checked);
        this.updateResourceTrackingState();
        this._updateButton();
    },

    _updateResourceProgress: function()
    {
        if (!this._resourceProgressContainer)
            return;

        if (!this._auditRunning) {
            this._resetResourceCount();
            this._resourceProgressContainer.addStyleClass("hidden");
        } else
            this._resourceProgressContainer.removeStyleClass("hidden");
        this._resourceProgressTextElement.textContent = WebInspector.UIString("Loading (%d of %d)", this.loadedResources, this.totalResources);
    },

    _updateButton: function()
    {
        this._launchButton.disabled = !this._currentCategoriesCount || this._auditRunning;
    }
}

WebInspector.AuditLauncherView.prototype.__proto__ = WebInspector.View.prototype;

/* AuditRules.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AuditRules.IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;

WebInspector.AuditRules.CacheableResponseCodes =
{
    200: true,
    203: true,
    206: true,
    300: true,
    301: true,
    410: true,

    304: true // Underlying resource is cacheable
}

WebInspector.AuditRules.getDomainToResourcesMap = function(resources, types, regexp, needFullResources)
{
    var domainToResourcesMap = {};
    for (var i = 0, size = resources.length; i < size; ++i) {
        var resource = resources[i];
        if (types && types.indexOf(resource.type) === -1)
            continue;
        var match = resource.url.match(regexp);
        if (!match)
            continue;
        var domain = match[2];
        var domainResources = domainToResourcesMap[domain];
        if (domainResources === undefined) {
          domainResources = [];
          domainToResourcesMap[domain] = domainResources;
        }
        domainResources.push(needFullResources ? resource : resource.url);
    }
    return domainToResourcesMap;
}

WebInspector.AuditRules.evaluateInTargetWindow = function(func, args, callback)
{
    InjectedScriptAccess.getDefault().evaluateOnSelf(func.toString(), args, callback);
}


WebInspector.AuditRules.GzipRule = function()
{
    WebInspector.AuditRule.call(this, "network-gzip", "Enable gzip compression");
}

WebInspector.AuditRules.GzipRule.prototype = {
    doRun: function(resources, result, callback)
    {
        var totalSavings = 0;
        var compressedSize = 0;
        var candidateSize = 0;
        var summary = result.addChild("", true);
        for (var i = 0, length = resources.length; i < length; ++i) {
            var resource = resources[i];
            if (this._shouldCompress(resource)) {
                var size = resource.resourceSize;
                candidateSize += size;
                if (this._isCompressed(resource)) {
                    compressedSize += size;
                    continue;
                }
                var savings = 2 * size / 3;
                totalSavings += savings;
                summary.addChild(String.sprintf("%s could save ~%s", WebInspector.AuditRuleResult.linkifyDisplayName(resource.url), Number.bytesToString(savings, WebInspector.UIString)));
                result.violationCount++;
            }
        }
        if (!totalSavings)
            return callback(null);
        summary.value = String.sprintf("Compressing the following resources with gzip could reduce their transfer size by about two thirds (~%s):", Number.bytesToString(totalSavings, WebInspector.UIString));
        callback(result);
    },

    _isCompressed: function(resource)
    {
        var encoding = resource.responseHeaders["Content-Encoding"];
        return encoding === "gzip" || encoding === "deflate";
    },

    _shouldCompress: function(resource)
    {
        return WebInspector.Resource.Type.isTextType(resource.type) && resource.domain && resource.resourceSize !== undefined && resource.resourceSize > 150;
    }
}

WebInspector.AuditRules.GzipRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type, resourceTypeName, allowedPerDomain)
{
    WebInspector.AuditRule.call(this, id, name);
    this._type = type;
    this._resourceTypeName = resourceTypeName;
    this._allowedPerDomain = allowedPerDomain;
}

WebInspector.AuditRules.CombineExternalResourcesRule.prototype = {
    doRun: function(resources, result, callback)
    {
        var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, [this._type], WebInspector.URLRegExp);
        var penalizedResourceCount = 0;
        // TODO: refactor according to the chosen i18n approach
        var summary = result.addChild("", true);
        for (var domain in domainToResourcesMap) {
            var domainResources = domainToResourcesMap[domain];
            var extraResourceCount = domainResources.length - this._allowedPerDomain;
            if (extraResourceCount <= 0)
                continue;
            penalizedResourceCount += extraResourceCount - 1;
            summary.addChild(String.sprintf("%d %s resources served from %s.", domainResources.length, this._resourceTypeName, WebInspector.AuditRuleResult.resourceDomain(domain)));
            result.violationCount += domainResources.length;
        }
        if (!penalizedResourceCount)
            return callback(null);

        summary.value = "There are multiple resources served from same domain. Consider combining them into as few files as possible.";
        callback(result);
    }
}

WebInspector.AuditRules.CombineExternalResourcesRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CombineJsResourcesRule = function(allowedPerDomain) {
    WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externaljs", "Combine external JavaScript", WebInspector.Resource.Type.Script, "JavaScript", allowedPerDomain);
}

WebInspector.AuditRules.CombineJsResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype;


WebInspector.AuditRules.CombineCssResourcesRule = function(allowedPerDomain) {
    WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externalcss", "Combine external CSS", WebInspector.Resource.Type.Stylesheet, "CSS", allowedPerDomain);
}

WebInspector.AuditRules.CombineCssResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype;


WebInspector.AuditRules.MinimizeDnsLookupsRule = function(hostCountThreshold) {
    WebInspector.AuditRule.call(this, "network-minimizelookups", "Minimize DNS lookups");
    this._hostCountThreshold = hostCountThreshold;
}

WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype = {
    doRun: function(resources, result, callback)
    {
        var summary = result.addChild("");
        var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, undefined, WebInspector.URLRegExp);
        for (var domain in domainToResourcesMap) {
            if (domainToResourcesMap[domain].length > 1)
                continue;
            var match = domain.match(WebInspector.URLRegExp);
            if (!match)
                continue;
            if (!match[2].search(WebInspector.AuditRules.IPAddressRegexp))
                continue; // an IP address
            summary.addSnippet(match[2]);
            result.violationCount++;
        }
        if (!summary.children || summary.children.length <= this._hostCountThreshold)
            return callback(null);

        summary.value = "The following domains only serve one resource each. If possible, avoid the extra DNS lookups by serving these resources from existing domains.";
        callback(result);
    }
}

WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.ParallelizeDownloadRule = function(optimalHostnameCount, minRequestThreshold, minBalanceThreshold)
{
    WebInspector.AuditRule.call(this, "network-parallelizehosts", "Parallelize downloads across hostnames");
    this._optimalHostnameCount = optimalHostnameCount;
    this._minRequestThreshold = minRequestThreshold;
    this._minBalanceThreshold = minBalanceThreshold;
}


WebInspector.AuditRules.ParallelizeDownloadRule.prototype = {
    doRun: function(resources, result, callback)
    {
        function hostSorter(a, b)
        {
            var aCount = domainToResourcesMap[a].length;
            var bCount = domainToResourcesMap[b].length;
            return (aCount < bCount) ? 1 : (aCount == bCount) ? 0 : -1;
        }

        var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(
            resources,
            [WebInspector.Resource.Type.Stylesheet, WebInspector.Resource.Type.Image],
            WebInspector.URLRegExp,
            true);

        var hosts = [];
        for (var url in domainToResourcesMap)
            hosts.push(url);

        if (!hosts.length)
            return callback(null); // no hosts (local file or something)

        hosts.sort(hostSorter);

        var optimalHostnameCount = this._optimalHostnameCount;
        if (hosts.length > optimalHostnameCount)
            hosts.splice(optimalHostnameCount);

        var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length;
        var resourceCountAboveThreshold = busiestHostResourceCount - this._minRequestThreshold;
        if (resourceCountAboveThreshold <= 0)
            return callback(null);

        var avgResourcesPerHost = 0;
        for (var i = 0, size = hosts.length; i < size; ++i)
            avgResourcesPerHost += domainToResourcesMap[hosts[i]].length;

        // Assume optimal parallelization.
        avgResourcesPerHost /= optimalHostnameCount;
        avgResourcesPerHost = Math.max(avgResourcesPerHost, 1);

        var pctAboveAvg = (resourceCountAboveThreshold / avgResourcesPerHost) - 1.0;
        var minBalanceThreshold = this._minBalanceThreshold;
        if (pctAboveAvg < minBalanceThreshold)
            return callback(null);

        var resourcesOnBusiestHost = domainToResourcesMap[hosts[0]];
        var entry = result.addChild(String.sprintf("This page makes %d parallelizable requests to %s. Increase download parallelization by distributing the following requests across multiple hostnames.", busiestHostResourceCount, hosts[0]), true);
        for (var i = 0; i < resourcesOnBusiestHost.length; ++i)
            entry.addURL(resourcesOnBusiestHost[i].url);

        result.violationCount = resourcesOnBusiestHost.length;
        callback(result);
    }
}

WebInspector.AuditRules.ParallelizeDownloadRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


// The reported CSS rule size is incorrect (parsed != original in WebKit),
// so use percentages instead, which gives a better approximation.
WebInspector.AuditRules.UnusedCssRule = function()
{
    WebInspector.AuditRule.call(this, "page-unusedcss", "Remove unused CSS rules");
}

WebInspector.AuditRules.UnusedCssRule.prototype = {
    doRun: function(resources, result, callback)
    {
        var self = this;
        function evalCallback(styleSheets) {
            if (!styleSheets.length)
                return callback(null);

            var pseudoSelectorRegexp = /:hover|:link|:active|:visited|:focus|:before|:after/;
            var selectors = [];
            var testedSelectors = {};
            for (var i = 0; i < styleSheets.length; ++i) {
                var styleSheet = styleSheets[i];
                for (var curRule = 0; curRule < styleSheet.cssRules.length; ++curRule) {
                    var rule = styleSheet.cssRules[curRule];
                    if (rule.selectorText.match(pseudoSelectorRegexp))
                        continue;
                    selectors.push(rule.selectorText);
                    testedSelectors[rule.selectorText] = 1;
                }
            }

            function selectorsCallback(callback, styleSheets, testedSelectors, foundSelectors)
            {
                var inlineBlockOrdinal = 0;
                var totalStylesheetSize = 0;
                var totalUnusedStylesheetSize = 0;
                var summary;

                for (var i = 0; i < styleSheets.length; ++i) {
                    var styleSheet = styleSheets[i];
                    var stylesheetSize = 0;
                    var unusedStylesheetSize = 0;
                    var unusedRules = [];
                    for (var curRule = 0; curRule < styleSheet.cssRules.length; ++curRule) {
                        var rule = styleSheet.cssRules[curRule];
                        var textLength = rule.cssText ? rule.cssText.length : 0;
                        stylesheetSize += textLength;
                        if (!testedSelectors[rule.selectorText] || foundSelectors[rule.selectorText])
                            continue;
                        unusedStylesheetSize += textLength;
                        unusedRules.push(rule.selectorText);
                    }
                    totalStylesheetSize += stylesheetSize;
                    totalUnusedStylesheetSize += unusedStylesheetSize;

                    if (!unusedRules.length)
                        continue;

                    var url = styleSheet.href ? WebInspector.AuditRuleResult.linkifyDisplayName(styleSheet.href) : String.sprintf("Inline block #%d", ++inlineBlockOrdinal);
                    var pctUnused = Math.round(100 * unusedStylesheetSize / stylesheetSize);
                    if (!summary)
                        summary = result.addChild("", true);
                    var entry = summary.addChild(String.sprintf("%s: %d%% (estimated) is not used by the current page.", url, pctUnused));

                    for (var j = 0; j < unusedRules.length; ++j)
                        entry.addSnippet(unusedRules[j]);

                    result.violationCount += unusedRules.length;
                }

                if (!totalUnusedStylesheetSize)
                    return callback(null);

                var totalUnusedPercent = Math.round(100 * totalUnusedStylesheetSize / totalStylesheetSize);
                summary.value = String.sprintf("%d%% of CSS (estimated) is not used by the current page.", totalUnusedPercent);

                callback(result);
            }

            function routine(selectorArray)
            {
                var result = {};
                for (var i = 0; i < selectorArray.length; ++i) {
                    try {
                        var nodes = document.querySelectorAll(selectorArray[i]);
                        if (nodes && nodes.length)
                            result[selectorArray[i]] = true;
                    } catch(e) {
                        // ignore and mark as unused
                    }
                }
                return result;
            }

            WebInspector.AuditRules.evaluateInTargetWindow(routine, [selectors], selectorsCallback.bind(null, callback, styleSheets, testedSelectors));
        }

        function routine()
        {
            var styleSheets = document.styleSheets;
            if (!styleSheets)
                return false;

            return routineResult;
        }

        InspectorBackend.getAllStyles(evalCallback);
    }
}

WebInspector.AuditRules.UnusedCssRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CacheControlRule = function(id, name)
{
    WebInspector.AuditRule.call(this, id, name);
}

WebInspector.AuditRules.CacheControlRule.MillisPerMonth = 1000 * 60 * 60 * 24 * 30;

WebInspector.AuditRules.CacheControlRule.prototype = {

    doRun: function(resources, result, callback)
    {
        var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResources(resources);
        if (cacheableAndNonCacheableResources[0].length)
            this.runChecks(cacheableAndNonCacheableResources[0], result);
        this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], result);

        callback(result);
    },

    handleNonCacheableResources: function()
    {
    },

    _cacheableAndNonCacheableResources: function(resources)
    {
        var processedResources = [[], []];
        for (var i = 0; i < resources.length; ++i) {
            var resource = resources[i];
            if (!this.isCacheableResource(resource))
                continue;
            if (this._isExplicitlyNonCacheable(resource))
                processedResources[1].push(resource);
            else
                processedResources[0].push(resource);
        }
        return processedResources;
    },

    execCheck: function(messageText, resourceCheckFunction, resources, result)
    {
        var resourceCount = resources.length;
        var urls = [];
        for (var i = 0; i < resourceCount; ++i) {
            if (resourceCheckFunction.call(this, resources[i]))
                urls.push(resources[i].url);
        }
        if (urls.length) {
            var entry = result.addChild(messageText, true);
            entry.addURLs(urls);
            result.violationCount += urls.length;
        }
    },

    freshnessLifetimeGreaterThan: function(resource, timeMs)
    {
        var dateHeader = this.responseHeader(resource, "Date");
        if (!dateHeader)
            return false;

        var dateHeaderMs = Date.parse(dateHeader);
        if (isNaN(dateHeaderMs))
            return false;

        var freshnessLifetimeMs;
        var maxAgeMatch = this.responseHeaderMatch(resource, "Cache-Control", "max-age=(\\d+)");

        if (maxAgeMatch)
            freshnessLifetimeMs = (maxAgeMatch[1]) ? 1000 * maxAgeMatch[1] : 0;
        else {
            var expiresHeader = this.responseHeader(resource, "Expires");
            if (expiresHeader) {
                var expDate = Date.parse(expiresHeader);
                if (!isNaN(expDate))
                    freshnessLifetimeMs = expDate - dateHeaderMs;
            }
        }

        return (isNaN(freshnessLifetimeMs)) ? false : freshnessLifetimeMs > timeMs;
    },

    responseHeader: function(resource, header)
    {
        return resource.responseHeaders[header];
    },

    hasResponseHeader: function(resource, header)
    {
        return resource.responseHeaders[header] !== undefined;
    },

    isCompressible: function(resource)
    {
        return WebInspector.Resource.Type.isTextType(resource.type);
    },

    isPubliclyCacheable: function(resource)
    {
        if (this._isExplicitlyNonCacheable(resource))
            return false;

        if (this.responseHeaderMatch(resource, "Cache-Control", "public"))
            return true;

        return resource.url.indexOf("?") == -1 && !this.responseHeaderMatch(resource, "Cache-Control", "private");
    },

    responseHeaderMatch: function(resource, header, regexp)
    {
        return resource.responseHeaders[header]
            ? resource.responseHeaders[header].match(new RegExp(regexp, "im"))
            : undefined;
    },

    hasExplicitExpiration: function(resource)
    {
        return this.hasResponseHeader(resource, "Date") &&
            (this.hasResponseHeader(resource, "Expires") || this.responseHeaderMatch(resource, "Cache-Control", "max-age"));
    },

    _isExplicitlyNonCacheable: function(resource)
    {
        var hasExplicitExp = this.hasExplicitExpiration(resource);
        return this.responseHeaderMatch(resource, "Cache-Control", "(no-cache|no-store|must-revalidate)") ||
            this.responseHeaderMatch(resource, "Pragma", "no-cache") ||
            (hasExplicitExp && !this.freshnessLifetimeGreaterThan(resource, 0)) ||
            (!hasExplicitExp && resource.url && resource.url.indexOf("?") >= 0) ||
            (!hasExplicitExp && !this.isCacheableResource(resource));
    },

    isCacheableResource: function(resource)
    {
        return resource.statusCode !== undefined && WebInspector.AuditRules.CacheableResponseCodes[resource.statusCode];
    }
}

WebInspector.AuditRules.CacheControlRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.BrowserCacheControlRule = function()
{
    WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", "Leverage browser caching");
}

WebInspector.AuditRules.BrowserCacheControlRule.prototype = {
    handleNonCacheableResources: function(resources, result)
    {
        if (resources.length) {
            var entry = result.addChild("The following resources are explicitly non-cacheable. Consider making them cacheable if possible:", true);
            result.violationCount += resources.length;
            for (var i = 0; i < resources.length; ++i)
                entry.addURL(resources[i].url);
        }
    },

    runChecks: function(resources, result, callback)
    {
        this.execCheck("The following resources are missing a cache expiration. Resources that do not specify an expiration may not be cached by browsers:",
            this._missingExpirationCheck, resources, result);
        this.execCheck("The following resources specify a \"Vary\" header that disables caching in most versions of Internet Explorer:",
            this._varyCheck, resources, result);
        this.execCheck("The following cacheable resources have a short freshness lifetime:",
            this._oneMonthExpirationCheck, resources, result);

        // Unable to implement the favicon check due to the WebKit limitations.
        this.execCheck("To further improve cache hit rate, specify an expiration one year in the future for the following cacheable resources:",
            this._oneYearExpirationCheck, resources, result);
    },

    _missingExpirationCheck: function(resource)
    {
        return this.isCacheableResource(resource) && !this.hasResponseHeader(resource, "Set-Cookie") && !this.hasExplicitExpiration(resource);
    },

    _varyCheck: function(resource)
    {
        var varyHeader = this.responseHeader(resource, "Vary");
        if (varyHeader) {
            varyHeader = varyHeader.replace(/User-Agent/gi, "");
            varyHeader = varyHeader.replace(/Accept-Encoding/gi, "");
            varyHeader = varyHeader.replace(/[, ]*/g, "");
        }
        return varyHeader && varyHeader.length && this.isCacheableResource(resource) && this.freshnessLifetimeGreaterThan(resource, 0);
    },

    _oneMonthExpirationCheck: function(resource)
    {
        return this.isCacheableResource(resource) &&
            !this.hasResponseHeader(resource, "Set-Cookie") &&
            !this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth) &&
            this.freshnessLifetimeGreaterThan(resource, 0);
    },

    _oneYearExpirationCheck: function(resource)
    {
        return this.isCacheableResource(resource) &&
            !this.hasResponseHeader(resource, "Set-Cookie") &&
            !this.freshnessLifetimeGreaterThan(resource, 11 * WebInspector.AuditRules.CacheControlRule.MillisPerMonth) &&
            this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth);
    }
}

WebInspector.AuditRules.BrowserCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype;


WebInspector.AuditRules.ProxyCacheControlRule = function() {
    WebInspector.AuditRules.CacheControlRule.call(this, "http-proxycache", "Leverage proxy caching");
}

WebInspector.AuditRules.ProxyCacheControlRule.prototype = {
    runChecks: function(resources, result, callback)
    {
        this.execCheck("Resources with a \"?\" in the URL are not cached by most proxy caching servers:",
            this._questionMarkCheck, resources, result);
        this.execCheck("Consider adding a \"Cache-Control: public\" header to the following resources:",
            this._publicCachingCheck, resources, result);
        this.execCheck("The following publicly cacheable resources contain a Set-Cookie header. This security vulnerability can cause cookies to be shared by multiple users.",
            this._setCookieCacheableCheck, resources, result);
    },

    _questionMarkCheck: function(resource)
    {
        return resource.url.indexOf("?") >= 0 && !this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource);
    },

    _publicCachingCheck: function(resource)
    {
        return this.isCacheableResource(resource) &&
            !this.isCompressible(resource) &&
            !this.responseHeaderMatch(resource, "Cache-Control", "public") &&
            !this.hasResponseHeader(resource, "Set-Cookie");
    },

    _setCookieCacheableCheck: function(resource)
    {
        return this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource);
    }
}

WebInspector.AuditRules.ProxyCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype;


WebInspector.AuditRules.ImageDimensionsRule = function()
{
    WebInspector.AuditRule.call(this, "page-imagedims", "Specify image dimensions");
}

WebInspector.AuditRules.ImageDimensionsRule.prototype = {
    doRun: function(resources, result, callback)
    {
        function doneCallback(context)
        {
            var map = context.urlToNoDimensionCount;
            for (var url in map) {
                var entry = entry || result.addChild("A width and height should be specified for all images in order to speed up page display. The following image(s) are missing a width and/or height:", true);
                var value = WebInspector.AuditRuleResult.linkifyDisplayName(url);
                if (map[url] > 1)
                    value += String.sprintf(" (%d uses)", map[url]);
                entry.addChild(value);
                result.violationCount++;
            }
            callback(entry ? result : null);
        }

        function imageStylesReady(imageId, context, styles)
        {
            --context.imagesLeft;

            const node = WebInspector.domAgent.nodeForId(imageId);
            var src = node.getAttribute("src");
            if (!WebInspector.URLRegExp.test(src)) {
                for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
                    if (frameOwnerCandidate.documentURL) {
                        var completeSrc = WebInspector.completeURL(frameOwnerCandidate.documentURL, src);
                        break;
                    }
                }
            }
            if (completeSrc)
                src = completeSrc;

            const computedStyle = new WebInspector.CSSStyleDeclaration(styles.computedStyle);
            if (computedStyle.getPropertyValue("position") === "absolute") {
                if (!context.imagesLeft)
                    doneCallback(context);
                return;
            }

            var widthFound = "width" in styles.styleAttributes;
            var heightFound = "height" in styles.styleAttributes;

            for (var i = styles.matchedCSSRules.length - 1; i >= 0 && !(widthFound && heightFound); --i) {
                var style = WebInspector.CSSStyleDeclaration.parseRule(styles.matchedCSSRules[i]).style;
                if (style.getPropertyValue("width") !== "")
                    widthFound = true;
                if (style.getPropertyValue("height") !== "")
                    heightFound = true;
            }
            
            if (!widthFound || !heightFound) {
                if (src in context.urlToNoDimensionCount)
                    ++context.urlToNoDimensionCount[src];
                else
                    context.urlToNoDimensionCount[src] = 1;
            }

            if (!context.imagesLeft)
                doneCallback(context);
        }

        function receivedImages(imageIds)
        {
            if (!imageIds || !imageIds.length)
                return callback(null);
            var context = {imagesLeft: imageIds.length, urlToNoDimensionCount: {}};
            for (var i = imageIds.length - 1; i >= 0; --i)
                InspectorBackend.getStyles(imageIds[i], true, imageStylesReady.bind(this, imageIds[i], context));
        }

        function pushImageNodes()
        {
            const nodeIds = [];
            var nodes = document.getElementsByTagName("img");
            for (var i = 0; i < nodes.length; ++i) {
                if (!nodes[i].src)
                    continue;
                var nodeId = this.getNodeId(nodes[i]);
                nodeIds.push(nodeId);
            }
            return nodeIds;
        }

        WebInspector.AuditRules.evaluateInTargetWindow(pushImageNodes, null, receivedImages);
    }
}

WebInspector.AuditRules.ImageDimensionsRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CssInHeadRule = function()
{
    WebInspector.AuditRule.call(this, "page-cssinhead", "Put CSS in the document head");
}

WebInspector.AuditRules.CssInHeadRule.prototype = {
    doRun: function(resources, result, callback)
    {
        function evalCallback(evalResult)
        {
            if (!evalResult)
                return callback(null);

            var summary = result.addChild("");

            var outputMessages = [];
            for (var url in evalResult) {
                var urlViolations = evalResult[url];
                if (urlViolations[0]) {
                    result.addChild(String.sprintf("%s style block(s) in the %s body should be moved to the document head.", urlViolations[0], WebInspector.AuditRuleResult.linkifyDisplayName(url)));
                    result.violationCount += urlViolations[0];
                }
                for (var i = 0; i < urlViolations[1].length; ++i)
                    result.addChild(String.sprintf("Link node %s should be moved to the document head in %s", WebInspector.AuditRuleResult.linkifyDisplayName(urlViolations[1][i]), WebInspector.AuditRuleResult.linkifyDisplayName(url)));
                result.violationCount += urlViolations[1].length;
            }
            summary.value = String.sprintf("CSS in the document body adversely impacts rendering performance.");
            callback(result);
        }

        function routine()
        {
            function allViews() {
                var views = [document.defaultView];
                var curView = 0;
                while (curView < views.length) {
                    var view = views[curView];
                    var frames = view.frames;
                    for (var i = 0; i < frames.length; ++i) {
                        if (frames[i] !== view)
                            views.push(frames[i]);
                    }
                    ++curView;
                }
                return views;
            }

            var views = allViews();
            var urlToViolationsArray = {};
            var found = false;
            for (var i = 0; i < views.length; ++i) {
                var view = views[i];
                if (!view.document)
                    continue;

                var inlineStyles = view.document.querySelectorAll("body style");
                var inlineStylesheets = view.document.querySelectorAll("body link[rel~='stylesheet'][href]");
                if (!inlineStyles.length && !inlineStylesheets.length)
                    continue;

                found = true;
                var inlineStylesheetHrefs = [];
                for (var j = 0; j < inlineStylesheets.length; ++j)
                    inlineStylesheetHrefs.push(inlineStylesheets[j].href);
                urlToViolationsArray[view.location.href] = [inlineStyles.length, inlineStylesheetHrefs];
            }
            return found ? urlToViolationsArray : null;
        }

        WebInspector.AuditRules.evaluateInTargetWindow(routine, null, evalCallback);
    }
}

WebInspector.AuditRules.CssInHeadRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.StylesScriptsOrderRule = function()
{
    WebInspector.AuditRule.call(this, "page-stylescriptorder", "Optimize the order of styles and scripts");
}

WebInspector.AuditRules.StylesScriptsOrderRule.prototype = {
    doRun: function(resources, result, callback)
    {
        function evalCallback(resultValue)
        {
            if (!resultValue)
                return callback(null);

            var lateCssUrls = resultValue[0];
            var cssBeforeInlineCount = resultValue[1];

            var entry = result.addChild("The following external CSS files were included after an external JavaScript file in the document head. To ensure CSS files are downloaded in parallel, always include external CSS before external JavaScript.", true);
            entry.addURLs(lateCssUrls);
            result.violationCount += lateCssUrls.length;

            if (cssBeforeInlineCount) {
                result.addChild(String.sprintf(" %d inline script block%s found in the head between an external CSS file and another resource. To allow parallel downloading, move the inline script before the external CSS file, or after the next resource.", cssBeforeInlineCount, cssBeforeInlineCount > 1 ? "s were" : " was"));
                result.violationCount += cssBeforeInlineCount;
            }
            callback(result);
        }

        function routine()
        {
            var lateStyles = document.querySelectorAll("head script[src] ~ link[rel~='stylesheet'][href]");
            var cssBeforeInlineCount = document.querySelectorAll("head link[rel~='stylesheet'][href] ~ script:not([src])").length;
            if (!lateStyles.length && !cssBeforeInlineCount)
                return null;

            var lateStyleUrls = [];
            for (var i = 0; i < lateStyles.length; ++i)
                lateStyleUrls.push(lateStyles[i].href);
            return [ lateStyleUrls, cssBeforeInlineCount ];
        }

        WebInspector.AuditRules.evaluateInTargetWindow(routine, null, evalCallback.bind(this));
    }
}

WebInspector.AuditRules.StylesScriptsOrderRule.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CookieRuleBase = function(id, name)
{
    WebInspector.AuditRule.call(this, id, name);
}

WebInspector.AuditRules.CookieRuleBase.prototype = {
    doRun: function(resources, result, callback)
    {
        var self = this;
        function resultCallback(receivedCookies, isAdvanced) {
            self.processCookies(isAdvanced ? receivedCookies : [], resources, result);
            callback(result);
        }
        WebInspector.Cookies.getCookiesAsync(resultCallback);
    },

    mapResourceCookies: function(resourcesByDomain, allCookies, callback)
    {
        for (var i = 0; i < allCookies.length; ++i) {
            for (var resourceDomain in resourcesByDomain) {
                if (WebInspector.Cookies.cookieDomainMatchesResourceDomain(allCookies[i].domain, resourceDomain))
                    this._callbackForResourceCookiePairs(resourcesByDomain[resourceDomain], allCookies[i], callback);
            }
        }
    },

    _callbackForResourceCookiePairs: function(resources, cookie, callback)
    {
        if (!resources)
            return;
        for (var i = 0; i < resources.length; ++i) {
            if (WebInspector.Cookies.cookieMatchesResourceURL(cookie, resources[i].url))
                callback(resources[i], cookie);
        }
    }
}

WebInspector.AuditRules.CookieRuleBase.prototype.__proto__ = WebInspector.AuditRule.prototype;


WebInspector.AuditRules.CookieSizeRule = function(avgBytesThreshold)
{
    WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", "Minimize cookie size");
    this._avgBytesThreshold = avgBytesThreshold;
    this._maxBytesThreshold = 1000;
}

WebInspector.AuditRules.CookieSizeRule.prototype = {
    _average: function(cookieArray)
    {
        var total = 0;
        for (var i = 0; i < cookieArray.length; ++i)
            total += cookieArray[i].size;
        return cookieArray.length ? Math.round(total / cookieArray.length) : 0;
    },

    _max: function(cookieArray)
    {
        var result = 0;
        for (var i = 0; i < cookieArray.length; ++i)
            result = Math.max(cookieArray[i].size, result);
        return result;
    },

    processCookies: function(allCookies, resources, result)
    {
        function maxSizeSorter(a, b)
        {
            return b.maxCookieSize - a.maxCookieSize;
        }

        function avgSizeSorter(a, b)
        {
            return b.avgCookieSize - a.avgCookieSize;
        }

        var cookiesPerResourceDomain = {};

        function collectorCallback(resource, cookie)
        {
            var cookies = cookiesPerResourceDomain[resource.domain];
            if (!cookies) {
                cookies = [];
                cookiesPerResourceDomain[resource.domain] = cookies;
            }
            cookies.push(cookie);
        }

        if (!allCookies.length)
            return;

        var sortedCookieSizes = [];

        var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources,
                null,
                WebInspector.URLRegExp,
                true);
        var matchingResourceData = {};
        this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallback.bind(this));

        for (var resourceDomain in cookiesPerResourceDomain) {
            var cookies = cookiesPerResourceDomain[resourceDomain];
            sortedCookieSizes.push({
                domain: resourceDomain,
                avgCookieSize: this._average(cookies),
                maxCookieSize: this._max(cookies)
            });
        }
        var avgAllCookiesSize = this._average(allCookies);

        var hugeCookieDomains = [];
        sortedCookieSizes.sort(maxSizeSorter);

        for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) {
            var maxCookieSize = sortedCookieSizes[i].maxCookieSize;
            if (maxCookieSize > this._maxBytesThreshold)
                hugeCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(sortedCookieSizes[i].domain) + ": " + Number.bytesToString(maxCookieSize, WebInspector.UIString));
        }

        var bigAvgCookieDomains = [];
        sortedCookieSizes.sort(avgSizeSorter);
        for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) {
            var domain = sortedCookieSizes[i].domain;
            var avgCookieSize = sortedCookieSizes[i].avgCookieSize;
            if (avgCookieSize > this._avgBytesThreshold && avgCookieSize < this._maxBytesThreshold)
                bigAvgCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(domain) + ": " + Number.bytesToString(avgCookieSize, WebInspector.UIString));
        }
        result.addChild(String.sprintf("The average cookie size for all requests on this page is %s", Number.bytesToString(avgAllCookiesSize, WebInspector.UIString)));

        var message;
        if (hugeCookieDomains.length) {
            var entry = result.addChild("The following domains have a cookie size in excess of 1KB. This is harmful because requests with cookies larger than 1KB typically cannot fit into a single network packet.", true);
            entry.addURLs(hugeCookieDomains);
            result.violationCount += hugeCookieDomains.length;
        }

        if (bigAvgCookieDomains.length) {
            var entry = result.addChild(String.sprintf("The following domains have an average cookie size in excess of %d bytes. Reducing the size of cookies for these domains can reduce the time it takes to send requests.", this._avgBytesThreshold), true);
            entry.addURLs(bigAvgCookieDomains);
            result.violationCount += bigAvgCookieDomains.length;
        }
    }
}

WebInspector.AuditRules.CookieSizeRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype;


WebInspector.AuditRules.StaticCookielessRule = function(minResources)
{
    WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", "Serve static content from a cookieless domain");
    this._minResources = minResources;
}

WebInspector.AuditRules.StaticCookielessRule.prototype = {
    processCookies: function(allCookies, resources, result)
    {
        var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources,
                [WebInspector.Resource.Type.Stylesheet,
                 WebInspector.Resource.Type.Image],
                WebInspector.URLRegExp,
                true);
        var totalStaticResources = 0;
        for (var domain in domainToResourcesMap)
            totalStaticResources += domainToResourcesMap[domain].length;
        if (totalStaticResources < this._minResources)
            return;
        var matchingResourceData = {};
        this.mapResourceCookies(domainToResourcesMap, allCookies, this._collectorCallback.bind(this, matchingResourceData));

        var badUrls = [];
        var cookieBytes = 0;
        for (var url in matchingResourceData) {
            badUrls.push(url);
            cookieBytes += matchingResourceData[url]
        }
        if (badUrls.length < this._minResources)
            return;

        var entry = result.addChild(String.sprintf("%s of cookies were sent with the following static resources. Serve these static resources from a domain that does not set cookies:", Number.bytesToString(cookieBytes, WebInspector.UIString)), true);
        entry.addURLs(badUrls);
        result.violationCount = badUrls.length;
    },

    _collectorCallback: function(matchingResourceData, resource, cookie)
    {
        matchingResourceData[resource.url] = (matchingResourceData[resource.url] || 0) + cookie.size;
    }
}

WebInspector.AuditRules.StaticCookielessRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype;

/* AuditCategories.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.AuditCategories.PagePerformance = function() {
    WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.PagePerformance.AuditCategoryName);
}

WebInspector.AuditCategories.PagePerformance.AuditCategoryName = "Web Page Performance";

WebInspector.AuditCategories.PagePerformance.prototype = {
    initialize: function()
    {
        this.addRule(new WebInspector.AuditRules.UnusedCssRule(), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.CssInHeadRule(), WebInspector.AuditRule.Severity.Severe);
        this.addRule(new WebInspector.AuditRules.StylesScriptsOrderRule(), WebInspector.AuditRule.Severity.Severe);
    }
}

WebInspector.AuditCategories.PagePerformance.prototype.__proto__ = WebInspector.AuditCategory.prototype;

WebInspector.AuditCategories.NetworkUtilization = function() {
    WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName);
}

WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName = "Network Utilization";

WebInspector.AuditCategories.NetworkUtilization.prototype = {
    initialize: function()
    {
        this.addRule(new WebInspector.AuditRules.GzipRule(), WebInspector.AuditRule.Severity.Severe);
        this.addRule(new WebInspector.AuditRules.ImageDimensionsRule(), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.CookieSizeRule(400), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.StaticCookielessRule(5), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.CombineJsResourcesRule(2), WebInspector.AuditRule.Severity.Severe);
        this.addRule(new WebInspector.AuditRules.CombineCssResourcesRule(2), WebInspector.AuditRule.Severity.Severe);
        this.addRule(new WebInspector.AuditRules.MinimizeDnsLookupsRule(4), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.ParallelizeDownloadRule(4, 10, 0.5), WebInspector.AuditRule.Severity.Warning);
        this.addRule(new WebInspector.AuditRules.BrowserCacheControlRule(), WebInspector.AuditRule.Severity.Severe);
        this.addRule(new WebInspector.AuditRules.ProxyCacheControlRule(), WebInspector.AuditRule.Severity.Warning);
    }
}

WebInspector.AuditCategories.NetworkUtilization.prototype.__proto__ = WebInspector.AuditCategory.prototype;

/* AuditFormatters.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.applyFormatters = function(value)
{
    var formatter;
    var type = typeof value;
    var args;

    switch (type) {
        case "string":
        case "boolean":
        case "number":
            formatter = WebInspector.AuditFormatters.text;
            args = [ value.toString() ];
            break;

        case "object":
            if (value instanceof Array) {
                formatter = WebInspector.AuditFormatters.concat;
                args = value;
            } else if (value.type && value.arguments) {
                formatter = WebInspector.AuditFormatters[value.type];
                args = value.arguments;
            }
    }
    if (!formatter)
        throw "Invalid value or formatter: " + type + JSON.stringify(value);

    return formatter.apply(null, args);
}

WebInspector.AuditFormatters = {
    text: function(text)
    {
        return document.createTextNode(text);
    },

    snippet: function(snippetText)
    {
        var div = document.createElement("div");
        div.innerText = snippetText;
        div.className = "source-code";
        return div;
    },

    concat: function()
    {
        var parent = document.createElement("span");
        for (var arg = 0; arg < arguments.length; ++arg)
            parent.appendChild(WebInspector.applyFormatters(arguments[arg]));
        return parent;
    },

    url: function(url, displayText, allowExternalNavigation)
    {
        var a = document.createElement("a");
        a.href = url;
        a.title = url;
        a.textContent = displayText || url;
        if (allowExternalNavigation)
            a.target = "_blank";
        return a;
    }
};

/* ResourceView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) IBM Corp. 2009  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ResourceView = function(resource)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("resource-view");

    this.resource = resource;

    this.tabbedPane = new WebInspector.TabbedPane(this.element);

    this.headersElement = document.createElement("div");
    this.headersElement.className = "resource-view-headers";
    this.tabbedPane.appendTab("headers", WebInspector.UIString("Headers"), this.headersElement, this._selectHeadersTab.bind(this, true));

    if (this.hasContentTab()) {
        this.contentElement = document.createElement("div");
        this.contentElement.className = "resource-view-content";
        this.tabbedPane.appendTab("content", WebInspector.UIString("Content"), this.contentElement, this.selectContentTab.bind(this, true));
    }

    this.headersListElement = document.createElement("ol");
    this.headersListElement.className = "outline-disclosure";
    this.headersElement.appendChild(this.headersListElement);

    this.headersTreeOutline = new TreeOutline(this.headersListElement);
    this.headersTreeOutline.expandTreeElementsWhenArrowing = true;

    this.urlTreeElement = new TreeElement("", null, false);
    this.urlTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.urlTreeElement);

    this.requestMethodTreeElement = new TreeElement("", null, false);
    this.requestMethodTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.requestMethodTreeElement);

    this.statusCodeTreeElement = new TreeElement("", null, false);
    this.statusCodeTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.statusCodeTreeElement);
     
    this.requestHeadersTreeElement = new TreeElement("", null, true);
    this.requestHeadersTreeElement.expanded = true;
    this.requestHeadersTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.requestHeadersTreeElement);

    this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats");
    this._decodeRequestParameters = true;

    this.queryStringTreeElement = new TreeElement("", null, true);
    this.queryStringTreeElement.expanded = true;
    this.queryStringTreeElement.selectable = false;
    this.queryStringTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.queryStringTreeElement);

    this.formDataTreeElement = new TreeElement("", null, true);
    this.formDataTreeElement.expanded = true;
    this.formDataTreeElement.selectable = false;
    this.formDataTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.formDataTreeElement);

    this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
    this.requestPayloadTreeElement.expanded = true;
    this.requestPayloadTreeElement.selectable = false;
    this.requestPayloadTreeElement.hidden = true;
    this.headersTreeOutline.appendChild(this.requestPayloadTreeElement);

    this.responseHeadersTreeElement = new TreeElement("", null, true);
    this.responseHeadersTreeElement.expanded = true;
    this.responseHeadersTreeElement.selectable = false;
    this.headersTreeOutline.appendChild(this.responseHeadersTreeElement);

    this.headersVisible = true;

    resource.addEventListener("url changed", this._refreshURL, this);
    resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this);
    resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this);
    resource.addEventListener("finished", this._refreshHTTPInformation, this);

    this._refreshURL();
    this._refreshQueryString();
    this._refreshRequestHeaders();
    this._refreshResponseHeaders();
    this._refreshHTTPInformation();
    this._selectTab();
}

WebInspector.ResourceView.prototype = {
    attach: function()
    {
        if (!this.element.parentNode) {
            var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views"));
            if (parentElement)
                parentElement.appendChild(this.element);
        }
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this._selectTab();
    },

    set headersVisible(x)
    {
        if (x === this._headersVisible)
            return;
        this._headersVisible = x;
        if (x)
            this.element.addStyleClass("headers-visible"); 
        else
            this.element.removeStyleClass("headers-visible"); 
        this._selectTab();
    },

    _selectTab: function()
    {
        if (this._headersVisible) {
            if (!this.hasContentTab() || WebInspector.applicationSettings.resourceViewTab === "headers")
                this._selectHeadersTab();
            else
                this.selectContentTab();
        } else
            this._innerSelectContentTab();
    },

    _selectHeadersTab: function(updatePrefs)
    {
        if (updatePrefs)
            WebInspector.applicationSettings.resourceViewTab = "headers";
        this.tabbedPane.selectTabById("headers");
    },

    selectContentTab: function(updatePrefs)
    {
        if (updatePrefs)
            WebInspector.applicationSettings.resourceViewTab = "content";
        this._innerSelectContentTab();
    },

    hasContentTab: function()
    {
        // Derived classes should override this method and define this.contentTabSelected for content rendering.
        return false;
    },

    _innerSelectContentTab: function()
    {
        this.tabbedPane.selectTabById("content");
        if ("resize" in this)
            this.resize();
        if (this.hasContentTab())
            this.contentTabSelected();
    },

    _refreshURL: function()
    {
        this.urlTreeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request URL") + ":</div>" +
            "<div class=\"header-value source-code\">" + this.resource.url.escapeHTML() + "</div>";
    },

    _refreshQueryString: function()
    {
        var queryParameters = this.resource.queryParameters;
        this.queryStringTreeElement.hidden = !queryParameters;
        if (queryParameters)
            this._refreshParms(WebInspector.UIString("Query String Parameters"), queryParameters, this.queryStringTreeElement);
    },

    _refreshFormData: function()
    {
        this.formDataTreeElement.hidden = true;
        this.requestPayloadTreeElement.hidden = true;

        var formData = this.resource.requestFormData;
        if (!formData)
            return;

        var formParameters = this.resource.formParameters;
        if (formParameters) {
            this.formDataTreeElement.hidden = false;
            this._refreshParms(WebInspector.UIString("Form Data"), formParameters, this.formDataTreeElement);
        } else {
            this.requestPayloadTreeElement.hidden = false;
            this._refreshRequestPayload(formData);
        }
    },

    _refreshRequestPayload: function(formData)
    {
        this.requestPayloadTreeElement.removeChildren();

        var title = "<div class=\"raw-form-data header-value source-code\">" + formData.escapeHTML() + "</div>";
        var parmTreeElement = new TreeElement(title, null, false);
        parmTreeElement.selectable = false;
        this.requestPayloadTreeElement.appendChild(parmTreeElement);
    },

    _refreshParms: function(title, parms, parmsTreeElement)
    {
        parmsTreeElement.removeChildren();

        parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>";

        for (var i = 0; i < parms.length; ++i) {
            var name = parms[i].name;
            var value = parms[i].value;

            var errorDecoding = false;
            if (this._decodeRequestParameters) {
                if (value.indexOf("%") >= 0) {
                    try {
                        value = decodeURIComponent(value);
                    } catch(e) {
                        errorDecoding = true;
                    }
                }
                    
                value = value.replace(/\+/g, " ");
            }

            valueEscaped = value.escapeHTML();
            if (errorDecoding)
                valueEscaped += " <span class=\"error-message\">" + WebInspector.UIString("(unable to decode value)").escapeHTML() + "</span>";

            var title = "<div class=\"header-name\">" + name.escapeHTML() + ":</div>";
            title += "<div class=\"header-value source-code\">" + valueEscaped + "</div>";

            var parmTreeElement = new TreeElement(title, null, false);
            parmTreeElement.selectable = false;
            parmTreeElement.tooltip = this._decodeHover;
            parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this);
            parmsTreeElement.appendChild(parmTreeElement);
        }
    },

    _toggleURLdecoding: function(event)
    {
        this._decodeRequestParameters = !this._decodeRequestParameters;
        this._refreshQueryString();
        this._refreshFormData();
    },

    _getHeaderValue: function(headers, key)
    {
        var lowerKey = key.toLowerCase();
        for (var testKey in headers) {
            if (testKey.toLowerCase() === lowerKey)
                return headers[testKey];
        }
    },

    _refreshRequestHeaders: function()
    {
        var additionalRow = null;
        if (typeof this.resource.webSocketRequestKey3 !== "undefined")
            additionalRow = {header: "(Key3)", value: this.resource.webSocketRequestKey3};
        this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, additionalRow, this.requestHeadersTreeElement);
        this._refreshFormData();
    },

    _refreshResponseHeaders: function()
    {
        var additionalRow = null;
        if (typeof this.resource.webSocketChallengeResponse !== "undefined")
            additionalRow = {header: "(Challenge Response)", value: this.resource.webSocketChallengeResponse};
        this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, additionalRow, this.responseHeadersTreeElement);
    },

    _refreshHTTPInformation: function()
    {
        var requestMethodElement = this.requestMethodTreeElement;
        requestMethodElement.hidden = !this.resource.statusCode;
        var statusCodeElement = this.statusCodeTreeElement;
        statusCodeElement.hidden = !this.resource.statusCode;
        var statusCodeImage = "";

        if (this.resource.statusCode) {
            var statusImageSource = "";
            if (this.resource.statusCode < 300)
                statusImageSource = "Images/successGreenDot.png";
            else if (this.resource.statusCode < 400)
                statusImageSource = "Images/warningOrangeDot.png";
            else
                statusImageSource = "Images/errorRedDot.png";

            var statusTextEscaped = this.resource.statusCode + " " + this.resource.statusText.escapeHTML();
            statusCodeImage = "<img class=\"resource-status-image\" src=\"" + statusImageSource + "\" title=\"" + statusTextEscaped + "\">";
    
            requestMethodElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request Method") + ":</div>" +
                "<div class=\"header-value source-code\">" + this.resource.requestMethod + "</div>";

            statusCodeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Status Code") + ":</div>" +
                statusCodeImage + "<div class=\"header-value source-code\">" + statusTextEscaped + "</div>";
        }
    },
    
    _refreshHeaders: function(title, headers, additionalRow, headersTreeElement)
    {
        headersTreeElement.removeChildren();

        var length = headers.length;
        headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>";
        headersTreeElement.hidden = !length;

        var length = headers.length;
        for (var i = 0; i < length; ++i) {
            var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>";
            title += "<div class=\"header-value source-code\">" + headers[i].value.escapeHTML() + "</div>"

            var headerTreeElement = new TreeElement(title, null, false);
            headerTreeElement.selectable = false;
            headersTreeElement.appendChild(headerTreeElement);
        }

        if (additionalRow) {
            var title = "<div class=\"header-name\">" + additionalRow.header.escapeHTML() + ":</div>";
            title += "<div class=\"header-value source-code\">" + additionalRow.value.escapeHTML() + "</div>"

            var headerTreeElement = new TreeElement(title, null, false);
            headerTreeElement.selectable = false;
            headersTreeElement.appendChild(headerTreeElement);
        }
    }
}

WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype;

/* SourceFrame.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, editDelegate, continueToHereDelegate)
{
    this._parentElement = parentElement;

    this._textModel = new WebInspector.TextEditorModel();
    this._textModel.replaceTabsWithSpaces = true;

    this._messages = [];
    this._rowMessages = {};
    this._messageBubbles = {};
    this.breakpoints = [];

    this._loaded = false;

    this._continueToHereDelegate = continueToHereDelegate;
    this._addBreakpointDelegate = addBreakpointDelegate;
    this._editDelegate = editDelegate;
    this._popoverObjectGroup = "popover";
}

WebInspector.SourceFrame.prototype = {

    set visible(visible)
    {
        this._visible = visible;
        this._createViewerIfNeeded();

        if (visible) {
            if (this._textViewer && this._scrollTop)
                this._textViewer.element.scrollTop = this._scrollTop;
            if (this._textViewer && this._scrollLeft)
                this._textViewer.element.scrollLeft = this._scrollLeft;
        } else {
            this._hidePopup();
            if (this._textViewer) {
                this._scrollTop = this._textViewer.element.scrollTop;
                this._scrollLeft = this._textViewer.element.scrollLeft;
                this._textViewer.freeCachedElements();
            }
        }
    },

    get executionLine()
    {
        return this._executionLine;
    },

    set executionLine(x)
    {
        if (this._executionLine === x)
            return;

        var previousLine = this._executionLine;
        this._executionLine = x;

        if (this._textViewer)
            this._updateExecutionLine(previousLine);
    },

    revealLine: function(lineNumber)
    {
        if (this._textViewer)
            this._textViewer.revealLine(lineNumber - 1, 0);
        else
            this._lineNumberToReveal = lineNumber;
    },

    addBreakpoint: function(breakpoint)
    {
        this.breakpoints.push(breakpoint);
        breakpoint.addEventListener("removed", this._breakpointRemoved, this);
        if (this._textViewer)
            this._addBreakpointToSource(breakpoint);
    },

    _breakpointRemoved: function(event)
    {
        var breakpoint = event.target;

        this.breakpoints.remove(breakpoint);
        if (this._textViewer)
            this._removeBreakpointFromSource(breakpoint);
    },

    addMessage: function(msg)
    {
        // Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
        if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
            return;
        this._messages.push(msg)
        if (this._textViewer)
            this._addMessageToSource(msg);
    },

    clearMessages: function()
    {
        for (var line in this._messageBubbles) {
            var bubble = this._messageBubbles[line];
            bubble.parentNode.removeChild(bubble);
        }

        this._messages = [];
        this._rowMessages = {};
        this._messageBubbles = {};
        if (this._textViewer)
            this._textViewer.resize();
    },

    sizeToFitContentHeight: function()
    {
        if (this._textViewer)
            this._textViewer.revalidateDecorationsAndPaint();
    },

    setContent: function(mimeType, content, url)
    {
        this._loaded = true;
        this._textModel.setText(null, content);
        this._mimeType = mimeType;
        this._url = url;
        this._createViewerIfNeeded();
    },

    updateContent: function(content)
    {
        this._textModel.setText(null, content);
    },

    get textModel()
    {
        return this._textModel;
    },

    highlightLine: function(line)
    {
        if (this._textViewer)
            this._textViewer.highlightLine(line - 1);
        else
            this._lineToHighlight = line;
    },

    clearLineHighlight: function()
    {
        if (this._textViewer)
            this._textViewer.clearLineHighlight();
        else
            delete this._lineToHighlight;
    },

    _createViewerIfNeeded: function()
    {
        if (!this._visible || !this._loaded || this._textViewer)
            return;

        this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url);
        var element = this._textViewer.element;
        element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
        element.addEventListener("mousedown", this._mouseDown.bind(this), true);
        element.addEventListener("mousemove", this._mouseMove.bind(this), true);
        element.addEventListener("scroll", this._scroll.bind(this), true);
        this._parentElement.appendChild(element);

        this._needsProgramCounterImage = true;
        this._needsBreakpointImages = true;

        this._textViewer.beginUpdates();

        this._textViewer.mimeType = this._mimeType;
        this._addExistingMessagesToSource();
        this._addExistingBreakpointsToSource();
        this._updateExecutionLine();
        this._textViewer.resize();

        if (this._lineNumberToReveal) {
            this.revealLine(this._lineNumberToReveal);
            delete this._lineNumberToReveal;
        }

        if (this._pendingMarkRange) {
            var range = this._pendingMarkRange;
            this.markAndRevealRange(range);
            delete this._pendingMarkRange;
        }

        if (this._lineToHighlight) {
            this.highlightLine(this._lineToHighlight);
            delete this._lineToHighlight;
        }
        this._textViewer.endUpdates();
        if (this._editDelegate)
            this._textViewer.editCallback = this._editDelegate;
    },

    findSearchMatches: function(query)
    {
        var ranges = [];

        // First do case-insensitive search.
        var regexObject = createSearchRegex(query);
        this._collectRegexMatches(regexObject, ranges);

        // Then try regex search if user knows the / / hint.
        try {
            if (/^\/.*\/$/.test(query))
                this._collectRegexMatches(new RegExp(query.substring(1, query.length - 1)), ranges);
        } catch (e) {
            // Silent catch.
        }
        return ranges;
    },

    _collectRegexMatches: function(regexObject, ranges)
    {
        for (var i = 0; i < this._textModel.linesCount; ++i) {
            var line = this._textModel.line(i);
            var offset = 0;
            do {
                var match = regexObject.exec(line);
                if (match) {
                    ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length));
                    offset += match.index + 1;
                    line = line.substring(match.index + 1);
                }
            } while (match)
        }
        return ranges;
    },

    markAndRevealRange: function(range)
    {
        if (this._textViewer)
            this._textViewer.markAndRevealRange(range);
        else
            this._pendingMarkRange = range;
    },

    clearMarkedRange: function()
    {
        if (this._textViewer) {
            this._textViewer.markAndRevealRange(null);
        } else
            delete this._pendingMarkRange;
    },

    _incrementMessageRepeatCount: function(msg, repeatDelta)
    {
        if (!msg._resourceMessageLineElement)
            return;

        if (!msg._resourceMessageRepeatCountElement) {
            var repeatedElement = document.createElement("span");
            msg._resourceMessageLineElement.appendChild(repeatedElement);
            msg._resourceMessageRepeatCountElement = repeatedElement;
        }

        msg.repeatCount += repeatDelta;
        msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount);
    },

    _breakpointChanged: function(event)
    {
        var breakpoint = event.target;
        var lineNumber = breakpoint.line - 1;
        if (lineNumber >= this._textModel.linesCount)
            return;

        if (breakpoint.enabled)
            this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled");
        else
            this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled");

        if (breakpoint.condition)
            this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional");
        else
            this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional");
    },

    _updateExecutionLine: function(previousLine)
    {
        if (previousLine) {
            if (previousLine - 1 < this._textModel.linesCount)
                this._textViewer.removeDecoration(previousLine - 1, "webkit-execution-line");
        }

        if (!this._executionLine)
            return;

        if (this._executionLine < this._textModel.linesCount)
            this._textViewer.addDecoration(this._executionLine - 1, "webkit-execution-line");
    },

    _addExistingMessagesToSource: function()
    {
        var length = this._messages.length;
        for (var i = 0; i < length; ++i)
            this._addMessageToSource(this._messages[i]);
    },

    _addMessageToSource: function(msg)
    {
        if (msg.line >= this._textModel.linesCount)
            return;

        var messageBubbleElement = this._messageBubbles[msg.line];
        if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
            messageBubbleElement = document.createElement("div");
            messageBubbleElement.className = "webkit-html-message-bubble";
            this._messageBubbles[msg.line] = messageBubbleElement;
            this._textViewer.addDecoration(msg.line - 1, messageBubbleElement);
        }

        var rowMessages = this._rowMessages[msg.line];
        if (!rowMessages) {
            rowMessages = [];
            this._rowMessages[msg.line] = rowMessages;
        }

        for (var i = 0; i < rowMessages.length; ++i) {
            if (rowMessages[i].isEqual(msg, true)) {
                this._incrementMessageRepeatCount(rowMessages[i], msg.repeatDelta);
                return;
            }
        }

        rowMessages.push(msg);

        var imageURL;
        switch (msg.level) {
            case WebInspector.ConsoleMessage.MessageLevel.Error:
                messageBubbleElement.addStyleClass("webkit-html-error-message");
                imageURL = "Images/errorIcon.png";
                break;
            case WebInspector.ConsoleMessage.MessageLevel.Warning:
                messageBubbleElement.addStyleClass("webkit-html-warning-message");
                imageURL = "Images/warningIcon.png";
                break;
        }

        var messageLineElement = document.createElement("div");
        messageLineElement.className = "webkit-html-message-line";
        messageBubbleElement.appendChild(messageLineElement);

        // Create the image element in the Inspector's document so we can use relative image URLs.
        var image = document.createElement("img");
        image.src = imageURL;
        image.className = "webkit-html-message-icon";
        messageLineElement.appendChild(image);
        messageLineElement.appendChild(document.createTextNode(msg.message));

        msg._resourceMessageLineElement = messageLineElement;
    },

    _addExistingBreakpointsToSource: function()
    {
        for (var i = 0; i < this.breakpoints.length; ++i)
            this._addBreakpointToSource(this.breakpoints[i]);
    },

    _addBreakpointToSource: function(breakpoint)
    {
        breakpoint.addEventListener("enable-changed", this._breakpointChanged, this);
        breakpoint.addEventListener("condition-changed", this._breakpointChanged, this);

        var lineNumber = breakpoint.line - 1;
        if (lineNumber >= this._textModel.linesCount)
            return;

        this._textModel.setAttribute(lineNumber, "breakpoint", breakpoint);
        breakpoint.sourceText = this._textModel.line(breakpoint.line - 1);

        this._textViewer.beginUpdates();
        this._textViewer.addDecoration(lineNumber, "webkit-breakpoint");
        if (!breakpoint.enabled)
            this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled");
        if (breakpoint.condition)
            this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional");
        this._textViewer.endUpdates();
    },

    _removeBreakpointFromSource: function(breakpoint)
    {
        breakpoint.removeEventListener("enable-changed", null, this);
        breakpoint.removeEventListener("condition-changed", null, this);

        var lineNumber = breakpoint.line - 1;
        this._textViewer.beginUpdates();
        this._textModel.removeAttribute(lineNumber, "breakpoint");
        this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint");
        this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled");
        this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional");
        this._textViewer.endUpdates();
    },

    _contextMenu: function(event)
    {
        var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
        if (!target)
            return;
        var row = target.parentElement;

        if (!WebInspector.panels.scripts)
            return;

        var lineNumber = row.lineNumber;
        var contextMenu = new WebInspector.ContextMenu();

        contextMenu.appendItem(WebInspector.UIString("Continue to Here"), this._continueToHereDelegate.bind(this, lineNumber + 1));

        var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint");
        if (!breakpoint) {
            // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
            contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), this._addBreakpointDelegate.bind(this, lineNumber + 1));

            function addConditionalBreakpoint()
            {
                this._addBreakpointDelegate(lineNumber + 1);
                var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint");
                if (breakpoint)
                    this._editBreakpointCondition(breakpoint);
            }

            contextMenu.appendItem(WebInspector.UIString("Add Conditional Breakpoint…"), addConditionalBreakpoint.bind(this));
        } else {
            // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable.
            contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), breakpoint.remove.bind(breakpoint));
            contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint…"), this._editBreakpointCondition.bind(this, breakpoint));
            if (breakpoint.enabled)
                contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), function() { breakpoint.enabled = false; });
            else
                contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), function() { breakpoint.enabled = true; });
        }
        contextMenu.show(event);
    },

    _scroll: function(event)
    {
        this._hidePopup();
    },

    _mouseDown: function(event)
    {
        this._resetHoverTimer();
        this._hidePopup();
        if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey)
            return;
        var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
        if (!target)
            return;
        var row = target.parentElement;

        var lineNumber = row.lineNumber;

        var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint");
        if (breakpoint) {
            if (event.shiftKey)
                breakpoint.enabled = !breakpoint.enabled;
            else
                breakpoint.remove();
        } else
            this._addBreakpointDelegate(lineNumber + 1);
        event.preventDefault();
    },

    _mouseMove: function(event)
    {
        // Pretend that nothing has happened.
        if (this._hoverElement === event.target || event.target.hasStyleClass("source-frame-eval-expression"))
            return;

        this._resetHoverTimer();
        // User has 500ms to reach the popup.
        if (this._popup) {
            var self = this;
            function doHide()
            {
                self._hidePopup();
                delete self._hidePopupTimer;
            }
            if (!("_hidePopupTimer" in this))
                this._hidePopupTimer = setTimeout(doHide, 500);
        }

        this._hoverElement = event.target;

        // Now that cleanup routines are set up above, leave this in case we are not on a break.
        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
            return;

        // We are interested in identifiers and "this" keyword.
        if (this._hoverElement.hasStyleClass("webkit-javascript-keyword")) {
            if (this._hoverElement.textContent !== "this")
                return;
        } else if (!this._hoverElement.hasStyleClass("webkit-javascript-ident"))
            return;

        const toolTipDelay = this._popup ? 600 : 1000;
        this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay);
    },

    _resetHoverTimer: function()
    {
        if (this._hoverTimer) {
            clearTimeout(this._hoverTimer);
            delete this._hoverTimer;
        }
    },

    _hidePopup: function()
    {
        if (!this._popup)
            return;

        // Replace higlight element with its contents inplace.
        var parentElement = this._popup.highlightElement.parentElement;
        var child = this._popup.highlightElement.firstChild;
        while (child) {
            var nextSibling = child.nextSibling;
            parentElement.insertBefore(child, this._popup.highlightElement);
            child = nextSibling;
        }
        parentElement.removeChild(this._popup.highlightElement);

        this._popup.hide();
        delete this._popup;
        InspectorBackend.releaseWrapperObjectGroup(0, this._popoverObjectGroup);
    },

    _mouseHover: function(element)
    {
        delete this._hoverTimer;

        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
            return;

        var lineRow = element.enclosingNodeOrSelfWithNodeName("tr");
        if (!lineRow)
            return;

        // Collect tokens belonging to evaluated exression.
        var tokens = [ element ];
        var token = element.previousSibling;
        while (token && (token.className === "webkit-javascript-ident" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) {
            tokens.push(token);
            token = token.previousSibling;
        }
        tokens.reverse();

        // Wrap them with highlight element.
        var parentElement = element.parentElement;
        var nextElement = element.nextSibling;
        var container = document.createElement("span");
        for (var i = 0; i < tokens.length; ++i)
            container.appendChild(tokens[i]);
        parentElement.insertBefore(container, nextElement);
        this._showPopup(container);
    },

    _showPopup: function(element)
    {
        function killHidePopupTimer()
        {
            if (this._hidePopupTimer) {
                clearTimeout(this._hidePopupTimer);
                delete this._hidePopupTimer;

                // We know that we reached the popup, but we might have moved over other elements.
                // Discard pending command.
                this._resetHoverTimer();
            }
        }

        function showObjectPopup(result)
        {
            if (!WebInspector.panels.scripts.paused)
                return;

            var popupContentElement = null;
            if (result.type !== "object" && result.type !== "node" && result.type !== "array") {
                popupContentElement = document.createElement("span");
                popupContentElement.className = "monospace console-formatted-" + result.type;
                popupContentElement.style.whiteSpace = "pre";
                popupContentElement.textContent = result.description;
                if (result.type === "string")
                    popupContentElement.textContent = "\"" + popupContentElement.textContent + "\"";
                this._popup = new WebInspector.Popover(popupContentElement);
                this._popup.show(element);
            } else {
                var popupContentElement = document.createElement("div");

                var titleElement = document.createElement("div");
                titleElement.className = "source-frame-popover-title monospace";
                titleElement.textContent = result.description;
                popupContentElement.appendChild(titleElement);

                var section = new WebInspector.ObjectPropertiesSection(result, "", null, false);
                section.expanded = true;
                section.element.addStyleClass("source-frame-popover-tree");
                section.headerElement.addStyleClass("hidden");
                popupContentElement.appendChild(section.element);

                this._popup = new WebInspector.Popover(popupContentElement);
                const popupWidth = 300;
                const popupHeight = 250;
                this._popup.show(element, popupWidth, popupHeight);
            }
            this._popup.highlightElement = element;
            this._popup.highlightElement.addStyleClass("source-frame-eval-expression");
            popupContentElement.addEventListener("mousemove", killHidePopupTimer.bind(this), true);
        }

        function evaluateCallback(result)
        {
            if (result.isError())
                return;
            if (!WebInspector.panels.scripts.paused)
                return;
            showObjectPopup.call(this, result);
        }
        WebInspector.panels.scripts.evaluateInSelectedCallFrame(element.textContent, false, this._popoverObjectGroup, evaluateCallback.bind(this));
    },

    _editBreakpointCondition: function(breakpoint)
    {
        this._showBreakpointConditionPopup(breakpoint.line);

        function committed(element, newText)
        {
            breakpoint.condition = newText;
            dismissed.call(this);
        }

        function dismissed()
        {
            if (this._conditionElement)
                this._textViewer.removeDecoration(breakpoint.line - 1, this._conditionElement);
            delete this._conditionEditorElement;
            delete this._conditionElement;
        }

        var dismissedHandler = dismissed.bind(this);
        this._conditionEditorElement.addEventListener("blur", dismissedHandler, false);

        WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler);
        this._conditionEditorElement.value = breakpoint.condition;
        this._conditionEditorElement.select();
    },

    _showBreakpointConditionPopup: function(lineNumber)
    {
        this._conditionElement = this._createConditionElement(lineNumber);
        this._textViewer.addDecoration(lineNumber - 1, this._conditionElement);
    },

    _createConditionElement: function(lineNumber)
    {
        var conditionElement = document.createElement("div");
        conditionElement.className = "source-frame-breakpoint-condition";

        var labelElement = document.createElement("label");
        labelElement.className = "source-frame-breakpoint-message";
        labelElement.htmlFor = "source-frame-breakpoint-condition";
        labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
        conditionElement.appendChild(labelElement);

        var editorElement = document.createElement("input");
        editorElement.id = "source-frame-breakpoint-condition";
        editorElement.className = "monospace";
        editorElement.type = "text"
        conditionElement.appendChild(editorElement);
        this._conditionEditorElement = editorElement;

        return conditionElement;
    },

    _evalSelectionInCallFrame: function(event)
    {
        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
            return;

        var selection = this.element.contentWindow.getSelection();
        if (!selection.rangeCount)
            return;

        var expression = selection.getRangeAt(0).toString().trim();
        WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", function(result) {
            WebInspector.showConsole();
            var commandMessage = new WebInspector.ConsoleCommand(expression);
            WebInspector.console.addMessage(commandMessage);
            WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
        });
    },

    resize: function()
    {
        if (this._textViewer)
            this._textViewer.resize();
    }
}


WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;

/* DOMSyntaxHighlighter.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMSyntaxHighlighter = function(mimeType)
{
    this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType);
}

WebInspector.DOMSyntaxHighlighter.prototype = {
    createSpan: function(content, className)
    {
        var span = document.createElement("span");
        span.className = "webkit-" + className;
        span.appendChild(document.createTextNode(content));
        return span;
    },

    syntaxHighlightNode: function(node)
    {
        this._tokenizer.condition = this._tokenizer.initialCondition;
        var lines = node.textContent.split("\n");
        node.removeChildren();

        for (var i = lines[0].length ? 0 : 1; i < lines.length; ++i) {
            var line = lines[i];
            var plainTextStart = 0;
            this._tokenizer.line = line;
            var column = 0;
            do {
                var newColumn = this._tokenizer.nextToken(column);
                var tokenType = this._tokenizer.tokenType;
                if (tokenType) {
                    if (column > plainTextStart) {
                        var plainText = line.substring(plainTextStart, column);
                        node.appendChild(document.createTextNode(plainText));
                    }
                    var token = line.substring(column, newColumn);
                    node.appendChild(this.createSpan(token, tokenType));
                    plainTextStart = newColumn;
                }
                column = newColumn;
           } while (column < line.length)

           if (plainTextStart < line.length) {
               var plainText = line.substring(plainTextStart, line.length);
               node.appendChild(document.createTextNode(plainText));
           }
           if (i < lines.length - 1)
               node.appendChild(document.createElement("br"));
        }
    }
}

/* TextEditorModel.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TextRange = function(startLine, startColumn, endLine, endColumn)
{
    this.startLine = startLine;
    this.startColumn = startColumn;
    this.endLine = endLine;
    this.endColumn = endColumn;
}

WebInspector.TextRange.prototype = {
    isEmpty: function()
    {
        return this.startLine === this.endLine && this.startColumn === this.endColumn;
    },

    get linesCount()
    {
        return this.endLine - this.startLine;
    },

    clone: function()
    {
        return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn); 
    }
}

WebInspector.TextEditorModel = function()
{
    this._lines = [""];
    this._attributes = [];
    this._undoStack = [];
    this._noPunctuationRegex = /[^ !%&()*+,-.:;<=>?\[\]\^{|}~]+/;
}

WebInspector.TextEditorModel.prototype = {
    set changeListener(changeListener)
    {
        this._changeListener = changeListener;
    },

    get linesCount()
    {
        return this._lines.length;
    },

    line: function(lineNumber)
    {
        if (lineNumber >= this._lines.length)
            throw "Out of bounds:" + lineNumber;
        return this._lines[lineNumber];
    },

    lineLength: function(lineNumber)
    {
        return this._lines[lineNumber].length;
    },

    setText: function(range, text)
    {
        if (!range)
            range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length);
        var command = this._pushUndoableCommand(range, text);
        var newRange = this._innerSetText(range, text);
        command.range = newRange.clone();

        if (this._changeListener)
            this._changeListener(range, newRange, command.text, text);
        return newRange;
    },

    set replaceTabsWithSpaces(replaceTabsWithSpaces)
    {
        this._replaceTabsWithSpaces = replaceTabsWithSpaces;
    },

    _innerSetText: function(range, text)
    {
        this._eraseRange(range);
        if (text === "")
            return new WebInspector.TextRange(range.startLine, range.startColumn, range.startLine, range.startColumn);

        var newLines = text.split("\n");
        this._replaceTabsIfNeeded(newLines);

        var prefix = this._lines[range.startLine].substring(0, range.startColumn);
        var prefixArguments = this._arguments
        var suffix = this._lines[range.startLine].substring(range.startColumn);

        var postCaret = prefix.length;
        // Insert text.
        if (newLines.length === 1) {
            this._setLine(range.startLine, prefix + newLines[0] + suffix);
            postCaret += newLines[0].length;
        } else {
            this._setLine(range.startLine, prefix + newLines[0]);
            for (var i = 1; i < newLines.length; ++i)
                this._insertLine(range.startLine + i, newLines[i]);
            this._setLine(range.startLine + newLines.length - 1, newLines[newLines.length - 1] + suffix);
            postCaret = newLines[newLines.length - 1].length;
        }
        return new WebInspector.TextRange(range.startLine, range.startColumn,
                                          range.startLine + newLines.length - 1, postCaret);
    },

    _replaceTabsIfNeeded: function(lines)
    {
        if (!this._replaceTabsWithSpaces)
            return;
        var spaces = [ "    ", "   ", "  ", " "];
        for (var i = 0; i < lines.length; ++i) {
            var line = lines[i];
            var index = line.indexOf("\t");
            while (index !== -1) {
                line = line.substring(0, index) + spaces[index % 4] + line.substring(index + 1);
                index = line.indexOf("\t", index + 1);
            }
            lines[i] = line;
        }
    },

    _eraseRange: function(range)
    {
        if (range.isEmpty())
            return;

        var prefix = this._lines[range.startLine].substring(0, range.startColumn);
        var suffix = this._lines[range.endLine].substring(range.endColumn);

        if (range.endLine > range.startLine)
            this._removeLines(range.startLine + 1, range.endLine - range.startLine);
        this._setLine(range.startLine, prefix + suffix);
    },

    _setLine: function(lineNumber, text)
    {
        this._lines[lineNumber] = text;
    },

    _removeLines: function(fromLine, count)
    {
        this._lines.splice(fromLine, count);
        this._attributes.splice(fromLine, count);
    },

    _insertLine: function(lineNumber, text)
    {
        this._lines.splice(lineNumber, 0, text);
        this._attributes.splice(lineNumber, 0, {});
    },

    wordRange: function(lineNumber, column)
    {
        return new WebInspector.TextRange(lineNumber, this.wordStart(lineNumber, column, true), lineNumber, this.wordEnd(lineNumber, column, true));
    },

    wordStart: function(lineNumber, column, gapless)
    {
        var line = this._lines[lineNumber];
        var prefix = line.substring(0, column).split("").reverse().join("");
        var prefixMatch = this._noPunctuationRegex.exec(prefix);
        return prefixMatch && (!gapless || prefixMatch.index === 0) ? column - prefixMatch.index - prefixMatch[0].length : column;
    },

    wordEnd: function(lineNumber, column, gapless)
    {
        var line = this._lines[lineNumber];
        var suffix = line.substring(column);
        var suffixMatch = this._noPunctuationRegex.exec(suffix);
        return suffixMatch && (!gapless || suffixMatch.index === 0) ? column + suffixMatch.index + suffixMatch[0].length : column;
    },

    copyRange: function(range)
    {
        if (!range)
            range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length);

        var clip = [];
        if (range.startLine === range.endLine) {
            clip.push(this._lines[range.startLine].substring(range.startColumn, range.endColumn));
            return clip.join("\n");
        }
        clip.push(this._lines[range.startLine].substring(range.startColumn));
        for (var i = range.startLine + 1; i < range.endLine; ++i)
            clip.push(this._lines[i]);
        clip.push(this._lines[range.endLine].substring(0, range.endColumn));
        return clip.join("\n");
    },

    setAttribute: function(line, name, value)
    {
        var attrs = this._attributes[line];
        if (!attrs) {
            attrs = {};
            this._attributes[line] = attrs;
        }
        attrs[name] = value;
    },

    getAttribute: function(line, name)
    {
        var attrs = this._attributes[line];
        return attrs ? attrs[name] : null;
    },

    removeAttribute: function(line, name)
    {
        var attrs = this._attributes[line];
        if (attrs)
            delete attrs[name];
    },

    _pushUndoableCommand: function(range, text)
    {
        var command = {
            text: this.copyRange(range),
            startLine: range.startLine,
            startColumn: range.startColumn,
            endLine: range.startLine,
            endColumn: range.startColumn
        };
        if (this._inUndo)
            this._redoStack.push(command);
        else {
            if (!this._inRedo)
                this._redoStack = [];
            this._undoStack.push(command);
        }
        return command;
    },

    undo: function()
    {
        this._markRedoableState();

        this._inUndo = true;
        var range = this._doUndo(this._undoStack);
        delete this._inUndo;

        return range;
    },

    redo: function()
    {
        this.markUndoableState();

        this._inRedo = true;
        var range = this._doUndo(this._redoStack);
        delete this._inRedo;

        return range;
    },

    _doUndo: function(stack)
    {
        var range = null;
        for (var i = stack.length - 1; i >= 0; --i) {
            var command = stack[i];
            stack.length = i;

            range = this.setText(command.range, command.text);
            if (i > 0 && stack[i - 1].explicit)
                return range;
        }
        return range;
    },

    markUndoableState: function()
    {
        if (this._undoStack.length)
            this._undoStack[this._undoStack.length - 1].explicit = true;
    },

    _markRedoableState: function()
    {
        if (this._redoStack.length)
            this._redoStack[this._redoStack.length - 1].explicit = true;
    },

    resetUndoStack: function()
    {
        this._undoStack = [];
    }
}

/* TextEditorHighlighter.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TextEditorHighlighter = function(textModel, damageCallback)
{
    this._textModel = textModel;
    this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/html");
    this._damageCallback = damageCallback;
    this.reset();
}

WebInspector.TextEditorHighlighter.prototype = {
    set mimeType(mimeType)
    {
        var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType);
        if (tokenizer) {
            this._tokenizer = tokenizer;
            this._tokenizerCondition = this._tokenizer.initialCondition;
        }
    },

    reset: function()
    {
        this._lastHighlightedLine = 0;
        this._lastHighlightedColumn = 0;
        this._tokenizerCondition = this._tokenizer.initialCondition;
    },

    highlight: function(endLine)
    {
        // First check if we have work to do.
        if (endLine <= this._lastHighlightedLine)
            return;

        this._requestedEndLine = endLine;

        if (this._highlightTimer) {
            // There is a timer scheduled, it will catch the new job based on the new endLine set.
            return;
        }

        // Do small highlight synchronously. This will provide instant highlight on PageUp / PageDown, gentle scrolling.
        this._highlightInChunks(endLine);

        // Schedule tail highlight if necessary.
        if (this._lastHighlightedLine < endLine)
            this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, endLine), 100);
    },

    _highlightInChunks: function(endLine)
    {
        delete this._highlightTimer;

        // First we always check if we have work to do. Could be that user scrolled back and we can quit.
        if (this._requestedEndLine <= this._lastHighlightedLine)
            return;

        if (this._requestedEndLine !== endLine) {
            // User keeps updating the job in between of our timer ticks. Just reschedule self, don't eat CPU (they must be scrolling).
            this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._requestedEndLine), 100);
            return;
        }

        this._highlightLines(this._requestedEndLine);

        // Schedule tail highlight if necessary.
        if (this._lastHighlightedLine < this._requestedEndLine)
            this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._requestedEndLine), 10);
    },

    _highlightLines: function(endLine)
    {
        // Tokenizer is stateless and reused accross viewers, restore its condition before highlight and save it after.
        this._tokenizer.condition = this._tokenizerCondition;
        var tokensCount = 0;
        for (var lineNumber = this._lastHighlightedLine; lineNumber < endLine; ++lineNumber) {
            var line = this._textModel.line(lineNumber);
            this._tokenizer.line = line;
            var attributes = this._textModel.getAttribute(lineNumber, "highlight") || {};

            // Highlight line.
            do {
                var newColumn = this._tokenizer.nextToken(this._lastHighlightedColumn);
                var tokenType = this._tokenizer.tokenType;
                if (tokenType)
                    attributes[this._lastHighlightedColumn] = { length: newColumn - this._lastHighlightedColumn, tokenType: tokenType, subTokenizer: this._tokenizer.subTokenizer };
                this._lastHighlightedColumn = newColumn;
                if (++tokensCount > 1000)
                    break;
            } while (this._lastHighlightedColumn < line.length)

            this._textModel.setAttribute(lineNumber, "highlight", attributes);
            if (this._lastHighlightedColumn < line.length) {
                // Too much work for single chunk - exit.
                break;
            } else
                this._lastHighlightedColumn = 0;
        }

        this._damageCallback(this._lastHighlightedLine, lineNumber);
        this._tokenizerCondition = this._tokenizer.condition;
        this._lastHighlightedLine = lineNumber;
    }
}

/* TextViewer.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TextViewer = function(textModel, platform, url)
{
    this._textModel = textModel;
    this._textModel.changeListener = this._buildChunks.bind(this);
    this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightDataReady.bind(this));

    this.element = document.createElement("div");
    this.element.className = "text-editor monospace";
    this.element.tabIndex = 0;

    this.element.addEventListener("scroll", this._scroll.bind(this), false);
    this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
    this.element.addEventListener("beforecopy", this._beforeCopy.bind(this), false);
    this.element.addEventListener("copy", this._copy.bind(this), false);
    this.element.addEventListener("dblclick", this._handleDoubleClick.bind(this), false);

    this._url = url;

    this._linesContainerElement = document.createElement("table");
    this._linesContainerElement.className = "text-editor-lines";
    this._linesContainerElement.setAttribute("cellspacing", 0);
    this._linesContainerElement.setAttribute("cellpadding", 0);
    this.element.appendChild(this._linesContainerElement);

    this._defaultChunkSize = 50;
    this._paintCoalescingLevel = 0;

    this.freeCachedElements();
    this._buildChunks();
}

WebInspector.TextViewer.prototype = {
    set mimeType(mimeType)
    {
        this._highlighter.mimeType = mimeType;
    },

    get textModel()
    {
        return this._textModel;
    },

    revealLine: function(lineNumber)
    {
        if (lineNumber >= this._textModel.linesCount)
            return;

        var chunk = this._makeLineAChunk(lineNumber);
        chunk.element.scrollIntoViewIfNeeded();
    },

    set editCallback(editCallback)
    {
        this._editCallback = editCallback;
    },

    addDecoration: function(lineNumber, decoration)
    {
        var chunk = this._makeLineAChunk(lineNumber);
        chunk.addDecoration(decoration);
    },

    removeDecoration: function(lineNumber, decoration)
    {
        var chunk = this._makeLineAChunk(lineNumber);
        chunk.removeDecoration(decoration);
    },

    markAndRevealRange: function(range)
    {
        if (this._rangeToMark) {
            var markedLine = this._rangeToMark.startLine;
            this._rangeToMark = null;
            this._paintLines(markedLine, markedLine + 1);
        }

        if (range) {
            this._rangeToMark = range;
            this.revealLine(range.startLine);
            this._paintLines(range.startLine, range.startLine + 1);
            if (this._markedRangeElement)
                this._markedRangeElement.scrollIntoViewIfNeeded();
        }
        delete this._markedRangeElement;
    },

    highlightLine: function(lineNumber)
    {
        this.clearLineHighlight();
        this._highlightedLine = lineNumber;
        this.revealLine(lineNumber);
        var chunk = this._makeLineAChunk(lineNumber);
        chunk.addDecoration("webkit-highlighted-line");
    },

    clearLineHighlight: function()
    {
        if (typeof this._highlightedLine === "number") {
            var chunk = this._makeLineAChunk(this._highlightedLine);
            chunk.removeDecoration("webkit-highlighted-line");
            delete this._highlightedLine;
        }
    },

    freeCachedElements: function()
    {
        this._cachedSpans = [];
        this._cachedTextNodes = [];
        this._cachedRows = [];
    },

    _buildChunks: function()
    {
        this._linesContainerElement.removeChildren();

        this._textChunks = [];
        for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
            var chunk = new WebInspector.TextChunk(this, i, i + this._defaultChunkSize);
            this._textChunks.push(chunk);
            this._linesContainerElement.appendChild(chunk.element);
        }

        this._indexChunks();
        this._highlighter.reset();
        this._repaintAll();
    },

    _makeLineAChunk: function(lineNumber)
    {
        if (!this._textChunks)
            this._buildChunks();

        var chunkNumber = this._chunkNumberForLine(lineNumber);
        var oldChunk = this._textChunks[chunkNumber];
        if (oldChunk.linesCount === 1)
            return oldChunk;

        var wasExpanded = oldChunk.expanded;
        oldChunk.expanded = false;

        var insertIndex = oldChunk.chunkNumber + 1;

        // Prefix chunk.
        if (lineNumber > oldChunk.startLine) {
            var prefixChunk = new WebInspector.TextChunk(this, oldChunk.startLine, lineNumber);
            this._textChunks.splice(insertIndex++, 0, prefixChunk);
            this._linesContainerElement.insertBefore(prefixChunk.element, oldChunk.element);
        }

        // Line chunk.
        var lineChunk = new WebInspector.TextChunk(this, lineNumber, lineNumber + 1);
        this._textChunks.splice(insertIndex++, 0, lineChunk);
        this._linesContainerElement.insertBefore(lineChunk.element, oldChunk.element);

        // Suffix chunk.
        if (oldChunk.startLine + oldChunk.linesCount > lineNumber + 1) {
            var suffixChunk = new WebInspector.TextChunk(this, lineNumber + 1, oldChunk.startLine + oldChunk.linesCount);
            this._textChunks.splice(insertIndex, 0, suffixChunk);
            this._linesContainerElement.insertBefore(suffixChunk.element, oldChunk.element);
        }

        // Remove enclosing chunk.
        this._textChunks.splice(oldChunk.chunkNumber, 1);
        this._linesContainerElement.removeChild(oldChunk.element);
        this._indexChunks();

        if (wasExpanded) {
            if (prefixChunk)
                prefixChunk.expanded = true;
            lineChunk.expanded = true;
            if (suffixChunk)
                suffixChunk.expanded = true;
        }

        return lineChunk;
    },

    _indexChunks: function()
    {
        for (var i = 0; i < this._textChunks.length; ++i)
            this._textChunks[i].chunkNumber = i;
    },

    _scroll: function()
    {
        var scrollTop = this.element.scrollTop;
        setTimeout(function() {
            if (scrollTop === this.element.scrollTop)
                this._repaintAll();
        }.bind(this), 50);
    },

    _handleKeyDown: function()
    {
        if (this._editingLine || event.metaKey || event.shiftKey || event.ctrlKey || event.altKey)
            return;

        var scrollValue = 0;
        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Up.code)
            scrollValue = -1;
        else if (event.keyCode == WebInspector.KeyboardShortcut.Keys.Down.code)
            scrollValue = 1;
        
        if (scrollValue) {
            event.preventDefault();
            event.stopPropagation();
            this.element.scrollByLines(scrollValue);
            return;
        }
        
        scrollValue = 0;
        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Left.code)
            scrollValue = -40;
        else if (event.keyCode == WebInspector.KeyboardShortcut.Keys.Right.code)
            scrollValue = 40;
        
        if (scrollValue) {
            event.preventDefault();
            event.stopPropagation();
            this.element.scrollLeft += scrollValue;
        }
    },

    _handleDoubleClick: function(e)
    {
        if (!this._editCallback)
            return;

        var cell = e.target.enclosingNodeOrSelfWithNodeName("TD");
        if (!cell)
            return;

        var lineRow = cell.parentElement;
        if (lineRow.firstChild === cell)
            return;  // Do not trigger editing from line numbers.

        var oldContent = lineRow.lastChild.innerHTML;
        var cancelEditingCallback = this._cancelEditingLine.bind(this, lineRow.lastChild, oldContent);
        var commitEditingCallback = this._commitEditingLine.bind(this, lineRow.lineNumber, lineRow.lastChild, cancelEditingCallback);
        this._editingLine = WebInspector.startEditing(lineRow.lastChild, commitEditingCallback, cancelEditingCallback, null, true);
    },

    _commitEditingLine: function(lineNumber, element, cancelEditingCallback)
    {
        this._editCallback(lineNumber, element.textContent, cancelEditingCallback);
        delete this._editingLine;
    },

    _cancelEditingLine: function(element, oldContent, e)
    {
        element.innerHTML = oldContent;
        delete this._editingLine;
    },

    _beforeCopy: function(e)
    {
        e.preventDefault();
    },

    _copy: function(e)
    {
        var range = this._getSelection();
        var text = this._textModel.copyRange(range);
        InspectorFrontendHost.copyText(text);
        e.preventDefault();
    },

    beginUpdates: function(enabled)
    {
        this._paintCoalescingLevel++;
    },

    endUpdates: function(enabled)
    {
        this._paintCoalescingLevel--;
        if (!this._paintCoalescingLevel)
            this._repaintAll();
    },

    _chunkForOffset: function(offset)
    {
        var currentOffset = 0;
        var row = this._linesContainerElement.firstChild;
        while (row) {
            var rowHeight = row.offsetHeight;
            if (offset >= currentOffset && offset < currentOffset + rowHeight)
                return row.chunkNumber;
            row = row.nextSibling;
            currentOffset += rowHeight;
        }
        return this._textChunks.length - 1;
    },

    _chunkNumberForLine: function(lineNumber)
    {
        for (var i = 0; i < this._textChunks.length; ++i) {
            var line = this._textChunks[i].startLine;
            if (lineNumber >= this._textChunks[i].startLine && lineNumber < this._textChunks[i].startLine + this._textChunks[i].linesCount)
                return i;
        }
        return this._textChunks.length - 1;
    },

    _chunkForLine: function(lineNumber)
    {
        return this._textChunks[this._chunkNumberForLine(lineNumber)];
    },

    _chunkStartLine: function(chunkNumber)
    {
        var lineNumber = 0;
        for (var i = 0; i < chunkNumber && i < this._textChunks.length; ++i)
            lineNumber += this._textChunks[i].linesCount;
        return lineNumber;
    },

    _repaintAll: function()
    {
        if (this._paintCoalescingLevel)
            return;

        if (!this._textChunks)
            this._buildChunks();

        var visibleFrom = this.element.scrollTop;
        var visibleTo = this.element.scrollTop + this.element.clientHeight;

        var offset = 0;
        var firstVisibleLine = -1;
        var lastVisibleLine = 0;
        var toExpand = [];
        var toCollapse = [];
        for (var i = 0; i < this._textChunks.length; ++i) {
            var chunk = this._textChunks[i];
            var chunkHeight = chunk.height;
            if (offset + chunkHeight > visibleFrom && offset < visibleTo) {
                toExpand.push(chunk);
                if (firstVisibleLine === -1)
                    firstVisibleLine = chunk.startLine;
                lastVisibleLine = chunk.startLine + chunk.linesCount;
            } else {
                toCollapse.push(chunk);
                if (offset >= visibleTo)
                    break;
            }
            offset += chunkHeight;
        }

        for (var j = i; j < this._textChunks.length; ++j)
            toCollapse.push(this._textChunks[i]);

        var selection = this._getSelection();

        this._muteHighlightListener = true;
        this._highlighter.highlight(lastVisibleLine);
        delete this._muteHighlightListener;

        for (var i = 0; i < toCollapse.length; ++i)
            toCollapse[i].expanded = false;
        for (var i = 0; i < toExpand.length; ++i)
            toExpand[i].expanded = true;

        this._restoreSelection(selection);
    },

    _highlightDataReady: function(fromLine, toLine)
    {
        if (this._muteHighlightListener)
            return;

        var selection;
        for (var i = fromLine; i < toLine; ++i) {
            var lineRow = this._textModel.getAttribute(i, "line-row");
            if (!lineRow || lineRow.highlighted)
                continue;
            if (!selection)
                selection = this._getSelection();
            this._paintLine(lineRow, i);
        }
        this._restoreSelection(selection);
    },

    _paintLines: function(fromLine, toLine)
    {
        for (var i = fromLine; i < toLine; ++i) {
            var lineRow = this._textModel.getAttribute(i, "line-row");
            if (lineRow)
                this._paintLine(lineRow, i);
        }
    },

    _paintLine: function(lineRow, lineNumber)
    {
        var element = lineRow.lastChild;
        var highlight = this._textModel.getAttribute(lineNumber, "highlight");
        if (!highlight) {
            if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
                this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
            return;
        }

        element.removeChildren();
        var line = this._textModel.line(lineNumber);

        var plainTextStart = -1;
        for (var j = 0; j < line.length;) {
            if (j > 1000) {
                // This line is too long - do not waste cycles on minified js highlighting.
                if (plainTextStart === -1)
                    plainTextStart = j;
                break;
            }
            var attribute = highlight[j];
            if (!attribute || !attribute.tokenType) {
                if (plainTextStart === -1)
                    plainTextStart = j;
                j++;
            } else {
                if (plainTextStart !== -1) {
                    this._appendTextNode(element, line.substring(plainTextStart, j));
                    plainTextStart = -1;
                }
                this._appendSpan(element, line.substring(j, j + attribute.length), attribute.tokenType);
                j += attribute.length;
            }
        }
        if (plainTextStart !== -1)
            this._appendTextNode(element, line.substring(plainTextStart, line.length));
        if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
            this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
        if (lineRow.decorationsElement)
            element.appendChild(lineRow.decorationsElement);
    },

    _releaseLinesHighlight: function(fromLine, toLine)
    {
        for (var i = fromLine; i < toLine; ++i) {
            var lineRow = this._textModel.getAttribute(i, "line-row");
            if (!lineRow)
                continue;
            var element = lineRow.lastChild;
            if ("spans" in element) {
                var spans = element.spans;
                for (var j = 0; j < spans.length; ++j)
                    this._cachedSpans.push(spans[j]);
                delete element.spans;
            }
            if ("textNodes" in element) {
                var textNodes = element.textNodes;
                for (var j = 0; j < textNodes.length; ++j)
                    this._cachedTextNodes.push(textNodes[j]);
                delete element.textNodes;
            }
        }
    },

    _getSelection: function()
    {
        var selection = window.getSelection();
        if (selection.isCollapsed)
            return null;
        var selectionRange = selection.getRangeAt(0);
        // Selection may be outside of the viewer.
        if (!this.element.isAncestor(selectionRange.startContainer) || !this.element.isAncestor(selectionRange.endContainer))
            return null;
        var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset);
        var end = this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
        return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
    },

    _restoreSelection: function(range)
    {
        if (!range)
            return;
        var startRow = this._textModel.getAttribute(range.startLine, "line-row");
        if (startRow)
            var start = startRow.lastChild.rangeBoundaryForOffset(range.startColumn);
        else {
            var offset = range.startColumn;
            var chunkNumber = this._chunkNumberForLine(range.startLine);
            for (var i = this._chunkStartLine(chunkNumber); i < range.startLine; ++i)
                offset += this._textModel.line(i).length + 1; // \n
            var lineCell = this._textChunks[chunkNumber].element.lastChild;
            if (lineCell.firstChild)
                var start = { container: lineCell.firstChild, offset: offset };
            else
                var start = { container: lineCell, offset: 0 };
        }

        var endRow = this._textModel.getAttribute(range.endLine, "line-row");
        if (endRow)
            var end = endRow.lastChild.rangeBoundaryForOffset(range.endColumn);
        else {
            var offset = range.endColumn;
            var chunkNumber = this._chunkNumberForLine(range.endLine);
            for (var i = this._chunkStartLine(chunkNumber); i < range.endLine; ++i)
                offset += this._textModel.line(i).length + 1; // \n
            var lineCell = this._textChunks[chunkNumber].element.lastChild;
            if (lineCell.firstChild)
                var end = { container: lineCell.firstChild, offset: offset };
            else
                var end = { container: lineCell, offset: 0 };
        }

        var selectionRange = document.createRange();
        selectionRange.setStart(start.container, start.offset);
        selectionRange.setEnd(end.container, end.offset);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(selectionRange);
    },

    _selectionToPosition: function(container, offset)
    {
        if (container === this.element && offset === 0)
            return { line: 0, column: 0 };
        if (container === this.element && offset === 1)
            return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };

        var lineRow = container.enclosingNodeOrSelfWithNodeName("tr");
        var lineNumber = lineRow.lineNumber;
        if (container.nodeName === "TD" && offset === 0)
            return { line: lineNumber, column: 0 };
        if (container.nodeName === "TD" && offset === 1)
            return { line: lineNumber, column: this._textModel.lineLength(lineNumber) };

        var column = 0;
        var node = lineRow.lastChild.traverseNextTextNode(lineRow.lastChild);
        while (node && node !== container) {
            column += node.textContent.length;
            node = node.traverseNextTextNode(lineRow.lastChild);
        }

        // This may be chunk and chunks may contain \n.
        if (node === container && offset) {
            var text = node.textContent;
            for (var i = 0; i < offset; ++i) {
                if (text.charAt(i) === "\n") {
                    lineNumber++;
                    column = 0;
                } else
                    column++;
            }
        }
        return { line: lineNumber, column: column };
    },

    _appendSpan: function(element, content, className)
    {
        if (className === "html-resource-link" || className === "html-external-link") {
            element.appendChild(this._createLink(content, className === "html-external-link"));
            return;
        }

        var span = this._cachedSpans.pop() || document.createElement("span");
        span.className = "webkit-" + className;
        span.textContent = content;
        element.appendChild(span);
        if (!("spans" in element))
            element.spans = [];
        element.spans.push(span);
    },

    _appendTextNode: function(element, text)
    {
        var textNode = this._cachedTextNodes.pop();
        if (textNode) {
            textNode.nodeValue = text;
        } else
            textNode = document.createTextNode(text);
        element.appendChild(textNode);
        if (!("textNodes" in element))
            element.textNodes = [];
        element.textNodes.push(textNode);
    },

    _createLink: function(content, isExternal)
    {
        var quote = content.charAt(0);
        if (content.length > 1 && (quote === "\"" ||   quote === "'"))
            content = content.substring(1, content.length - 1);
        else
            quote = null;

        var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, null, isExternal);
        var span = document.createElement("span");
        span.className = "webkit-html-attribute-value";
        if (quote)
            span.appendChild(document.createTextNode(quote));
        span.appendChild(a);
        if (quote)
            span.appendChild(document.createTextNode(quote));
        return span;
    },

    _rewriteHref: function(hrefValue, isExternal)
    {
        if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0)
            return hrefValue;
        return WebInspector.completeURL(this._url, hrefValue);
    },

    resize: function()
    {
        this._repaintAll();
    }
}

var cachedSpans = [];

WebInspector.TextChunk = function(textViewer, startLine, endLine)
{
    this._textViewer = textViewer;
    this.element = document.createElement("tr");
    this._textModel = textViewer._textModel;
    this.element.chunk = this;
    this.element.lineNumber = startLine;

    this.startLine = startLine;
    endLine = Math.min(this._textModel.linesCount, endLine);
    this.linesCount = endLine - startLine;

    this._lineNumberElement = document.createElement("td");
    this._lineNumberElement.className = "webkit-line-number";
    this.element.appendChild(this._lineNumberElement);

    this._lineContentElement = document.createElement("td");
    this._lineContentElement.className = "webkit-line-content";
    this.element.appendChild(this._lineContentElement);

    this._expanded = false;

    var lineNumbers = [];
    var lines = [];
    for (var i = startLine; i < endLine; ++i) {
        lineNumbers.push(i + 1);
        lines.push(this._textModel.line(i));
    }
    if (this.linesCount === 1) {
        // Single line chunks are typically created for decorations. Host line number in
        // the sub-element in order to allow flexible border / margin management.
        var innerSpan = document.createElement("span");
        innerSpan.className = "webkit-line-number-inner";
        innerSpan.textContent = startLine + 1;
        var outerSpan = document.createElement("div");
        outerSpan.className = "webkit-line-number-outer";
        outerSpan.appendChild(innerSpan);
        this._lineNumberElement.appendChild(outerSpan);
    } else
        this._lineNumberElement.textContent = lineNumbers.join("\n");
    this._lineContentElement.textContent = lines.join("\n");
}

WebInspector.TextChunk.prototype = {
    addDecoration: function(decoration)
    {
        if (typeof decoration === "string") {
            this.element.addStyleClass(decoration);
            return;
        }
        if (!this.element.decorationsElement) {
            this.element.decorationsElement = document.createElement("div");
            this._lineContentElement.appendChild(this.element.decorationsElement);
        }
        this.element.decorationsElement.appendChild(decoration);
    },

    removeDecoration: function(decoration)
    {
        if (typeof decoration === "string") {
            this.element.removeStyleClass(decoration);
            return;
        }
        if (!this.element.decorationsElement)
            return;
        this.element.decorationsElement.removeChild(decoration);
    },

    get expanded()
    {
        return this._expanded;
    },

    set expanded(expanded)
    {
        if (this._expanded === expanded)
            return;

        this._expanded = expanded;

        if (this.linesCount === 1) {
            this._textModel.setAttribute(this.startLine, "line-row", this.element);
            if (expanded)
                this._textViewer._paintLines(this.startLine, this.startLine + 1);
            return;
        }

        if (expanded) {
            var parentElement = this.element.parentElement;
            for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
                var lineRow = this._createRow(i);
                this._textModel.setAttribute(i, "line-row", lineRow);
                parentElement.insertBefore(lineRow, this.element);
            }
            parentElement.removeChild(this.element);

            this._textViewer._paintLines(this.startLine, this.startLine + this.linesCount);
        } else {
            var firstLine = this._textModel.getAttribute(this.startLine, "line-row");
            var parentElement = firstLine.parentElement;
            this._textViewer._releaseLinesHighlight(this.startLine, this.startLine + this.linesCount);

            parentElement.insertBefore(this.element, firstLine);
            for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
                var lineRow = this._textModel.getAttribute(i, "line-row");
                this._textModel.removeAttribute(i, "line-row");
                this._textViewer._cachedRows.push(lineRow);
                parentElement.removeChild(lineRow);
            }
        }
    },

    get height()
    {
        if (!this._expanded)
            return this.element.offsetHeight;
        var result = 0;
        for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
            var lineRow = this._textModel.getAttribute(i, "line-row");
            result += lineRow.offsetHeight;
        }
        return result;
    },

    _createRow: function(lineNumber)
    {
        var cachedRows = this._textViewer._cachedRows;
        if (cachedRows.length) {
            var lineRow = cachedRows[cachedRows.length - 1];
            cachedRows.length--;
            var lineNumberElement = lineRow.firstChild;
            var lineContentElement = lineRow.lastChild;
        } else {
            var lineRow = document.createElement("tr");

            var lineNumberElement = document.createElement("td");
            lineNumberElement.className = "webkit-line-number";
            lineRow.appendChild(lineNumberElement);

            var lineContentElement = document.createElement("td");
            lineContentElement.className = "webkit-line-content";
            lineRow.appendChild(lineContentElement);        
        }
        lineRow.lineNumber = lineNumber;
        lineNumberElement.textContent = lineNumber + 1;
        lineContentElement.textContent = this._textModel.line(lineNumber);
        return lineRow;
    }
}

/* SourceTokenizer.js */

/* Generated by re2c 0.13.5 on Tue Jan 26 01:16:33 2010 */
/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SourceTokenizer = function()
{
}

WebInspector.SourceTokenizer.prototype = {
    set line(line) {
        this._line = line;
    },

    set condition(condition)
    {
        this._condition = condition;
    },

    get condition()
    {
        return this._condition;
    },

    get subTokenizer()
    {
        return this._condition.subTokenizer;
    },

    getLexCondition: function()
    {
        return this.condition.lexCondition;
    },

    setLexCondition: function(lexCondition)
    {
        this.condition.lexCondition = lexCondition;
    },

    _charAt: function(cursor)
    {
        return cursor < this._line.length ? this._line.charAt(cursor) : "\n";
    }
}


WebInspector.SourceTokenizer.Registry = function() {
    this._tokenizers = {};
    this._tokenizerConstructors = {
        "text/css": "SourceCSSTokenizer",
        "text/html": "SourceHTMLTokenizer",
        "text/javascript": "SourceJavaScriptTokenizer"
    };
}

WebInspector.SourceTokenizer.Registry.getInstance = function()
{
    if (!WebInspector.SourceTokenizer.Registry._instance)
        WebInspector.SourceTokenizer.Registry._instance = new WebInspector.SourceTokenizer.Registry();
    return WebInspector.SourceTokenizer.Registry._instance;
}

WebInspector.SourceTokenizer.Registry.prototype = {
    getTokenizer: function(mimeType)
    {
        if (!this._tokenizerConstructors[mimeType])
            return null;
        var tokenizerClass = this._tokenizerConstructors[mimeType];
        var tokenizer = this._tokenizers[tokenizerClass];
        if (!tokenizer) {
            tokenizer = new WebInspector[tokenizerClass]();
            this._tokenizers[mimeType] = tokenizer;
        }
        return tokenizer;
    }
}

/* SourceCSSTokenizer.js */

/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */
/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Generate js file as follows:
//
// re2c -isc WebCore/inspector/front-end/SourceCSSTokenizer.re2js \
// | sed 's|^yy\([^:]*\)*\:|case \1:|' \
// | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \
// | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \
// | sed 's|[*]cursor|this._charAt(cursor)|' \
// | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \
// | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \
// | sed 's|unsigned\ int|var|' \
// | sed 's|var\ yych|case 1: case 1: var yych|'

WebInspector.SourceCSSTokenizer = function()
{
    WebInspector.SourceTokenizer.call(this);

    this._propertyKeywords = WebInspector.CSSCompletions.keySet();

    this._valueKeywords = [
        "above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll",
        "alternate", "always","amharic", "amharic-abegede", "antialiased", "appworkspace", "aqua", "arabic-indic", "armenian",
        "auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "black", "blink",
        "block", "block-axis", "blue", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button",
        "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator",
        "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic",
        "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu",
        "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default",
        "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document",
        "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element",
        "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez",
        "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et",
        "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et",
        "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded",
        "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "forwards", "from", "fuchsia", "geometricPrecision",
        "georgian", "gray", "graytext", "green", "grey", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help",
        "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
        "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline",
        "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana",
        "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "lime", "line-through", "linear", "lines",
        "list-button", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-greek", "lower-hexadecimal", "lower-latin",
        "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "maroon", "match", "media-controls-background", "media-current-time-display",
        "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", "media-rewind-button",
        "media-seek-back-button", "media-seek-forward-button", "media-slider", "media-sliderthumb", "media-time-remaining-display",
        "media-volume-slider", "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button",
        "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple",
        "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none",
        "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "olive", "open-quote", "optimizeLegibility",
        "optimizeSpeed", "orange", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused",
        "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "purple",
        "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "red", "relative", "repeat", "repeat-x",
        "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif",
        "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
        "searchfield-results-decoration", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "silver", "single",
        "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
        "small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over",
        "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super",
        "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group",
        "table-row", "table-row-group", "teal", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin",
        "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede",
        "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-greek",
        "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible",
        "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext",
        "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle",
        "-webkit-body", "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing",
        "-webkit-gradient", "-webkit-inline-box", "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-right",
        "-webkit-small-control", "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out",
    ].keySet();

    this._mediaTypes = ["all", "aural", "braille", "embossed", "handheld", "import", "print", "projection", "screen", "tty", "tv"].keySet();

    this._lexConditions = {
        INITIAL: 0,
        COMMENT: 1,
        DSTRING: 2,
        SSTRING: 3
    };

    this._parseConditions = {
        INITIAL: 0,
        PROPERTY: 1,
        PROPERTY_VALUE: 2,
        AT_RULE: 3
    };

    this.case_INITIAL = 1000;
    this.case_COMMENT = 1002;
    this.case_DSTRING = 1003;
    this.case_SSTRING = 1004;

    this.initialCondition = { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL }
}

WebInspector.SourceCSSTokenizer.prototype = {
    _stringToken: function(cursor, stringEnds)
    {
        if (this._isPropertyValue())
            this.tokenType = "css-string";
        else
            this.tokenType = null;
        return cursor;
    },

    _isPropertyValue: function()
    {
        return this._condition.parseCondition === this._parseConditions.PROPERTY_VALUE || this._condition.parseCondition === this._parseConditions.AT_RULE;
    },

    nextToken: function(cursor)
    {
        var cursorOnEnter = cursor;
        var gotoCase = 1;
        while (1) {
            switch (gotoCase)
            // Following comment is replaced with generated state machine.
            
        {
            case 1: var yych;
            var yyaccept = 0;
            if (this.getLexCondition() < 2) {
                if (this.getLexCondition() < 1) {
                    { gotoCase = this.case_INITIAL; continue; };
                } else {
                    { gotoCase = this.case_COMMENT; continue; };
                }
            } else {
                if (this.getLexCondition() < 3) {
                    { gotoCase = this.case_DSTRING; continue; };
                } else {
                    { gotoCase = this.case_SSTRING; continue; };
                }
            }
/* *********************************** */
case this.case_COMMENT:

            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 4; continue; };
                { gotoCase = 3; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 4; continue; };
                if (yych == '*') { gotoCase = 6; continue; };
                { gotoCase = 3; continue; };
            }
case 2:
            { this.tokenType = "css-comment"; return cursor; }
case 3:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 12; continue; };
case 4:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 6:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '*') { gotoCase = 9; continue; };
            if (yych != '/') { gotoCase = 11; continue; };
case 7:
            ++cursor;
            this.setLexCondition(this._lexConditions.INITIAL);
            { this.tokenType = "css-comment"; return cursor; }
case 9:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '*') { gotoCase = 9; continue; };
            if (yych == '/') { gotoCase = 7; continue; };
case 11:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 12:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 2; continue; };
                { gotoCase = 11; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 2; continue; };
                if (yych == '*') { gotoCase = 9; continue; };
                { gotoCase = 11; continue; };
            }
/* *********************************** */
case this.case_DSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 17; continue; };
                if (yych <= '\f') { gotoCase = 16; continue; };
                { gotoCase = 17; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 16; continue; };
                    { gotoCase = 19; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 21; continue; };
                    { gotoCase = 16; continue; };
                }
            }
case 15:
            { return this._stringToken(cursor); }
case 16:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 23; continue; };
case 17:
            ++cursor;
case 18:
            { this.tokenType = null; return cursor; }
case 19:
            ++cursor;
case 20:
            this.setLexCondition(this._lexConditions.INITIAL);
            { return this._stringToken(cursor, true); }
case 21:
            yych = this._charAt(++cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 22; continue; };
                    if (yych <= '&') { gotoCase = 18; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 18; continue; };
                    } else {
                        if (yych != 'b') { gotoCase = 18; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych >= 'g') { gotoCase = 18; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 22; continue; };
                        if (yych <= 'q') { gotoCase = 18; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 18; continue; };
                    } else {
                        if (yych != 'v') { gotoCase = 18; continue; };
                    }
                }
            }
case 22:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 23:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 15; continue; };
                if (yych <= '\f') { gotoCase = 22; continue; };
                { gotoCase = 15; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 22; continue; };
                    { gotoCase = 26; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 22; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 22; continue; };
                    if (yych >= '\'') { gotoCase = 22; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych >= '\\') { gotoCase = 22; continue; };
                    } else {
                        if (yych == 'b') { gotoCase = 22; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych <= 'f') { gotoCase = 22; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 22; continue; };
                        if (yych >= 'r') { gotoCase = 22; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych >= 't') { gotoCase = 22; continue; };
                    } else {
                        if (yych == 'v') { gotoCase = 22; continue; };
                    }
                }
            }
            cursor = YYMARKER;
            { gotoCase = 15; continue; };
case 26:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 20; continue; };
/* *********************************** */
case this.case_INITIAL:
            yych = this._charAt(cursor);
            if (yych <= ';') {
                if (yych <= '\'') {
                    if (yych <= '"') {
                        if (yych <= ' ') { gotoCase = 29; continue; };
                        if (yych <= '!') { gotoCase = 31; continue; };
                        { gotoCase = 33; continue; };
                    } else {
                        if (yych == '$') { gotoCase = 31; continue; };
                        if (yych >= '\'') { gotoCase = 34; continue; };
                    }
                } else {
                    if (yych <= '.') {
                        if (yych <= ',') { gotoCase = 29; continue; };
                        if (yych <= '-') { gotoCase = 35; continue; };
                        { gotoCase = 36; continue; };
                    } else {
                        if (yych <= '/') { gotoCase = 37; continue; };
                        if (yych <= '9') { gotoCase = 38; continue; };
                        if (yych <= ':') { gotoCase = 40; continue; };
                        { gotoCase = 42; continue; };
                    }
                }
            } else {
                if (yych <= '^') {
                    if (yych <= '?') {
                        if (yych == '=') { gotoCase = 31; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 29; continue; };
                        if (yych <= ']') { gotoCase = 31; continue; };
                    }
                } else {
                    if (yych <= 'z') {
                        if (yych != '`') { gotoCase = 31; continue; };
                    } else {
                        if (yych <= '{') { gotoCase = 44; continue; };
                        if (yych == '}') { gotoCase = 46; continue; };
                    }
                }
            }
case 29:
            ++cursor;
case 30:
            { this.tokenType = null; return cursor; }
case 31:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 49; continue; };
case 32:
            {
                    var token = this._line.substring(cursorOnEnter, cursor);
                    if (this._condition.parseCondition === this._parseConditions.INITIAL) {
                        if (token === "@import" || token === "@media") {
                            this.tokenType = "css-at-rule";
                            this._condition.parseCondition = this._parseConditions.AT_RULE;
                        } else if (token.indexOf("@") === 0)
                            this.tokenType = "css-at-rule";
                        else
                            this.tokenType = "css-selector";
                    }
                    else if (this._condition.parseCondition === this._parseConditions.AT_RULE && token in this._mediaTypes)
                        this.tokenType = "css-keyword";
                    else if (this._condition.parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords)
                        this.tokenType = "css-property";
                    else if (this._isPropertyValue() && token in this._valueKeywords)
                        this.tokenType = "css-keyword";
                    else if (token === "!important")
                        this.tokenType = "css-important";
                    else
                        this.tokenType = null;
                    return cursor;
                }
case 33:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '-') {
                if (yych <= '!') {
                    if (yych <= '\f') {
                        if (yych == '\n') { gotoCase = 32; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych <= '\r') { gotoCase = 32; continue; };
                        if (yych <= ' ') { gotoCase = 124; continue; };
                        { gotoCase = 122; continue; };
                    }
                } else {
                    if (yych <= '$') {
                        if (yych <= '"') { gotoCase = 114; continue; };
                        if (yych <= '#') { gotoCase = 124; continue; };
                        { gotoCase = 122; continue; };
                    } else {
                        if (yych == '\'') { gotoCase = 122; continue; };
                        if (yych <= ',') { gotoCase = 124; continue; };
                        { gotoCase = 122; continue; };
                    }
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '<') {
                        if (yych <= '.') { gotoCase = 124; continue; };
                        if (yych <= '9') { gotoCase = 122; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych <= '=') { gotoCase = 122; continue; };
                        if (yych <= '?') { gotoCase = 124; continue; };
                        { gotoCase = 122; continue; };
                    }
                } else {
                    if (yych <= '^') {
                        if (yych <= '\\') { gotoCase = 126; continue; };
                        if (yych <= ']') { gotoCase = 122; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych == '`') { gotoCase = 124; continue; };
                        if (yych <= 'z') { gotoCase = 122; continue; };
                        { gotoCase = 124; continue; };
                    }
                }
            }
case 34:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '-') {
                if (yych <= '"') {
                    if (yych <= '\f') {
                        if (yych == '\n') { gotoCase = 32; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '\r') { gotoCase = 32; continue; };
                        if (yych <= ' ') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                } else {
                    if (yych <= '&') {
                        if (yych == '$') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '\'') { gotoCase = 114; continue; };
                        if (yych <= ',') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '<') {
                        if (yych <= '.') { gotoCase = 116; continue; };
                        if (yych <= '9') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '=') { gotoCase = 112; continue; };
                        if (yych <= '?') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                } else {
                    if (yych <= '^') {
                        if (yych <= '\\') { gotoCase = 118; continue; };
                        if (yych <= ']') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych == '`') { gotoCase = 116; continue; };
                        if (yych <= 'z') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    }
                }
            }
case 35:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '.') { gotoCase = 65; continue; };
            if (yych <= '/') { gotoCase = 49; continue; };
            if (yych <= '9') { gotoCase = 50; continue; };
            { gotoCase = 49; continue; };
case 36:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 30; continue; };
            if (yych <= '9') { gotoCase = 68; continue; };
            { gotoCase = 30; continue; };
case 37:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '*') { gotoCase = 104; continue; };
            { gotoCase = 49; continue; };
case 38:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            switch (yych) {
            case '!':
            case '"':
            case '$':
            case '\'':
            case '-':
            case '/':
            case '=':
            case '@':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '[':
            case ']':
            case 'a':
            case 'b':
            case 'f':
            case 'h':
            case 'j':
            case 'l':
            case 'n':
            case 'o':
            case 'q':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':    { gotoCase = 48; continue; };
            case '%':    { gotoCase = 67; continue; };
            case '.':    { gotoCase = 65; continue; };
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':    { gotoCase = 50; continue; };
            case 'H':    { gotoCase = 52; continue; };
            case '_':    { gotoCase = 53; continue; };
            case 'c':    { gotoCase = 54; continue; };
            case 'd':    { gotoCase = 55; continue; };
            case 'e':    { gotoCase = 56; continue; };
            case 'g':    { gotoCase = 57; continue; };
            case 'i':    { gotoCase = 58; continue; };
            case 'k':    { gotoCase = 59; continue; };
            case 'm':    { gotoCase = 60; continue; };
            case 'p':    { gotoCase = 61; continue; };
            case 'r':    { gotoCase = 62; continue; };
            case 's':    { gotoCase = 63; continue; };
            case 't':    { gotoCase = 64; continue; };
            default:    { gotoCase = 39; continue; };
            }
case 39:
            {
                    if (this._isPropertyValue())
                        this.tokenType = "css-number";
                    else
                        this.tokenType = null;
                    return cursor;
                }
case 40:
            ++cursor;
            {
                    this.tokenType = null;
                    if (this._condition.parseCondition === this._parseConditions.PROPERTY)
                        this._condition.parseCondition = this._parseConditions.PROPERTY_VALUE;
                    return cursor;
                }
case 42:
            ++cursor;
            {
                    this.tokenType = null;
                    if (this._condition.parseCondition === this._parseConditions.AT_RULE)
                        this._condition.parseCondition = this._parseConditions.INITIAL;
                    else
                        this._condition.parseCondition = this._parseConditions.PROPERTY;
                    return cursor;
                }
case 44:
            ++cursor;
            {
                    this.tokenType = null;
                    if (this._condition.parseCondition === this._parseConditions.AT_RULE)
                        this._condition.parseCondition = this._parseConditions.INITIAL;
                    else
                        this._condition.parseCondition = this._parseConditions.PROPERTY;
                    return cursor;
                }
case 46:
            ++cursor;
            {
                    this.tokenType = null;
                    this._condition.parseCondition = this._parseConditions.INITIAL;
                    return cursor;
                }
case 48:
            ++cursor;
            yych = this._charAt(cursor);
case 49:
            if (yych <= '9') {
                if (yych <= '&') {
                    if (yych <= '"') {
                        if (yych <= ' ') { gotoCase = 32; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych == '$') { gotoCase = 48; continue; };
                        { gotoCase = 32; continue; };
                    }
                } else {
                    if (yych <= ',') {
                        if (yych <= '\'') { gotoCase = 48; continue; };
                        { gotoCase = 32; continue; };
                    } else {
                        if (yych == '.') { gotoCase = 32; continue; };
                        { gotoCase = 48; continue; };
                    }
                }
            } else {
                if (yych <= '\\') {
                    if (yych <= '=') {
                        if (yych <= '<') { gotoCase = 32; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '?') { gotoCase = 32; continue; };
                        if (yych <= '[') { gotoCase = 48; continue; };
                        { gotoCase = 32; continue; };
                    }
                } else {
                    if (yych <= '_') {
                        if (yych == '^') { gotoCase = 32; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '`') { gotoCase = 32; continue; };
                        if (yych <= 'z') { gotoCase = 48; continue; };
                        { gotoCase = 32; continue; };
                    }
                }
            }
case 50:
            yyaccept = 1;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            switch (yych) {
            case '!':
            case '"':
            case '$':
            case '\'':
            case '-':
            case '/':
            case '=':
            case '@':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '[':
            case ']':
            case 'a':
            case 'b':
            case 'f':
            case 'h':
            case 'j':
            case 'l':
            case 'n':
            case 'o':
            case 'q':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':    { gotoCase = 48; continue; };
            case '%':    { gotoCase = 67; continue; };
            case '.':    { gotoCase = 65; continue; };
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':    { gotoCase = 50; continue; };
            case 'H':    { gotoCase = 52; continue; };
            case '_':    { gotoCase = 53; continue; };
            case 'c':    { gotoCase = 54; continue; };
            case 'd':    { gotoCase = 55; continue; };
            case 'e':    { gotoCase = 56; continue; };
            case 'g':    { gotoCase = 57; continue; };
            case 'i':    { gotoCase = 58; continue; };
            case 'k':    { gotoCase = 59; continue; };
            case 'm':    { gotoCase = 60; continue; };
            case 'p':    { gotoCase = 61; continue; };
            case 'r':    { gotoCase = 62; continue; };
            case 's':    { gotoCase = 63; continue; };
            case 't':    { gotoCase = 64; continue; };
            default:    { gotoCase = 39; continue; };
            }
case 52:
            yych = this._charAt(++cursor);
            if (yych == 'z') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 53:
            yych = this._charAt(++cursor);
            if (yych == '_') { gotoCase = 101; continue; };
            { gotoCase = 49; continue; };
case 54:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 55:
            yych = this._charAt(++cursor);
            if (yych == 'e') { gotoCase = 100; continue; };
            { gotoCase = 49; continue; };
case 56:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 63; continue; };
            if (yych == 'x') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 57:
            yych = this._charAt(++cursor);
            if (yych == 'r') { gotoCase = 98; continue; };
            { gotoCase = 49; continue; };
case 58:
            yych = this._charAt(++cursor);
            if (yych == 'n') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 59:
            yych = this._charAt(++cursor);
            if (yych == 'H') { gotoCase = 97; continue; };
            { gotoCase = 49; continue; };
case 60:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 63; continue; };
            if (yych == 's') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 61:
            yych = this._charAt(++cursor);
            if (yych <= 's') {
                if (yych == 'c') { gotoCase = 63; continue; };
                { gotoCase = 49; continue; };
            } else {
                if (yych <= 't') { gotoCase = 63; continue; };
                if (yych == 'x') { gotoCase = 63; continue; };
                { gotoCase = 49; continue; };
            }
case 62:
            yych = this._charAt(++cursor);
            if (yych == 'a') { gotoCase = 95; continue; };
            if (yych == 'e') { gotoCase = 96; continue; };
            { gotoCase = 49; continue; };
case 63:
            yych = this._charAt(++cursor);
            if (yych <= '9') {
                if (yych <= '&') {
                    if (yych <= '"') {
                        if (yych <= ' ') { gotoCase = 39; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych == '$') { gotoCase = 48; continue; };
                        { gotoCase = 39; continue; };
                    }
                } else {
                    if (yych <= ',') {
                        if (yych <= '\'') { gotoCase = 48; continue; };
                        { gotoCase = 39; continue; };
                    } else {
                        if (yych == '.') { gotoCase = 39; continue; };
                        { gotoCase = 48; continue; };
                    }
                }
            } else {
                if (yych <= '\\') {
                    if (yych <= '=') {
                        if (yych <= '<') { gotoCase = 39; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '?') { gotoCase = 39; continue; };
                        if (yych <= '[') { gotoCase = 48; continue; };
                        { gotoCase = 39; continue; };
                    }
                } else {
                    if (yych <= '_') {
                        if (yych == '^') { gotoCase = 39; continue; };
                        { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '`') { gotoCase = 39; continue; };
                        if (yych <= 'z') { gotoCase = 48; continue; };
                        { gotoCase = 39; continue; };
                    }
                }
            }
case 64:
            yych = this._charAt(++cursor);
            if (yych == 'u') { gotoCase = 93; continue; };
            { gotoCase = 49; continue; };
case 65:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 66; continue; };
            if (yych <= '9') { gotoCase = 68; continue; };
case 66:
            cursor = YYMARKER;
            if (yyaccept <= 0) {
                { gotoCase = 32; continue; };
            } else {
                { gotoCase = 39; continue; };
            }
case 67:
            yych = this._charAt(++cursor);
            { gotoCase = 39; continue; };
case 68:
            yyaccept = 1;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'f') {
                if (yych <= 'H') {
                    if (yych <= '/') {
                        if (yych == '%') { gotoCase = 67; continue; };
                        { gotoCase = 39; continue; };
                    } else {
                        if (yych <= '9') { gotoCase = 68; continue; };
                        if (yych <= 'G') { gotoCase = 39; continue; };
                        { gotoCase = 80; continue; };
                    }
                } else {
                    if (yych <= 'b') {
                        if (yych == '_') { gotoCase = 72; continue; };
                        { gotoCase = 39; continue; };
                    } else {
                        if (yych <= 'c') { gotoCase = 74; continue; };
                        if (yych <= 'd') { gotoCase = 77; continue; };
                        if (yych >= 'f') { gotoCase = 39; continue; };
                    }
                }
            } else {
                if (yych <= 'm') {
                    if (yych <= 'i') {
                        if (yych <= 'g') { gotoCase = 78; continue; };
                        if (yych <= 'h') { gotoCase = 39; continue; };
                        { gotoCase = 76; continue; };
                    } else {
                        if (yych == 'k') { gotoCase = 81; continue; };
                        if (yych <= 'l') { gotoCase = 39; continue; };
                        { gotoCase = 75; continue; };
                    }
                } else {
                    if (yych <= 'q') {
                        if (yych == 'p') { gotoCase = 73; continue; };
                        { gotoCase = 39; continue; };
                    } else {
                        if (yych <= 'r') { gotoCase = 71; continue; };
                        if (yych <= 's') { gotoCase = 67; continue; };
                        if (yych <= 't') { gotoCase = 79; continue; };
                        { gotoCase = 39; continue; };
                    }
                }
            }
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 67; continue; };
            if (yych == 'x') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 71:
            yych = this._charAt(++cursor);
            if (yych == 'a') { gotoCase = 91; continue; };
            if (yych == 'e') { gotoCase = 92; continue; };
            { gotoCase = 66; continue; };
case 72:
            yych = this._charAt(++cursor);
            if (yych == '_') { gotoCase = 88; continue; };
            { gotoCase = 66; continue; };
case 73:
            yych = this._charAt(++cursor);
            if (yych <= 's') {
                if (yych == 'c') { gotoCase = 67; continue; };
                { gotoCase = 66; continue; };
            } else {
                if (yych <= 't') { gotoCase = 67; continue; };
                if (yych == 'x') { gotoCase = 67; continue; };
                { gotoCase = 66; continue; };
            }
case 74:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 75:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 67; continue; };
            if (yych == 's') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 76:
            yych = this._charAt(++cursor);
            if (yych == 'n') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 77:
            yych = this._charAt(++cursor);
            if (yych == 'e') { gotoCase = 87; continue; };
            { gotoCase = 66; continue; };
case 78:
            yych = this._charAt(++cursor);
            if (yych == 'r') { gotoCase = 85; continue; };
            { gotoCase = 66; continue; };
case 79:
            yych = this._charAt(++cursor);
            if (yych == 'u') { gotoCase = 83; continue; };
            { gotoCase = 66; continue; };
case 80:
            yych = this._charAt(++cursor);
            if (yych == 'z') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 81:
            yych = this._charAt(++cursor);
            if (yych != 'H') { gotoCase = 66; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'z') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 83:
            yych = this._charAt(++cursor);
            if (yych != 'r') { gotoCase = 66; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'n') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 85:
            yych = this._charAt(++cursor);
            if (yych != 'a') { gotoCase = 66; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'd') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 87:
            yych = this._charAt(++cursor);
            if (yych == 'g') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 88:
            yych = this._charAt(++cursor);
            if (yych != 'q') { gotoCase = 66; continue; };
            yych = this._charAt(++cursor);
            if (yych != 'e') { gotoCase = 66; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 91:
            yych = this._charAt(++cursor);
            if (yych == 'd') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 92:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 67; continue; };
            { gotoCase = 66; continue; };
case 93:
            yych = this._charAt(++cursor);
            if (yych != 'r') { gotoCase = 49; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'n') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 95:
            yych = this._charAt(++cursor);
            if (yych == 'd') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 96:
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 97:
            yych = this._charAt(++cursor);
            if (yych == 'z') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 98:
            yych = this._charAt(++cursor);
            if (yych != 'a') { gotoCase = 49; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'd') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 100:
            yych = this._charAt(++cursor);
            if (yych == 'g') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 101:
            yych = this._charAt(++cursor);
            if (yych != 'q') { gotoCase = 49; continue; };
            yych = this._charAt(++cursor);
            if (yych != 'e') { gotoCase = 49; continue; };
            yych = this._charAt(++cursor);
            if (yych == 'm') { gotoCase = 63; continue; };
            { gotoCase = 49; continue; };
case 104:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 108; continue; };
                { gotoCase = 104; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 108; continue; };
                if (yych != '*') { gotoCase = 104; continue; };
            }
case 106:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '*') { gotoCase = 106; continue; };
            if (yych == '/') { gotoCase = 110; continue; };
            { gotoCase = 104; continue; };
case 108:
            ++cursor;
            this.setLexCondition(this._lexConditions.COMMENT);
            { this.tokenType = "css-comment"; return cursor; }
case 110:
            ++cursor;
            { this.tokenType = "css-comment"; return cursor; }
case 112:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '-') {
                if (yych <= '"') {
                    if (yych <= '\f') {
                        if (yych == '\n') { gotoCase = 32; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '\r') { gotoCase = 32; continue; };
                        if (yych <= ' ') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                } else {
                    if (yych <= '&') {
                        if (yych == '$') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '\'') { gotoCase = 114; continue; };
                        if (yych <= ',') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '<') {
                        if (yych <= '.') { gotoCase = 116; continue; };
                        if (yych <= '9') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych <= '=') { gotoCase = 112; continue; };
                        if (yych <= '?') { gotoCase = 116; continue; };
                        { gotoCase = 112; continue; };
                    }
                } else {
                    if (yych <= '^') {
                        if (yych <= '\\') { gotoCase = 118; continue; };
                        if (yych <= ']') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych == '`') { gotoCase = 116; continue; };
                        if (yych <= 'z') { gotoCase = 112; continue; };
                        { gotoCase = 116; continue; };
                    }
                }
            }
case 114:
            ++cursor;
            if ((yych = this._charAt(cursor)) <= '9') {
                if (yych <= '&') {
                    if (yych <= '"') {
                        if (yych >= '!') { gotoCase = 48; continue; };
                    } else {
                        if (yych == '$') { gotoCase = 48; continue; };
                    }
                } else {
                    if (yych <= ',') {
                        if (yych <= '\'') { gotoCase = 48; continue; };
                    } else {
                        if (yych != '.') { gotoCase = 48; continue; };
                    }
                }
            } else {
                if (yych <= '\\') {
                    if (yych <= '=') {
                        if (yych >= '=') { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '?') { gotoCase = 115; continue; };
                        if (yych <= '[') { gotoCase = 48; continue; };
                    }
                } else {
                    if (yych <= '_') {
                        if (yych != '^') { gotoCase = 48; continue; };
                    } else {
                        if (yych <= '`') { gotoCase = 115; continue; };
                        if (yych <= 'z') { gotoCase = 48; continue; };
                    }
                }
            }
case 115:
            { return this._stringToken(cursor, true); }
case 116:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 66; continue; };
                if (yych <= '\f') { gotoCase = 116; continue; };
                { gotoCase = 66; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 116; continue; };
                    { gotoCase = 121; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 116; continue; };
                }
            }
case 118:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 66; continue; };
                    } else {
                        if (yych != '\r') { gotoCase = 66; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 116; continue; };
                        if (yych <= '&') { gotoCase = 66; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 116; continue; };
                        { gotoCase = 66; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 116; continue; };
                        if (yych <= 'e') { gotoCase = 66; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 116; continue; };
                        { gotoCase = 66; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 66; continue; };
                        { gotoCase = 116; continue; };
                    } else {
                        if (yych == 'v') { gotoCase = 116; continue; };
                        { gotoCase = 66; continue; };
                    }
                }
            }
            ++cursor;
            this.setLexCondition(this._lexConditions.SSTRING);
            { return this._stringToken(cursor); }
case 121:
            yych = this._charAt(++cursor);
            { gotoCase = 115; continue; };
case 122:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '-') {
                if (yych <= '!') {
                    if (yych <= '\f') {
                        if (yych == '\n') { gotoCase = 32; continue; };
                    } else {
                        if (yych <= '\r') { gotoCase = 32; continue; };
                        if (yych >= '!') { gotoCase = 122; continue; };
                    }
                } else {
                    if (yych <= '$') {
                        if (yych <= '"') { gotoCase = 114; continue; };
                        if (yych >= '$') { gotoCase = 122; continue; };
                    } else {
                        if (yych == '\'') { gotoCase = 122; continue; };
                        if (yych >= '-') { gotoCase = 122; continue; };
                    }
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '<') {
                        if (yych <= '.') { gotoCase = 124; continue; };
                        if (yych <= '9') { gotoCase = 122; continue; };
                    } else {
                        if (yych <= '=') { gotoCase = 122; continue; };
                        if (yych >= '@') { gotoCase = 122; continue; };
                    }
                } else {
                    if (yych <= '^') {
                        if (yych <= '\\') { gotoCase = 126; continue; };
                        if (yych <= ']') { gotoCase = 122; continue; };
                    } else {
                        if (yych == '`') { gotoCase = 124; continue; };
                        if (yych <= 'z') { gotoCase = 122; continue; };
                    }
                }
            }
case 124:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 66; continue; };
                if (yych <= '\f') { gotoCase = 124; continue; };
                { gotoCase = 66; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 124; continue; };
                    { gotoCase = 121; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 124; continue; };
                }
            }
case 126:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 66; continue; };
                    } else {
                        if (yych != '\r') { gotoCase = 66; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 124; continue; };
                        if (yych <= '&') { gotoCase = 66; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 124; continue; };
                        { gotoCase = 66; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 124; continue; };
                        if (yych <= 'e') { gotoCase = 66; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 124; continue; };
                        { gotoCase = 66; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 66; continue; };
                        { gotoCase = 124; continue; };
                    } else {
                        if (yych == 'v') { gotoCase = 124; continue; };
                        { gotoCase = 66; continue; };
                    }
                }
            }
            ++cursor;
            this.setLexCondition(this._lexConditions.DSTRING);
            { return this._stringToken(cursor); }
/* *********************************** */
case this.case_SSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 133; continue; };
                if (yych <= '\f') { gotoCase = 132; continue; };
                { gotoCase = 133; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 132; continue; };
                    { gotoCase = 135; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 137; continue; };
                    { gotoCase = 132; continue; };
                }
            }
case 131:
            { return this._stringToken(cursor); }
case 132:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 139; continue; };
case 133:
            ++cursor;
case 134:
            { this.tokenType = null; return cursor; }
case 135:
            ++cursor;
case 136:
            this.setLexCondition(this._lexConditions.INITIAL);
            { return this._stringToken(cursor, true); }
case 137:
            yych = this._charAt(++cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 138; continue; };
                    if (yych <= '&') { gotoCase = 134; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 134; continue; };
                    } else {
                        if (yych != 'b') { gotoCase = 134; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych >= 'g') { gotoCase = 134; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 138; continue; };
                        if (yych <= 'q') { gotoCase = 134; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 134; continue; };
                    } else {
                        if (yych != 'v') { gotoCase = 134; continue; };
                    }
                }
            }
case 138:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 139:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 131; continue; };
                if (yych <= '\f') { gotoCase = 138; continue; };
                { gotoCase = 131; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 138; continue; };
                    { gotoCase = 142; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 138; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 138; continue; };
                    if (yych >= '\'') { gotoCase = 138; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych >= '\\') { gotoCase = 138; continue; };
                    } else {
                        if (yych == 'b') { gotoCase = 138; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych <= 'f') { gotoCase = 138; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 138; continue; };
                        if (yych >= 'r') { gotoCase = 138; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych >= 't') { gotoCase = 138; continue; };
                    } else {
                        if (yych == 'v') { gotoCase = 138; continue; };
                    }
                }
            }
            cursor = YYMARKER;
            { gotoCase = 131; continue; };
case 142:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 136; continue; };
        }

        }
    }
}

WebInspector.SourceCSSTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype;

/* SourceHTMLTokenizer.js */

/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */
/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Generate js file as follows:
//
// re2c -isc WebCore/inspector/front-end/SourceHTMLTokenizer.re2js \
// | sed 's|^yy\([^:]*\)*\:|case \1:|' \
// | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \
// | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \
// | sed 's|[*]cursor|this._charAt(cursor)|' \
// | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \
// | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \
// | sed 's|unsigned\ int|var|' \
// | sed 's|var\ yych|case 1: case 1: var yych|'

WebInspector.SourceHTMLTokenizer = function()
{
    WebInspector.SourceTokenizer.call(this);

    // The order is determined by the generated code.
    this._lexConditions = {
        INITIAL: 0,
        COMMENT: 1,
        DOCTYPE: 2,
        TAG: 3,
        DSTRING: 4,
        SSTRING: 5
    };
    this.case_INITIAL = 1000;
    this.case_COMMENT = 1001;
    this.case_DOCTYPE = 1002;
    this.case_TAG = 1003;
    this.case_DSTRING = 1004;
    this.case_SSTRING = 1005;

    this._parseConditions = {
        INITIAL: 0,
        ATTRIBUTE: 1,
        ATTRIBUTE_VALUE: 2,
        LINKIFY: 4,
        A_NODE: 8,
        SCRIPT: 16
    };

    this.initialCondition = { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL };
    this.condition = this.initialCondition;
}

WebInspector.SourceHTMLTokenizer.prototype = {
    set line(line) {
        if (this._internalJavaScriptTokenizer) {
            var match = /<\/script/i.exec(line);
            if (match) {
                this._internalJavaScriptTokenizer.line = line.substring(0, match.index);
            } else
                this._internalJavaScriptTokenizer.line = line;
        }
        this._line = line;
    },

    _isExpectingAttribute: function()
    {
        return this._condition.parseCondition & this._parseConditions.ATTRIBUTE;
    },

    _isExpectingAttributeValue: function()
    {
        return this._condition.parseCondition & this._parseConditions.ATTRIBUTE_VALUE;
    },

    _setExpectingAttribute: function()
    {
        if (this._isExpectingAttributeValue())
            this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE;
        this._condition.parseCondition |= this._parseConditions.ATTRIBUTE;
    },

    _setExpectingAttributeValue: function()
    {
        if (this._isExpectingAttribute())
            this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE;
        this._condition.parseCondition |= this._parseConditions.ATTRIBUTE_VALUE;
    },

    _stringToken: function(cursor, stringEnds)
    {
        if (!this._isExpectingAttributeValue()) {
            this.tokenType = null;
            return cursor;
        }
        this.tokenType = this._attrValueTokenType();
        if (stringEnds)
            this._setExpectingAttribute();
        return cursor;
    },

    _attrValueTokenType: function()
    {
        if (this._condition.parseCondition & this._parseConditions.LINKIFY) {
            if (this._condition.parseCondition & this._parseConditions.A_NODE)
                return "html-external-link";
            return "html-resource-link";
        }
        return "html-attribute-value";
    },

    nextToken: function(cursor)
    {
        if (this._internalJavaScriptTokenizer) {
            // Re-set line to force </script> detection first.
            this.line = this._line;
            if (cursor !== this._internalJavaScriptTokenizer._line.length) {
                // Tokenizer is stateless, so restore its condition before tokenizing and save it after.
                this._internalJavaScriptTokenizer.condition = this._condition.internalJavaScriptTokenizerCondition;
                var result = this._internalJavaScriptTokenizer.nextToken(cursor);
                this.tokenType = this._internalJavaScriptTokenizer.tokenType;
                this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.condition;
                return result;
            } else if (cursor !== this._line.length)
                delete this._internalJavaScriptTokenizer;
        }

        var cursorOnEnter = cursor;
        var gotoCase = 1;
        while (1) {
            switch (gotoCase)
            // Following comment is replaced with generated state machine.
            
        {
            case 1: var yych;
            var yyaccept = 0;
            if (this.getLexCondition() < 3) {
                if (this.getLexCondition() < 1) {
                    { gotoCase = this.case_INITIAL; continue; };
                } else {
                    if (this.getLexCondition() < 2) {
                        { gotoCase = this.case_COMMENT; continue; };
                    } else {
                        { gotoCase = this.case_DOCTYPE; continue; };
                    }
                }
            } else {
                if (this.getLexCondition() < 4) {
                    { gotoCase = this.case_TAG; continue; };
                } else {
                    if (this.getLexCondition() < 5) {
                        { gotoCase = this.case_DSTRING; continue; };
                    } else {
                        { gotoCase = this.case_SSTRING; continue; };
                    }
                }
            }
/* *********************************** */
case this.case_COMMENT:

            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 4; continue; };
                { gotoCase = 3; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 4; continue; };
                if (yych == '-') { gotoCase = 6; continue; };
                { gotoCase = 3; continue; };
            }
case 2:
            { this.tokenType = "html-comment"; return cursor; }
case 3:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 9; continue; };
case 4:
            ++cursor;
case 5:
            { this.tokenType = null; return cursor; }
case 6:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych != '-') { gotoCase = 5; continue; };
case 7:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '>') { gotoCase = 10; continue; };
case 8:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 9:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 2; continue; };
                { gotoCase = 8; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 2; continue; };
                if (yych == '-') { gotoCase = 12; continue; };
                { gotoCase = 8; continue; };
            }
case 10:
            ++cursor;
            this.setLexCondition(this._lexConditions.INITIAL);
            { this.tokenType = "html-comment"; return cursor; }
case 12:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '-') { gotoCase = 7; continue; };
            cursor = YYMARKER;
            if (yyaccept <= 0) {
                { gotoCase = 2; continue; };
            } else {
                { gotoCase = 5; continue; };
            }
/* *********************************** */
case this.case_DOCTYPE:
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 18; continue; };
                { gotoCase = 17; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 18; continue; };
                if (yych == '>') { gotoCase = 20; continue; };
                { gotoCase = 17; continue; };
            }
case 16:
            { this.tokenType = "html-doctype"; return cursor; }
case 17:
            yych = this._charAt(++cursor);
            { gotoCase = 23; continue; };
case 18:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 20:
            ++cursor;
            this.setLexCondition(this._lexConditions.INITIAL);
            { this.tokenType = "html-doctype"; return cursor; }
case 22:
            ++cursor;
            yych = this._charAt(cursor);
case 23:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 16; continue; };
                { gotoCase = 22; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 16; continue; };
                if (yych == '>') { gotoCase = 16; continue; };
                { gotoCase = 22; continue; };
            }
/* *********************************** */
case this.case_DSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 28; continue; };
                { gotoCase = 27; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 28; continue; };
                if (yych == '"') { gotoCase = 30; continue; };
                { gotoCase = 27; continue; };
            }
case 26:
            { return this._stringToken(cursor); }
case 27:
            yych = this._charAt(++cursor);
            { gotoCase = 34; continue; };
case 28:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 30:
            ++cursor;
case 31:
            this.setLexCondition(this._lexConditions.TAG);
            { return this._stringToken(cursor, true); }
case 32:
            yych = this._charAt(++cursor);
            { gotoCase = 31; continue; };
case 33:
            ++cursor;
            yych = this._charAt(cursor);
case 34:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 26; continue; };
                { gotoCase = 33; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 26; continue; };
                if (yych == '"') { gotoCase = 32; continue; };
                { gotoCase = 33; continue; };
            }
/* *********************************** */
case this.case_INITIAL:
            yych = this._charAt(cursor);
            if (yych == '<') { gotoCase = 39; continue; };
            ++cursor;
            { this.tokenType = null; return cursor; }
case 39:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '/') {
                if (yych == '!') { gotoCase = 44; continue; };
                if (yych >= '/') { gotoCase = 41; continue; };
            } else {
                if (yych <= 'S') {
                    if (yych >= 'S') { gotoCase = 42; continue; };
                } else {
                    if (yych == 's') { gotoCase = 42; continue; };
                }
            }
case 40:
            this.setLexCondition(this._lexConditions.TAG);
            {
                    if (this._condition.parseCondition & this._parseConditions.SCRIPT) {
                        // Do not tokenize script tag contents, keep lexer state although processing "<".
                        this.setLexCondition(this._lexConditions.INITIAL);
                        this.tokenType = null;
                        return cursor;
                    }

                    this._condition.parseCondition = this._parseConditions.INITIAL;
                    this.tokenType = "html-tag";
                    return cursor;
                }
case 41:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == 'S') { gotoCase = 68; continue; };
            if (yych == 's') { gotoCase = 68; continue; };
            { gotoCase = 40; continue; };
case 42:
            yych = this._charAt(++cursor);
            if (yych == 'C') { gotoCase = 62; continue; };
            if (yych == 'c') { gotoCase = 62; continue; };
case 43:
            cursor = YYMARKER;
            { gotoCase = 40; continue; };
case 44:
            yych = this._charAt(++cursor);
            if (yych <= 'C') {
                if (yych != '-') { gotoCase = 43; continue; };
            } else {
                if (yych <= 'D') { gotoCase = 46; continue; };
                if (yych == 'd') { gotoCase = 46; continue; };
                { gotoCase = 43; continue; };
            }
            yych = this._charAt(++cursor);
            if (yych == '-') { gotoCase = 54; continue; };
            { gotoCase = 43; continue; };
case 46:
            yych = this._charAt(++cursor);
            if (yych == 'O') { gotoCase = 47; continue; };
            if (yych != 'o') { gotoCase = 43; continue; };
case 47:
            yych = this._charAt(++cursor);
            if (yych == 'C') { gotoCase = 48; continue; };
            if (yych != 'c') { gotoCase = 43; continue; };
case 48:
            yych = this._charAt(++cursor);
            if (yych == 'T') { gotoCase = 49; continue; };
            if (yych != 't') { gotoCase = 43; continue; };
case 49:
            yych = this._charAt(++cursor);
            if (yych == 'Y') { gotoCase = 50; continue; };
            if (yych != 'y') { gotoCase = 43; continue; };
case 50:
            yych = this._charAt(++cursor);
            if (yych == 'P') { gotoCase = 51; continue; };
            if (yych != 'p') { gotoCase = 43; continue; };
case 51:
            yych = this._charAt(++cursor);
            if (yych == 'E') { gotoCase = 52; continue; };
            if (yych != 'e') { gotoCase = 43; continue; };
case 52:
            ++cursor;
            this.setLexCondition(this._lexConditions.DOCTYPE);
            { this.tokenType = "html-doctype"; return cursor; }
case 54:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 57; continue; };
                { gotoCase = 54; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 57; continue; };
                if (yych != '-') { gotoCase = 54; continue; };
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '-') { gotoCase = 59; continue; };
            { gotoCase = 43; continue; };
case 57:
            ++cursor;
            this.setLexCondition(this._lexConditions.COMMENT);
            { this.tokenType = "html-comment"; return cursor; }
case 59:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych != '>') { gotoCase = 54; continue; };
            ++cursor;
            { this.tokenType = "html-comment"; return cursor; }
case 62:
            yych = this._charAt(++cursor);
            if (yych == 'R') { gotoCase = 63; continue; };
            if (yych != 'r') { gotoCase = 43; continue; };
case 63:
            yych = this._charAt(++cursor);
            if (yych == 'I') { gotoCase = 64; continue; };
            if (yych != 'i') { gotoCase = 43; continue; };
case 64:
            yych = this._charAt(++cursor);
            if (yych == 'P') { gotoCase = 65; continue; };
            if (yych != 'p') { gotoCase = 43; continue; };
case 65:
            yych = this._charAt(++cursor);
            if (yych == 'T') { gotoCase = 66; continue; };
            if (yych != 't') { gotoCase = 43; continue; };
case 66:
            ++cursor;
            this.setLexCondition(this._lexConditions.TAG);
            {
                    if (this._condition.parseCondition & this._parseConditions.SCRIPT) {
                        // Do not tokenize script tag contents, keep lexer state although processing "<".
                        this.setLexCondition(this._lexConditions.INITIAL);
                        this.tokenType = null;
                        return cursor;
                    }
                    this.tokenType = "html-tag";
                    this._condition.parseCondition = this._parseConditions.SCRIPT;
                    this._setExpectingAttribute();
                    return cursor;
                }
case 68:
            yych = this._charAt(++cursor);
            if (yych == 'C') { gotoCase = 69; continue; };
            if (yych != 'c') { gotoCase = 43; continue; };
case 69:
            yych = this._charAt(++cursor);
            if (yych == 'R') { gotoCase = 70; continue; };
            if (yych != 'r') { gotoCase = 43; continue; };
case 70:
            yych = this._charAt(++cursor);
            if (yych == 'I') { gotoCase = 71; continue; };
            if (yych != 'i') { gotoCase = 43; continue; };
case 71:
            yych = this._charAt(++cursor);
            if (yych == 'P') { gotoCase = 72; continue; };
            if (yych != 'p') { gotoCase = 43; continue; };
case 72:
            yych = this._charAt(++cursor);
            if (yych == 'T') { gotoCase = 73; continue; };
            if (yych != 't') { gotoCase = 43; continue; };
case 73:
            ++cursor;
            this.setLexCondition(this._lexConditions.TAG);
            {
                    this.tokenType = "html-tag";
                    this._condition.parseCondition = this._parseConditions.INITIAL;
                    return cursor;
                }
/* *********************************** */
case this.case_SSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 79; continue; };
                { gotoCase = 78; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 79; continue; };
                if (yych == '\'') { gotoCase = 81; continue; };
                { gotoCase = 78; continue; };
            }
case 77:
            { return this._stringToken(cursor); }
case 78:
            yych = this._charAt(++cursor);
            { gotoCase = 85; continue; };
case 79:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 81:
            ++cursor;
case 82:
            this.setLexCondition(this._lexConditions.TAG);
            { return this._stringToken(cursor, true); }
case 83:
            yych = this._charAt(++cursor);
            { gotoCase = 82; continue; };
case 84:
            ++cursor;
            yych = this._charAt(cursor);
case 85:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 77; continue; };
                { gotoCase = 84; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 77; continue; };
                if (yych == '\'') { gotoCase = 83; continue; };
                { gotoCase = 84; continue; };
            }
/* *********************************** */
case this.case_TAG:
            yych = this._charAt(cursor);
            if (yych <= '&') {
                if (yych <= '\r') {
                    if (yych == '\n') { gotoCase = 90; continue; };
                    if (yych >= '\r') { gotoCase = 90; continue; };
                } else {
                    if (yych <= ' ') {
                        if (yych >= ' ') { gotoCase = 90; continue; };
                    } else {
                        if (yych == '"') { gotoCase = 92; continue; };
                    }
                }
            } else {
                if (yych <= '>') {
                    if (yych <= ';') {
                        if (yych <= '\'') { gotoCase = 93; continue; };
                    } else {
                        if (yych <= '<') { gotoCase = 90; continue; };
                        if (yych <= '=') { gotoCase = 94; continue; };
                        { gotoCase = 96; continue; };
                    }
                } else {
                    if (yych <= '[') {
                        if (yych >= '[') { gotoCase = 90; continue; };
                    } else {
                        if (yych == ']') { gotoCase = 90; continue; };
                    }
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 109; continue; };
case 89:
            {
                    if (this._condition.parseCondition === this._parseConditions.SCRIPT) {
                        // Fall through if expecting attributes.
                        this.tokenType = null;
                        return cursor;
                    }

                    if (this._condition.parseCondition === this._parseConditions.INITIAL) {
                        this.tokenType = "html-tag";
                        this._setExpectingAttribute();
                        var token = this._line.substring(cursorOnEnter, cursor);
                        if (token === "a")
                            this._condition.parseCondition |= this._parseConditions.A_NODE;
                        else if (this._condition.parseCondition & this._parseConditions.A_NODE)
                            this._condition.parseCondition ^= this._parseConditions.A_NODE;
                    } else if (this._isExpectingAttribute()) {
                        var token = this._line.substring(cursorOnEnter, cursor);
                        if (token === "href" || token === "src")
                            this._condition.parseCondition |= this._parseConditions.LINKIFY;
                        else if (this._condition.parseCondition |= this._parseConditions.LINKIFY)
                            this._condition.parseCondition ^= this._parseConditions.LINKIFY;
                        this.tokenType = "html-attribute-name";
                    } else if (this._isExpectingAttributeValue())
                        this.tokenType = this._attrValueTokenType();
                    else
                        this.tokenType = null;
                    return cursor;
                }
case 90:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 92:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 105; continue; };
case 93:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 99; continue; };
case 94:
            ++cursor;
            {
                    if (this._isExpectingAttribute())
                        this._setExpectingAttributeValue();
                    this.tokenType = null;
                    return cursor;
                }
case 96:
            ++cursor;
            this.setLexCondition(this._lexConditions.INITIAL);
            {
                    this.tokenType = "html-tag";
                    if (this._condition.parseCondition & this._parseConditions.SCRIPT) {
                        if (!this._internalJavaScriptTokenizer) {
                            this._internalJavaScriptTokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/javascript");
                            this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.initialCondition;
                        }
                        // Do not tokenize script tag contents.
                        return cursor;
                    }

                    this._condition.parseCondition = this._parseConditions.INITIAL;
                    return cursor;
                }
case 98:
            ++cursor;
            yych = this._charAt(cursor);
case 99:
            if (yych <= '\f') {
                if (yych != '\n') { gotoCase = 98; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 100; continue; };
                if (yych == '\'') { gotoCase = 102; continue; };
                { gotoCase = 98; continue; };
            }
case 100:
            ++cursor;
            this.setLexCondition(this._lexConditions.SSTRING);
            { return this._stringToken(cursor); }
case 102:
            ++cursor;
            { return this._stringToken(cursor, true); }
case 104:
            ++cursor;
            yych = this._charAt(cursor);
case 105:
            if (yych <= '\f') {
                if (yych != '\n') { gotoCase = 104; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 106; continue; };
                if (yych == '"') { gotoCase = 102; continue; };
                { gotoCase = 104; continue; };
            }
case 106:
            ++cursor;
            this.setLexCondition(this._lexConditions.DSTRING);
            { return this._stringToken(cursor); }
case 108:
            ++cursor;
            yych = this._charAt(cursor);
case 109:
            if (yych <= '"') {
                if (yych <= '\r') {
                    if (yych == '\n') { gotoCase = 89; continue; };
                    if (yych <= '\f') { gotoCase = 108; continue; };
                    { gotoCase = 89; continue; };
                } else {
                    if (yych == ' ') { gotoCase = 89; continue; };
                    if (yych <= '!') { gotoCase = 108; continue; };
                    { gotoCase = 89; continue; };
                }
            } else {
                if (yych <= '>') {
                    if (yych == '\'') { gotoCase = 89; continue; };
                    if (yych <= ';') { gotoCase = 108; continue; };
                    { gotoCase = 89; continue; };
                } else {
                    if (yych <= '[') {
                        if (yych <= 'Z') { gotoCase = 108; continue; };
                        { gotoCase = 89; continue; };
                    } else {
                        if (yych == ']') { gotoCase = 89; continue; };
                        { gotoCase = 108; continue; };
                    }
                }
            }
        }

        }
    }
}

WebInspector.SourceHTMLTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype;

/* SourceJavaScriptTokenizer.js */

/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */
/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Generate js file as follows:
//
// re2c -isc WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js \
// | sed 's|^yy\([^:]*\)*\:|case \1:|' \
// | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \
// | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \
// | sed 's|[*]cursor|this._charAt(cursor)|' \
// | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \
// | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \
// | sed 's|unsigned\ int|var|' \
// | sed 's|var\ yych|case 1: case 1: var yych|'

WebInspector.SourceJavaScriptTokenizer = function()
{
    WebInspector.SourceTokenizer.call(this);

    this._keywords = [
        "null", "true", "false", "break", "case", "catch", "const", "default", "finally", "for",
        "instanceof", "new", "var", "continue", "function", "return", "void", "delete", "if",
        "this", "do", "while", "else", "in", "switch", "throw", "try", "typeof", "debugger",
        "class", "enum", "export", "extends", "import", "super", "get", "set", "with"
    ].keySet();

    this._lexConditions = {
        DIV: 0,
        NODIV: 1,
        COMMENT: 2,
        DSTRING: 3,
        SSTRING: 4,
        REGEX: 5
    };

    this.case_DIV = 1000;
    this.case_NODIV = 1001;
    this.case_COMMENT = 1002;
    this.case_DSTRING = 1003;
    this.case_SSTRING = 1004;
    this.case_REGEX = 1005;

    this.initialCondition = { lexCondition: this._lexConditions.NODIV }
    this.condition = this.initialCondition;
}

WebInspector.SourceJavaScriptTokenizer.prototype = {
    nextToken: function(cursor)
    {
        var cursorOnEnter = cursor;
        var gotoCase = 1;
        while (1) {
            switch (gotoCase)
            // Following comment is replaced with generated state machine.
            
        {
            case 1: var yych;
            var yyaccept = 0;
            if (this.getLexCondition() < 3) {
                if (this.getLexCondition() < 1) {
                    { gotoCase = this.case_DIV; continue; };
                } else {
                    if (this.getLexCondition() < 2) {
                        { gotoCase = this.case_NODIV; continue; };
                    } else {
                        { gotoCase = this.case_COMMENT; continue; };
                    }
                }
            } else {
                if (this.getLexCondition() < 4) {
                    { gotoCase = this.case_DSTRING; continue; };
                } else {
                    if (this.getLexCondition() < 5) {
                        { gotoCase = this.case_SSTRING; continue; };
                    } else {
                        { gotoCase = this.case_REGEX; continue; };
                    }
                }
            }
/* *********************************** */
case this.case_COMMENT:

            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 4; continue; };
                { gotoCase = 3; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 4; continue; };
                if (yych == '*') { gotoCase = 6; continue; };
                { gotoCase = 3; continue; };
            }
case 2:
            { this.tokenType = "javascript-comment"; return cursor; }
case 3:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 12; continue; };
case 4:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 6:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '*') { gotoCase = 9; continue; };
            if (yych != '/') { gotoCase = 11; continue; };
case 7:
            ++cursor;
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = "javascript-comment"; return cursor; }
case 9:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '*') { gotoCase = 9; continue; };
            if (yych == '/') { gotoCase = 7; continue; };
case 11:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 12:
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 2; continue; };
                { gotoCase = 11; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 2; continue; };
                if (yych == '*') { gotoCase = 9; continue; };
                { gotoCase = 11; continue; };
            }
/* *********************************** */
case this.case_DIV:
            yych = this._charAt(cursor);
            if (yych <= '9') {
                if (yych <= '(') {
                    if (yych <= '#') {
                        if (yych <= ' ') { gotoCase = 15; continue; };
                        if (yych <= '!') { gotoCase = 17; continue; };
                        if (yych <= '"') { gotoCase = 19; continue; };
                    } else {
                        if (yych <= '%') {
                            if (yych <= '$') { gotoCase = 20; continue; };
                            { gotoCase = 22; continue; };
                        } else {
                            if (yych <= '&') { gotoCase = 23; continue; };
                            if (yych <= '\'') { gotoCase = 24; continue; };
                            { gotoCase = 25; continue; };
                        }
                    }
                } else {
                    if (yych <= ',') {
                        if (yych <= ')') { gotoCase = 26; continue; };
                        if (yych <= '*') { gotoCase = 28; continue; };
                        if (yych <= '+') { gotoCase = 29; continue; };
                        { gotoCase = 25; continue; };
                    } else {
                        if (yych <= '.') {
                            if (yych <= '-') { gotoCase = 30; continue; };
                            { gotoCase = 31; continue; };
                        } else {
                            if (yych <= '/') { gotoCase = 32; continue; };
                            if (yych <= '0') { gotoCase = 34; continue; };
                            { gotoCase = 36; continue; };
                        }
                    }
                }
            } else {
                if (yych <= '\\') {
                    if (yych <= '>') {
                        if (yych <= ';') { gotoCase = 25; continue; };
                        if (yych <= '<') { gotoCase = 37; continue; };
                        if (yych <= '=') { gotoCase = 38; continue; };
                        { gotoCase = 39; continue; };
                    } else {
                        if (yych <= '@') {
                            if (yych <= '?') { gotoCase = 25; continue; };
                        } else {
                            if (yych <= 'Z') { gotoCase = 20; continue; };
                            if (yych <= '[') { gotoCase = 25; continue; };
                            { gotoCase = 40; continue; };
                        }
                    }
                } else {
                    if (yych <= 'z') {
                        if (yych <= '^') {
                            if (yych <= ']') { gotoCase = 25; continue; };
                            { gotoCase = 41; continue; };
                        } else {
                            if (yych != '`') { gotoCase = 20; continue; };
                        }
                    } else {
                        if (yych <= '|') {
                            if (yych <= '{') { gotoCase = 25; continue; };
                            { gotoCase = 42; continue; };
                        } else {
                            if (yych <= '~') { gotoCase = 25; continue; };
                            if (yych >= 0x80) { gotoCase = 20; continue; };
                        }
                    }
                }
            }
case 15:
            ++cursor;
case 16:
            { this.tokenType = null; return cursor; }
case 17:
            ++cursor;
            if ((yych = this._charAt(cursor)) == '=') { gotoCase = 115; continue; };
case 18:
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = null; return cursor; }
case 19:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '\n') { gotoCase = 16; continue; };
            if (yych == '\r') { gotoCase = 16; continue; };
            { gotoCase = 107; continue; };
case 20:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 50; continue; };
case 21:
            {
                    var token = this._line.substring(cursorOnEnter, cursor);
                    if (token in this._keywords)
                        this.tokenType = "javascript-keyword";
                    else
                        this.tokenType = "javascript-ident";
                    return cursor;
                }
case 22:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 23:
            yych = this._charAt(++cursor);
            if (yych == '&') { gotoCase = 43; continue; };
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 24:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '\n') { gotoCase = 16; continue; };
            if (yych == '\r') { gotoCase = 16; continue; };
            { gotoCase = 96; continue; };
case 25:
            yych = this._charAt(++cursor);
            { gotoCase = 18; continue; };
case 26:
            ++cursor;
            { this.tokenType = null; return cursor; }
case 28:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 29:
            yych = this._charAt(++cursor);
            if (yych == '+') { gotoCase = 43; continue; };
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 30:
            yych = this._charAt(++cursor);
            if (yych == '-') { gotoCase = 43; continue; };
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 31:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 18; continue; };
            if (yych <= '9') { gotoCase = 89; continue; };
            { gotoCase = 18; continue; };
case 32:
            yyaccept = 2;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '.') {
                if (yych == '*') { gotoCase = 78; continue; };
            } else {
                if (yych <= '/') { gotoCase = 80; continue; };
                if (yych == '=') { gotoCase = 77; continue; };
            }
case 33:
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = null; return cursor; }
case 34:
            yyaccept = 3;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= 'E') {
                if (yych <= '/') {
                    if (yych == '.') { gotoCase = 63; continue; };
                } else {
                    if (yych <= '7') { gotoCase = 72; continue; };
                    if (yych >= 'E') { gotoCase = 62; continue; };
                }
            } else {
                if (yych <= 'd') {
                    if (yych == 'X') { gotoCase = 74; continue; };
                } else {
                    if (yych <= 'e') { gotoCase = 62; continue; };
                    if (yych == 'x') { gotoCase = 74; continue; };
                }
            }
case 35:
            { this.tokenType = "javascript-number"; return cursor; }
case 36:
            yyaccept = 3;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '9') {
                if (yych == '.') { gotoCase = 63; continue; };
                if (yych <= '/') { gotoCase = 35; continue; };
                { gotoCase = 60; continue; };
            } else {
                if (yych <= 'E') {
                    if (yych <= 'D') { gotoCase = 35; continue; };
                    { gotoCase = 62; continue; };
                } else {
                    if (yych == 'e') { gotoCase = 62; continue; };
                    { gotoCase = 35; continue; };
                }
            }
case 37:
            yych = this._charAt(++cursor);
            if (yych <= ';') { gotoCase = 18; continue; };
            if (yych <= '<') { gotoCase = 59; continue; };
            if (yych <= '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 38:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 58; continue; };
            { gotoCase = 18; continue; };
case 39:
            yych = this._charAt(++cursor);
            if (yych <= '<') { gotoCase = 18; continue; };
            if (yych <= '=') { gotoCase = 43; continue; };
            if (yych <= '>') { gotoCase = 56; continue; };
            { gotoCase = 18; continue; };
case 40:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == 'u') { gotoCase = 44; continue; };
            { gotoCase = 16; continue; };
case 41:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 42:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            if (yych != '|') { gotoCase = 18; continue; };
case 43:
            yych = this._charAt(++cursor);
            { gotoCase = 18; continue; };
case 44:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 46; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 46; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 46; continue; };
            }
case 45:
            cursor = YYMARKER;
            if (yyaccept <= 1) {
                if (yyaccept <= 0) {
                    { gotoCase = 16; continue; };
                } else {
                    { gotoCase = 21; continue; };
                }
            } else {
                if (yyaccept <= 2) {
                    { gotoCase = 33; continue; };
                } else {
                    { gotoCase = 35; continue; };
                }
            }
case 46:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 47; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 47:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 48; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 48:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 49; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 49:
            yyaccept = 1;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 50:
            if (yych <= '[') {
                if (yych <= '/') {
                    if (yych == '$') { gotoCase = 49; continue; };
                    { gotoCase = 21; continue; };
                } else {
                    if (yych <= '9') { gotoCase = 49; continue; };
                    if (yych <= '@') { gotoCase = 21; continue; };
                    if (yych <= 'Z') { gotoCase = 49; continue; };
                    { gotoCase = 21; continue; };
                }
            } else {
                if (yych <= '_') {
                    if (yych <= '\\') { gotoCase = 51; continue; };
                    if (yych <= '^') { gotoCase = 21; continue; };
                    { gotoCase = 49; continue; };
                } else {
                    if (yych <= '`') { gotoCase = 21; continue; };
                    if (yych <= 'z') { gotoCase = 49; continue; };
                    if (yych <= 0x7F) { gotoCase = 21; continue; };
                    { gotoCase = 49; continue; };
                }
            }
case 51:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych != 'u') { gotoCase = 45; continue; };
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 53; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 53:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 54; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 54:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 55; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 55:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 49; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 49; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 49; continue; };
                { gotoCase = 45; continue; };
            }
case 56:
            yych = this._charAt(++cursor);
            if (yych <= '<') { gotoCase = 18; continue; };
            if (yych <= '=') { gotoCase = 43; continue; };
            if (yych >= '?') { gotoCase = 18; continue; };
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 58:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 59:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
case 60:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '9') {
                if (yych == '.') { gotoCase = 63; continue; };
                if (yych <= '/') { gotoCase = 35; continue; };
                { gotoCase = 60; continue; };
            } else {
                if (yych <= 'E') {
                    if (yych <= 'D') { gotoCase = 35; continue; };
                } else {
                    if (yych != 'e') { gotoCase = 35; continue; };
                }
            }
case 62:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych == '+') { gotoCase = 69; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= '-') { gotoCase = 69; continue; };
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 70; continue; };
                { gotoCase = 45; continue; };
            }
case 63:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'D') {
                if (yych <= '/') { gotoCase = 35; continue; };
                if (yych <= '9') { gotoCase = 63; continue; };
                { gotoCase = 35; continue; };
            } else {
                if (yych <= 'E') { gotoCase = 65; continue; };
                if (yych != 'e') { gotoCase = 35; continue; };
            }
case 65:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych != '+') { gotoCase = 45; continue; };
            } else {
                if (yych <= '-') { gotoCase = 66; continue; };
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 67; continue; };
                { gotoCase = 45; continue; };
            }
case 66:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 45; continue; };
            if (yych >= ':') { gotoCase = 45; continue; };
case 67:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 35; continue; };
            if (yych <= '9') { gotoCase = 67; continue; };
            { gotoCase = 35; continue; };
case 69:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 45; continue; };
            if (yych >= ':') { gotoCase = 45; continue; };
case 70:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 35; continue; };
            if (yych <= '9') { gotoCase = 70; continue; };
            { gotoCase = 35; continue; };
case 72:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 35; continue; };
            if (yych <= '7') { gotoCase = 72; continue; };
            { gotoCase = 35; continue; };
case 74:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 75; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 75:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 35; continue; };
                if (yych <= '9') { gotoCase = 75; continue; };
                { gotoCase = 35; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 75; continue; };
                if (yych <= '`') { gotoCase = 35; continue; };
                if (yych <= 'f') { gotoCase = 75; continue; };
                { gotoCase = 35; continue; };
            }
case 77:
            yych = this._charAt(++cursor);
            { gotoCase = 33; continue; };
case 78:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 85; continue; };
                { gotoCase = 78; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 85; continue; };
                if (yych == '*') { gotoCase = 83; continue; };
                { gotoCase = 78; continue; };
            }
case 80:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 82; continue; };
            if (yych != '\r') { gotoCase = 80; continue; };
case 82:
            { this.tokenType = "javascript-comment"; return cursor; }
case 83:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '*') { gotoCase = 83; continue; };
            if (yych == '/') { gotoCase = 87; continue; };
            { gotoCase = 78; continue; };
case 85:
            ++cursor;
            this.setLexCondition(this._lexConditions.COMMENT);
            { this.tokenType = "javascript-comment"; return cursor; }
case 87:
            ++cursor;
            { this.tokenType = "javascript-comment"; return cursor; }
case 89:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'D') {
                if (yych <= '/') { gotoCase = 35; continue; };
                if (yych <= '9') { gotoCase = 89; continue; };
                { gotoCase = 35; continue; };
            } else {
                if (yych <= 'E') { gotoCase = 91; continue; };
                if (yych != 'e') { gotoCase = 35; continue; };
            }
case 91:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych != '+') { gotoCase = 45; continue; };
            } else {
                if (yych <= '-') { gotoCase = 92; continue; };
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 93; continue; };
                { gotoCase = 45; continue; };
            }
case 92:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 45; continue; };
            if (yych >= ':') { gotoCase = 45; continue; };
case 93:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 35; continue; };
            if (yych <= '9') { gotoCase = 93; continue; };
            { gotoCase = 35; continue; };
case 95:
            ++cursor;
            yych = this._charAt(cursor);
case 96:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 45; continue; };
                if (yych <= '\f') { gotoCase = 95; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 95; continue; };
                    { gotoCase = 98; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 95; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 45; continue; };
                        { gotoCase = 101; continue; };
                    } else {
                        if (yych == '\r') { gotoCase = 101; continue; };
                        { gotoCase = 45; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 95; continue; };
                        if (yych <= '&') { gotoCase = 45; continue; };
                        { gotoCase = 95; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 95; continue; };
                        { gotoCase = 45; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 95; continue; };
                        if (yych <= 'e') { gotoCase = 45; continue; };
                        { gotoCase = 95; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 95; continue; };
                        { gotoCase = 45; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 45; continue; };
                        { gotoCase = 95; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 100; continue; };
                        if (yych <= 'v') { gotoCase = 95; continue; };
                        { gotoCase = 45; continue; };
                    }
                }
            }
case 98:
            ++cursor;
            { this.tokenType = "javascript-string"; return cursor; }
case 100:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 103; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 103; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 103; continue; };
                { gotoCase = 45; continue; };
            }
case 101:
            ++cursor;
            this.setLexCondition(this._lexConditions.SSTRING);
            { this.tokenType = "javascript-string"; return cursor; }
case 103:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 104; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 104:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 105; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 105:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 95; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 95; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 95; continue; };
                { gotoCase = 45; continue; };
            }
case 106:
            ++cursor;
            yych = this._charAt(cursor);
case 107:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 45; continue; };
                if (yych <= '\f') { gotoCase = 106; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 106; continue; };
                    { gotoCase = 98; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 106; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 45; continue; };
                        { gotoCase = 110; continue; };
                    } else {
                        if (yych == '\r') { gotoCase = 110; continue; };
                        { gotoCase = 45; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 106; continue; };
                        if (yych <= '&') { gotoCase = 45; continue; };
                        { gotoCase = 106; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 106; continue; };
                        { gotoCase = 45; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 106; continue; };
                        if (yych <= 'e') { gotoCase = 45; continue; };
                        { gotoCase = 106; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 106; continue; };
                        { gotoCase = 45; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 45; continue; };
                        { gotoCase = 106; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 109; continue; };
                        if (yych <= 'v') { gotoCase = 106; continue; };
                        { gotoCase = 45; continue; };
                    }
                }
            }
case 109:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 112; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 112; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 112; continue; };
                { gotoCase = 45; continue; };
            }
case 110:
            ++cursor;
            this.setLexCondition(this._lexConditions.DSTRING);
            { this.tokenType = "javascript-string"; return cursor; }
case 112:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 113; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 113:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych >= ':') { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 114; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych >= 'g') { gotoCase = 45; continue; };
            }
case 114:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 45; continue; };
                if (yych <= '9') { gotoCase = 106; continue; };
                { gotoCase = 45; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 106; continue; };
                if (yych <= '`') { gotoCase = 45; continue; };
                if (yych <= 'f') { gotoCase = 106; continue; };
                { gotoCase = 45; continue; };
            }
case 115:
            ++cursor;
            if ((yych = this._charAt(cursor)) == '=') { gotoCase = 43; continue; };
            { gotoCase = 18; continue; };
/* *********************************** */
case this.case_DSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 120; continue; };
                if (yych <= '\f') { gotoCase = 119; continue; };
                { gotoCase = 120; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 119; continue; };
                    { gotoCase = 122; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 124; continue; };
                    { gotoCase = 119; continue; };
                }
            }
case 118:
            { this.tokenType = "javascript-string"; return cursor; }
case 119:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 126; continue; };
case 120:
            ++cursor;
case 121:
            { this.tokenType = null; return cursor; }
case 122:
            ++cursor;
case 123:
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = "javascript-string"; return cursor; }
case 124:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 125; continue; };
                    if (yych <= '&') { gotoCase = 121; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 121; continue; };
                    } else {
                        if (yych != 'b') { gotoCase = 121; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych >= 'g') { gotoCase = 121; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 125; continue; };
                        if (yych <= 'q') { gotoCase = 121; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 121; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 127; continue; };
                        if (yych >= 'w') { gotoCase = 121; continue; };
                    }
                }
            }
case 125:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 126:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 118; continue; };
                if (yych <= '\f') { gotoCase = 125; continue; };
                { gotoCase = 118; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 125; continue; };
                    { gotoCase = 133; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 132; continue; };
                    { gotoCase = 125; continue; };
                }
            }
case 127:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 128; continue; };
                if (yych <= '9') { gotoCase = 129; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 129; continue; };
                if (yych <= '`') { gotoCase = 128; continue; };
                if (yych <= 'f') { gotoCase = 129; continue; };
            }
case 128:
            cursor = YYMARKER;
            if (yyaccept <= 0) {
                { gotoCase = 118; continue; };
            } else {
                { gotoCase = 121; continue; };
            }
case 129:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 128; continue; };
                if (yych >= ':') { gotoCase = 128; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 130; continue; };
                if (yych <= '`') { gotoCase = 128; continue; };
                if (yych >= 'g') { gotoCase = 128; continue; };
            }
case 130:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 128; continue; };
                if (yych >= ':') { gotoCase = 128; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 131; continue; };
                if (yych <= '`') { gotoCase = 128; continue; };
                if (yych >= 'g') { gotoCase = 128; continue; };
            }
case 131:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 128; continue; };
                if (yych <= '9') { gotoCase = 125; continue; };
                { gotoCase = 128; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 125; continue; };
                if (yych <= '`') { gotoCase = 128; continue; };
                if (yych <= 'f') { gotoCase = 125; continue; };
                { gotoCase = 128; continue; };
            }
case 132:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 125; continue; };
                    if (yych <= '&') { gotoCase = 128; continue; };
                    { gotoCase = 125; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 128; continue; };
                        { gotoCase = 125; continue; };
                    } else {
                        if (yych == 'b') { gotoCase = 125; continue; };
                        { gotoCase = 128; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych <= 'f') { gotoCase = 125; continue; };
                        { gotoCase = 128; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 125; continue; };
                        if (yych <= 'q') { gotoCase = 128; continue; };
                        { gotoCase = 125; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 128; continue; };
                        { gotoCase = 125; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 127; continue; };
                        if (yych <= 'v') { gotoCase = 125; continue; };
                        { gotoCase = 128; continue; };
                    }
                }
            }
case 133:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 123; continue; };
/* *********************************** */
case this.case_NODIV:
            yych = this._charAt(cursor);
            if (yych <= '9') {
                if (yych <= '(') {
                    if (yych <= '#') {
                        if (yych <= ' ') { gotoCase = 136; continue; };
                        if (yych <= '!') { gotoCase = 138; continue; };
                        if (yych <= '"') { gotoCase = 140; continue; };
                    } else {
                        if (yych <= '%') {
                            if (yych <= '$') { gotoCase = 141; continue; };
                            { gotoCase = 143; continue; };
                        } else {
                            if (yych <= '&') { gotoCase = 144; continue; };
                            if (yych <= '\'') { gotoCase = 145; continue; };
                            { gotoCase = 146; continue; };
                        }
                    }
                } else {
                    if (yych <= ',') {
                        if (yych <= ')') { gotoCase = 147; continue; };
                        if (yych <= '*') { gotoCase = 149; continue; };
                        if (yych <= '+') { gotoCase = 150; continue; };
                        { gotoCase = 146; continue; };
                    } else {
                        if (yych <= '.') {
                            if (yych <= '-') { gotoCase = 151; continue; };
                            { gotoCase = 152; continue; };
                        } else {
                            if (yych <= '/') { gotoCase = 153; continue; };
                            if (yych <= '0') { gotoCase = 154; continue; };
                            { gotoCase = 156; continue; };
                        }
                    }
                }
            } else {
                if (yych <= '\\') {
                    if (yych <= '>') {
                        if (yych <= ';') { gotoCase = 146; continue; };
                        if (yych <= '<') { gotoCase = 157; continue; };
                        if (yych <= '=') { gotoCase = 158; continue; };
                        { gotoCase = 159; continue; };
                    } else {
                        if (yych <= '@') {
                            if (yych <= '?') { gotoCase = 146; continue; };
                        } else {
                            if (yych <= 'Z') { gotoCase = 141; continue; };
                            if (yych <= '[') { gotoCase = 146; continue; };
                            { gotoCase = 160; continue; };
                        }
                    }
                } else {
                    if (yych <= 'z') {
                        if (yych <= '^') {
                            if (yych <= ']') { gotoCase = 146; continue; };
                            { gotoCase = 161; continue; };
                        } else {
                            if (yych != '`') { gotoCase = 141; continue; };
                        }
                    } else {
                        if (yych <= '|') {
                            if (yych <= '{') { gotoCase = 146; continue; };
                            { gotoCase = 162; continue; };
                        } else {
                            if (yych <= '~') { gotoCase = 146; continue; };
                            if (yych >= 0x80) { gotoCase = 141; continue; };
                        }
                    }
                }
            }
case 136:
            ++cursor;
case 137:
            { this.tokenType = null; return cursor; }
case 138:
            ++cursor;
            if ((yych = this._charAt(cursor)) == '=') { gotoCase = 260; continue; };
case 139:
            { this.tokenType = null; return cursor; }
case 140:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '\n') { gotoCase = 137; continue; };
            if (yych == '\r') { gotoCase = 137; continue; };
            { gotoCase = 252; continue; };
case 141:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 170; continue; };
case 142:
            this.setLexCondition(this._lexConditions.DIV);
            {
                    var token = this._line.substring(cursorOnEnter, cursor);
                    if (token in this._keywords)
                        this.tokenType = "javascript-keyword";
                    else
                        this.tokenType = "javascript-ident";
                    return cursor;
                }
case 143:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 144:
            yych = this._charAt(++cursor);
            if (yych == '&') { gotoCase = 163; continue; };
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 145:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == '\n') { gotoCase = 137; continue; };
            if (yych == '\r') { gotoCase = 137; continue; };
            { gotoCase = 241; continue; };
case 146:
            yych = this._charAt(++cursor);
            { gotoCase = 139; continue; };
case 147:
            ++cursor;
            this.setLexCondition(this._lexConditions.DIV);
            { this.tokenType = null; return cursor; }
case 149:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 150:
            yych = this._charAt(++cursor);
            if (yych == '+') { gotoCase = 163; continue; };
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 151:
            yych = this._charAt(++cursor);
            if (yych == '-') { gotoCase = 163; continue; };
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 152:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 139; continue; };
            if (yych <= '9') { gotoCase = 234; continue; };
            { gotoCase = 139; continue; };
case 153:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 137; continue; };
                    { gotoCase = 197; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 137; continue; };
                    if (yych <= ')') { gotoCase = 197; continue; };
                    { gotoCase = 202; continue; };
                }
            } else {
                if (yych <= 'Z') {
                    if (yych == '/') { gotoCase = 204; continue; };
                    { gotoCase = 197; continue; };
                } else {
                    if (yych <= '[') { gotoCase = 200; continue; };
                    if (yych <= '\\') { gotoCase = 199; continue; };
                    if (yych <= ']') { gotoCase = 137; continue; };
                    { gotoCase = 197; continue; };
                }
            }
case 154:
            yyaccept = 2;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= 'E') {
                if (yych <= '/') {
                    if (yych == '.') { gotoCase = 183; continue; };
                } else {
                    if (yych <= '7') { gotoCase = 192; continue; };
                    if (yych >= 'E') { gotoCase = 182; continue; };
                }
            } else {
                if (yych <= 'd') {
                    if (yych == 'X') { gotoCase = 194; continue; };
                } else {
                    if (yych <= 'e') { gotoCase = 182; continue; };
                    if (yych == 'x') { gotoCase = 194; continue; };
                }
            }
case 155:
            this.setLexCondition(this._lexConditions.DIV);
            { this.tokenType = "javascript-number"; return cursor; }
case 156:
            yyaccept = 2;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '9') {
                if (yych == '.') { gotoCase = 183; continue; };
                if (yych <= '/') { gotoCase = 155; continue; };
                { gotoCase = 180; continue; };
            } else {
                if (yych <= 'E') {
                    if (yych <= 'D') { gotoCase = 155; continue; };
                    { gotoCase = 182; continue; };
                } else {
                    if (yych == 'e') { gotoCase = 182; continue; };
                    { gotoCase = 155; continue; };
                }
            }
case 157:
            yych = this._charAt(++cursor);
            if (yych <= ';') { gotoCase = 139; continue; };
            if (yych <= '<') { gotoCase = 179; continue; };
            if (yych <= '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 158:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 178; continue; };
            { gotoCase = 139; continue; };
case 159:
            yych = this._charAt(++cursor);
            if (yych <= '<') { gotoCase = 139; continue; };
            if (yych <= '=') { gotoCase = 163; continue; };
            if (yych <= '>') { gotoCase = 176; continue; };
            { gotoCase = 139; continue; };
case 160:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych == 'u') { gotoCase = 164; continue; };
            { gotoCase = 137; continue; };
case 161:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 162:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            if (yych != '|') { gotoCase = 139; continue; };
case 163:
            yych = this._charAt(++cursor);
            { gotoCase = 139; continue; };
case 164:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 166; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 166; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 166; continue; };
            }
case 165:
            cursor = YYMARKER;
            if (yyaccept <= 1) {
                if (yyaccept <= 0) {
                    { gotoCase = 137; continue; };
                } else {
                    { gotoCase = 142; continue; };
                }
            } else {
                if (yyaccept <= 2) {
                    { gotoCase = 155; continue; };
                } else {
                    { gotoCase = 217; continue; };
                }
            }
case 166:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 167; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 167:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 168; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 168:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 169; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 169:
            yyaccept = 1;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 170:
            if (yych <= '[') {
                if (yych <= '/') {
                    if (yych == '$') { gotoCase = 169; continue; };
                    { gotoCase = 142; continue; };
                } else {
                    if (yych <= '9') { gotoCase = 169; continue; };
                    if (yych <= '@') { gotoCase = 142; continue; };
                    if (yych <= 'Z') { gotoCase = 169; continue; };
                    { gotoCase = 142; continue; };
                }
            } else {
                if (yych <= '_') {
                    if (yych <= '\\') { gotoCase = 171; continue; };
                    if (yych <= '^') { gotoCase = 142; continue; };
                    { gotoCase = 169; continue; };
                } else {
                    if (yych <= '`') { gotoCase = 142; continue; };
                    if (yych <= 'z') { gotoCase = 169; continue; };
                    if (yych <= 0x7F) { gotoCase = 142; continue; };
                    { gotoCase = 169; continue; };
                }
            }
case 171:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych != 'u') { gotoCase = 165; continue; };
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 173; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 173:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 174; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 174:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 175; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 175:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 169; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 169; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 169; continue; };
                { gotoCase = 165; continue; };
            }
case 176:
            yych = this._charAt(++cursor);
            if (yych <= '<') { gotoCase = 139; continue; };
            if (yych <= '=') { gotoCase = 163; continue; };
            if (yych >= '?') { gotoCase = 139; continue; };
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 178:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 179:
            yych = this._charAt(++cursor);
            if (yych == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
case 180:
            yyaccept = 2;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '9') {
                if (yych == '.') { gotoCase = 183; continue; };
                if (yych <= '/') { gotoCase = 155; continue; };
                { gotoCase = 180; continue; };
            } else {
                if (yych <= 'E') {
                    if (yych <= 'D') { gotoCase = 155; continue; };
                } else {
                    if (yych != 'e') { gotoCase = 155; continue; };
                }
            }
case 182:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych == '+') { gotoCase = 189; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= '-') { gotoCase = 189; continue; };
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 190; continue; };
                { gotoCase = 165; continue; };
            }
case 183:
            yyaccept = 2;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'D') {
                if (yych <= '/') { gotoCase = 155; continue; };
                if (yych <= '9') { gotoCase = 183; continue; };
                { gotoCase = 155; continue; };
            } else {
                if (yych <= 'E') { gotoCase = 185; continue; };
                if (yych != 'e') { gotoCase = 155; continue; };
            }
case 185:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych != '+') { gotoCase = 165; continue; };
            } else {
                if (yych <= '-') { gotoCase = 186; continue; };
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 187; continue; };
                { gotoCase = 165; continue; };
            }
case 186:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 165; continue; };
            if (yych >= ':') { gotoCase = 165; continue; };
case 187:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 155; continue; };
            if (yych <= '9') { gotoCase = 187; continue; };
            { gotoCase = 155; continue; };
case 189:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 165; continue; };
            if (yych >= ':') { gotoCase = 165; continue; };
case 190:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 155; continue; };
            if (yych <= '9') { gotoCase = 190; continue; };
            { gotoCase = 155; continue; };
case 192:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 155; continue; };
            if (yych <= '7') { gotoCase = 192; continue; };
            { gotoCase = 155; continue; };
case 194:
            yych = this._charAt(++cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 195; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 195:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 155; continue; };
                if (yych <= '9') { gotoCase = 195; continue; };
                { gotoCase = 155; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 195; continue; };
                if (yych <= '`') { gotoCase = 155; continue; };
                if (yych <= 'f') { gotoCase = 195; continue; };
                { gotoCase = 155; continue; };
            }
case 197:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '.') {
                if (yych <= '\n') {
                    if (yych <= '\t') { gotoCase = 197; continue; };
                    { gotoCase = 165; continue; };
                } else {
                    if (yych == '\r') { gotoCase = 165; continue; };
                    { gotoCase = 197; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '/') { gotoCase = 220; continue; };
                    if (yych <= 'Z') { gotoCase = 197; continue; };
                    { gotoCase = 228; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 227; continue; };
                    if (yych <= ']') { gotoCase = 165; continue; };
                    { gotoCase = 197; continue; };
                }
            }
case 199:
            yych = this._charAt(++cursor);
            if (yych == '\n') { gotoCase = 165; continue; };
            if (yych == '\r') { gotoCase = 165; continue; };
            { gotoCase = 197; continue; };
case 200:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 200; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 200; continue; };
                    { gotoCase = 165; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych == '/') { gotoCase = 165; continue; };
                    { gotoCase = 200; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 215; continue; };
                    if (yych <= ']') { gotoCase = 213; continue; };
                    { gotoCase = 200; continue; };
                }
            }
case 202:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '\f') {
                if (yych == '\n') { gotoCase = 209; continue; };
                { gotoCase = 202; continue; };
            } else {
                if (yych <= '\r') { gotoCase = 209; continue; };
                if (yych == '*') { gotoCase = 207; continue; };
                { gotoCase = 202; continue; };
            }
case 204:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 206; continue; };
            if (yych != '\r') { gotoCase = 204; continue; };
case 206:
            { this.tokenType = "javascript-comment"; return cursor; }
case 207:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '*') { gotoCase = 207; continue; };
            if (yych == '/') { gotoCase = 211; continue; };
            { gotoCase = 202; continue; };
case 209:
            ++cursor;
            this.setLexCondition(this._lexConditions.COMMENT);
            { this.tokenType = "javascript-comment"; return cursor; }
case 211:
            ++cursor;
            { this.tokenType = "javascript-comment"; return cursor; }
case 213:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 213; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 213; continue; };
                    { gotoCase = 197; continue; };
                }
            } else {
                if (yych <= 'Z') {
                    if (yych == '/') { gotoCase = 220; continue; };
                    { gotoCase = 213; continue; };
                } else {
                    if (yych <= '[') { gotoCase = 218; continue; };
                    if (yych <= '\\') { gotoCase = 216; continue; };
                    { gotoCase = 213; continue; };
                }
            }
case 215:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 165; continue; };
            if (yych == '\r') { gotoCase = 165; continue; };
            { gotoCase = 200; continue; };
case 216:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 217; continue; };
            if (yych != '\r') { gotoCase = 213; continue; };
case 217:
            this.setLexCondition(this._lexConditions.REGEX);
            { this.tokenType = "javascript-regexp"; return cursor; }
case 218:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 218; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 218; continue; };
                    { gotoCase = 165; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych == '/') { gotoCase = 165; continue; };
                    { gotoCase = 218; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 225; continue; };
                    if (yych <= ']') { gotoCase = 223; continue; };
                    { gotoCase = 218; continue; };
                }
            }
case 220:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'h') {
                if (yych == 'g') { gotoCase = 220; continue; };
            } else {
                if (yych <= 'i') { gotoCase = 220; continue; };
                if (yych == 'm') { gotoCase = 220; continue; };
            }
            { this.tokenType = "javascript-regexp"; return cursor; }
case 223:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 223; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 223; continue; };
                    { gotoCase = 197; continue; };
                }
            } else {
                if (yych <= 'Z') {
                    if (yych == '/') { gotoCase = 220; continue; };
                    { gotoCase = 223; continue; };
                } else {
                    if (yych <= '[') { gotoCase = 218; continue; };
                    if (yych <= '\\') { gotoCase = 226; continue; };
                    { gotoCase = 223; continue; };
                }
            }
case 225:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 165; continue; };
            if (yych == '\r') { gotoCase = 165; continue; };
            { gotoCase = 218; continue; };
case 226:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 217; continue; };
            if (yych == '\r') { gotoCase = 217; continue; };
            { gotoCase = 223; continue; };
case 227:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 217; continue; };
            if (yych == '\r') { gotoCase = 217; continue; };
            { gotoCase = 197; continue; };
case 228:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 228; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 228; continue; };
                    { gotoCase = 165; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych == '/') { gotoCase = 165; continue; };
                    { gotoCase = 228; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 232; continue; };
                    if (yych >= '^') { gotoCase = 228; continue; };
                }
            }
case 230:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 165; continue; };
                    { gotoCase = 230; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 165; continue; };
                    if (yych <= ')') { gotoCase = 230; continue; };
                    { gotoCase = 197; continue; };
                }
            } else {
                if (yych <= 'Z') {
                    if (yych == '/') { gotoCase = 220; continue; };
                    { gotoCase = 230; continue; };
                } else {
                    if (yych <= '[') { gotoCase = 228; continue; };
                    if (yych <= '\\') { gotoCase = 233; continue; };
                    { gotoCase = 230; continue; };
                }
            }
case 232:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 165; continue; };
            if (yych == '\r') { gotoCase = 165; continue; };
            { gotoCase = 228; continue; };
case 233:
            yyaccept = 3;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 217; continue; };
            if (yych == '\r') { gotoCase = 217; continue; };
            { gotoCase = 230; continue; };
case 234:
            yyaccept = 2;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'D') {
                if (yych <= '/') { gotoCase = 155; continue; };
                if (yych <= '9') { gotoCase = 234; continue; };
                { gotoCase = 155; continue; };
            } else {
                if (yych <= 'E') { gotoCase = 236; continue; };
                if (yych != 'e') { gotoCase = 155; continue; };
            }
case 236:
            yych = this._charAt(++cursor);
            if (yych <= ',') {
                if (yych != '+') { gotoCase = 165; continue; };
            } else {
                if (yych <= '-') { gotoCase = 237; continue; };
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 238; continue; };
                { gotoCase = 165; continue; };
            }
case 237:
            yych = this._charAt(++cursor);
            if (yych <= '/') { gotoCase = 165; continue; };
            if (yych >= ':') { gotoCase = 165; continue; };
case 238:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '/') { gotoCase = 155; continue; };
            if (yych <= '9') { gotoCase = 238; continue; };
            { gotoCase = 155; continue; };
case 240:
            ++cursor;
            yych = this._charAt(cursor);
case 241:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 165; continue; };
                if (yych <= '\f') { gotoCase = 240; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 240; continue; };
                    { gotoCase = 243; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 240; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 165; continue; };
                        { gotoCase = 246; continue; };
                    } else {
                        if (yych == '\r') { gotoCase = 246; continue; };
                        { gotoCase = 165; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 240; continue; };
                        if (yych <= '&') { gotoCase = 165; continue; };
                        { gotoCase = 240; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 240; continue; };
                        { gotoCase = 165; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 240; continue; };
                        if (yych <= 'e') { gotoCase = 165; continue; };
                        { gotoCase = 240; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 240; continue; };
                        { gotoCase = 165; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 165; continue; };
                        { gotoCase = 240; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 245; continue; };
                        if (yych <= 'v') { gotoCase = 240; continue; };
                        { gotoCase = 165; continue; };
                    }
                }
            }
case 243:
            ++cursor;
            { this.tokenType = "javascript-string"; return cursor; }
case 245:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 248; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 248; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 248; continue; };
                { gotoCase = 165; continue; };
            }
case 246:
            ++cursor;
            this.setLexCondition(this._lexConditions.SSTRING);
            { this.tokenType = "javascript-string"; return cursor; }
case 248:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 249; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 249:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 250; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 250:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 240; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 240; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 240; continue; };
                { gotoCase = 165; continue; };
            }
case 251:
            ++cursor;
            yych = this._charAt(cursor);
case 252:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 165; continue; };
                if (yych <= '\f') { gotoCase = 251; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= '"') {
                    if (yych <= '!') { gotoCase = 251; continue; };
                    { gotoCase = 243; continue; };
                } else {
                    if (yych != '\\') { gotoCase = 251; continue; };
                }
            }
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'a') {
                if (yych <= '!') {
                    if (yych <= '\n') {
                        if (yych <= '\t') { gotoCase = 165; continue; };
                        { gotoCase = 255; continue; };
                    } else {
                        if (yych == '\r') { gotoCase = 255; continue; };
                        { gotoCase = 165; continue; };
                    }
                } else {
                    if (yych <= '\'') {
                        if (yych <= '"') { gotoCase = 251; continue; };
                        if (yych <= '&') { gotoCase = 165; continue; };
                        { gotoCase = 251; continue; };
                    } else {
                        if (yych == '\\') { gotoCase = 251; continue; };
                        { gotoCase = 165; continue; };
                    }
                }
            } else {
                if (yych <= 'q') {
                    if (yych <= 'f') {
                        if (yych <= 'b') { gotoCase = 251; continue; };
                        if (yych <= 'e') { gotoCase = 165; continue; };
                        { gotoCase = 251; continue; };
                    } else {
                        if (yych == 'n') { gotoCase = 251; continue; };
                        { gotoCase = 165; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych == 's') { gotoCase = 165; continue; };
                        { gotoCase = 251; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 254; continue; };
                        if (yych <= 'v') { gotoCase = 251; continue; };
                        { gotoCase = 165; continue; };
                    }
                }
            }
case 254:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 257; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 257; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 257; continue; };
                { gotoCase = 165; continue; };
            }
case 255:
            ++cursor;
            this.setLexCondition(this._lexConditions.DSTRING);
            { this.tokenType = "javascript-string"; return cursor; }
case 257:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 258; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 258:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych >= ':') { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 259; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych >= 'g') { gotoCase = 165; continue; };
            }
case 259:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 165; continue; };
                if (yych <= '9') { gotoCase = 251; continue; };
                { gotoCase = 165; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 251; continue; };
                if (yych <= '`') { gotoCase = 165; continue; };
                if (yych <= 'f') { gotoCase = 251; continue; };
                { gotoCase = 165; continue; };
            }
case 260:
            ++cursor;
            if ((yych = this._charAt(cursor)) == '=') { gotoCase = 163; continue; };
            { gotoCase = 139; continue; };
/* *********************************** */
case this.case_REGEX:
            yych = this._charAt(cursor);
            if (yych <= '.') {
                if (yych <= '\n') {
                    if (yych <= '\t') { gotoCase = 264; continue; };
                    { gotoCase = 265; continue; };
                } else {
                    if (yych == '\r') { gotoCase = 265; continue; };
                    { gotoCase = 264; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '/') { gotoCase = 267; continue; };
                    if (yych <= 'Z') { gotoCase = 264; continue; };
                    { gotoCase = 269; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 270; continue; };
                    if (yych <= ']') { gotoCase = 265; continue; };
                    { gotoCase = 264; continue; };
                }
            }
case 263:
            { this.tokenType = "javascript-regexp"; return cursor; }
case 264:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 272; continue; };
case 265:
            ++cursor;
case 266:
            { this.tokenType = null; return cursor; }
case 267:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 278; continue; };
case 268:
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = "javascript-regexp"; return cursor; }
case 269:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 266; continue; };
                if (yych <= '\f') { gotoCase = 276; continue; };
                { gotoCase = 266; continue; };
            } else {
                if (yych <= '*') {
                    if (yych <= ')') { gotoCase = 276; continue; };
                    { gotoCase = 266; continue; };
                } else {
                    if (yych == '/') { gotoCase = 266; continue; };
                    { gotoCase = 276; continue; };
                }
            }
case 270:
            yych = this._charAt(++cursor);
            if (yych == '\n') { gotoCase = 266; continue; };
            if (yych == '\r') { gotoCase = 266; continue; };
case 271:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 272:
            if (yych <= '.') {
                if (yych <= '\n') {
                    if (yych <= '\t') { gotoCase = 271; continue; };
                    { gotoCase = 263; continue; };
                } else {
                    if (yych == '\r') { gotoCase = 263; continue; };
                    { gotoCase = 271; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych <= '/') { gotoCase = 277; continue; };
                    if (yych <= 'Z') { gotoCase = 271; continue; };
                    { gotoCase = 275; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 273; continue; };
                    if (yych <= ']') { gotoCase = 263; continue; };
                    { gotoCase = 271; continue; };
                }
            }
case 273:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 274; continue; };
            if (yych != '\r') { gotoCase = 271; continue; };
case 274:
            cursor = YYMARKER;
            if (yyaccept <= 0) {
                { gotoCase = 263; continue; };
            } else {
                { gotoCase = 266; continue; };
            }
case 275:
            ++cursor;
            yych = this._charAt(cursor);
case 276:
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 274; continue; };
                    { gotoCase = 275; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 274; continue; };
                    if (yych <= ')') { gotoCase = 275; continue; };
                    { gotoCase = 274; continue; };
                }
            } else {
                if (yych <= '[') {
                    if (yych == '/') { gotoCase = 274; continue; };
                    { gotoCase = 275; continue; };
                } else {
                    if (yych <= '\\') { gotoCase = 281; continue; };
                    if (yych <= ']') { gotoCase = 279; continue; };
                    { gotoCase = 275; continue; };
                }
            }
case 277:
            ++cursor;
            yych = this._charAt(cursor);
case 278:
            if (yych <= 'h') {
                if (yych == 'g') { gotoCase = 277; continue; };
                { gotoCase = 268; continue; };
            } else {
                if (yych <= 'i') { gotoCase = 277; continue; };
                if (yych == 'm') { gotoCase = 277; continue; };
                { gotoCase = 268; continue; };
            }
case 279:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '*') {
                if (yych <= '\f') {
                    if (yych == '\n') { gotoCase = 263; continue; };
                    { gotoCase = 279; continue; };
                } else {
                    if (yych <= '\r') { gotoCase = 263; continue; };
                    if (yych <= ')') { gotoCase = 279; continue; };
                    { gotoCase = 271; continue; };
                }
            } else {
                if (yych <= 'Z') {
                    if (yych == '/') { gotoCase = 277; continue; };
                    { gotoCase = 279; continue; };
                } else {
                    if (yych <= '[') { gotoCase = 275; continue; };
                    if (yych <= '\\') { gotoCase = 282; continue; };
                    { gotoCase = 279; continue; };
                }
            }
case 281:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 274; continue; };
            if (yych == '\r') { gotoCase = 274; continue; };
            { gotoCase = 275; continue; };
case 282:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych == '\n') { gotoCase = 274; continue; };
            if (yych == '\r') { gotoCase = 274; continue; };
            { gotoCase = 279; continue; };
/* *********************************** */
case this.case_SSTRING:
            yych = this._charAt(cursor);
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 287; continue; };
                if (yych <= '\f') { gotoCase = 286; continue; };
                { gotoCase = 287; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 286; continue; };
                    { gotoCase = 289; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 291; continue; };
                    { gotoCase = 286; continue; };
                }
            }
case 285:
            { this.tokenType = "javascript-string"; return cursor; }
case 286:
            yyaccept = 0;
            yych = this._charAt(YYMARKER = ++cursor);
            { gotoCase = 293; continue; };
case 287:
            ++cursor;
case 288:
            { this.tokenType = null; return cursor; }
case 289:
            ++cursor;
case 290:
            this.setLexCondition(this._lexConditions.NODIV);
            { this.tokenType = "javascript-string"; return cursor; }
case 291:
            yyaccept = 1;
            yych = this._charAt(YYMARKER = ++cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 292; continue; };
                    if (yych <= '&') { gotoCase = 288; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 288; continue; };
                    } else {
                        if (yych != 'b') { gotoCase = 288; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych >= 'g') { gotoCase = 288; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 292; continue; };
                        if (yych <= 'q') { gotoCase = 288; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 288; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 294; continue; };
                        if (yych >= 'w') { gotoCase = 288; continue; };
                    }
                }
            }
case 292:
            yyaccept = 0;
            YYMARKER = ++cursor;
            yych = this._charAt(cursor);
case 293:
            if (yych <= '\r') {
                if (yych == '\n') { gotoCase = 285; continue; };
                if (yych <= '\f') { gotoCase = 292; continue; };
                { gotoCase = 285; continue; };
            } else {
                if (yych <= '\'') {
                    if (yych <= '&') { gotoCase = 292; continue; };
                    { gotoCase = 300; continue; };
                } else {
                    if (yych == '\\') { gotoCase = 299; continue; };
                    { gotoCase = 292; continue; };
                }
            }
case 294:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 295; continue; };
                if (yych <= '9') { gotoCase = 296; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 296; continue; };
                if (yych <= '`') { gotoCase = 295; continue; };
                if (yych <= 'f') { gotoCase = 296; continue; };
            }
case 295:
            cursor = YYMARKER;
            if (yyaccept <= 0) {
                { gotoCase = 285; continue; };
            } else {
                { gotoCase = 288; continue; };
            }
case 296:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 295; continue; };
                if (yych >= ':') { gotoCase = 295; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 297; continue; };
                if (yych <= '`') { gotoCase = 295; continue; };
                if (yych >= 'g') { gotoCase = 295; continue; };
            }
case 297:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 295; continue; };
                if (yych >= ':') { gotoCase = 295; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 298; continue; };
                if (yych <= '`') { gotoCase = 295; continue; };
                if (yych >= 'g') { gotoCase = 295; continue; };
            }
case 298:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= '@') {
                if (yych <= '/') { gotoCase = 295; continue; };
                if (yych <= '9') { gotoCase = 292; continue; };
                { gotoCase = 295; continue; };
            } else {
                if (yych <= 'F') { gotoCase = 292; continue; };
                if (yych <= '`') { gotoCase = 295; continue; };
                if (yych <= 'f') { gotoCase = 292; continue; };
                { gotoCase = 295; continue; };
            }
case 299:
            ++cursor;
            yych = this._charAt(cursor);
            if (yych <= 'e') {
                if (yych <= '\'') {
                    if (yych == '"') { gotoCase = 292; continue; };
                    if (yych <= '&') { gotoCase = 295; continue; };
                    { gotoCase = 292; continue; };
                } else {
                    if (yych <= '\\') {
                        if (yych <= '[') { gotoCase = 295; continue; };
                        { gotoCase = 292; continue; };
                    } else {
                        if (yych == 'b') { gotoCase = 292; continue; };
                        { gotoCase = 295; continue; };
                    }
                }
            } else {
                if (yych <= 'r') {
                    if (yych <= 'm') {
                        if (yych <= 'f') { gotoCase = 292; continue; };
                        { gotoCase = 295; continue; };
                    } else {
                        if (yych <= 'n') { gotoCase = 292; continue; };
                        if (yych <= 'q') { gotoCase = 295; continue; };
                        { gotoCase = 292; continue; };
                    }
                } else {
                    if (yych <= 't') {
                        if (yych <= 's') { gotoCase = 295; continue; };
                        { gotoCase = 292; continue; };
                    } else {
                        if (yych <= 'u') { gotoCase = 294; continue; };
                        if (yych <= 'v') { gotoCase = 292; continue; };
                        { gotoCase = 295; continue; };
                    }
                }
            }
case 300:
            ++cursor;
            yych = this._charAt(cursor);
            { gotoCase = 290; continue; };
        }

        }
    }
}

WebInspector.SourceJavaScriptTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype;

/* SourceView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.SourceView = function(resource)
{
    WebInspector.ResourceView.call(this, resource);

    this.element.addStyleClass("source");

    var canEditScripts = WebInspector.panels.scripts && WebInspector.panels.scripts.canEditScripts() && resource.type === WebInspector.Resource.Type.Script;
    this.sourceFrame = new WebInspector.SourceFrame(this.contentElement, this._addBreakpoint.bind(this), canEditScripts ? this._editLine.bind(this) : null, this._continueToLine.bind(this));
    resource.addEventListener("finished", this._resourceLoadingFinished, this);
    this._frameNeedsSetup = true;
}

// This is a map from resource.type to mime types
// found in WebInspector.SourceTokenizer.Registry.
WebInspector.SourceView.DefaultMIMETypeForResourceType = {
    0: "text/html",
    1: "text/css",
    4: "text/javascript"
}

WebInspector.SourceView.prototype = {
    show: function(parentElement)
    {
        WebInspector.ResourceView.prototype.show.call(this, parentElement);
        this.sourceFrame.visible = true;
        if (this.localSourceFrame)
            this.localSourceFrame.visible = true;
        this.resize();
    },

    hide: function()
    {
        this.sourceFrame.visible = false;
        if (!this._frameNeedsSetup)
            this.sourceFrame.clearLineHighlight();
        WebInspector.View.prototype.hide.call(this);
        if (this.localSourceFrame)
            this.localSourceFrame.visible = false;
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.sourceFrame)
            this.sourceFrame.resize();
        if (this.localSourceFrame)
            this.localSourceFrame.resize();
    },

    setupSourceFrameIfNeeded: function()
    {
        if (!this._frameNeedsSetup)
            return;

        this.attach();

        delete this._frameNeedsSetup;
        WebInspector.getResourceContent(this.resource.identifier, this._contentLoaded.bind(this));
    },

    hasContentTab: function()
    {
        return true;
    },

    contentTabSelected: function()
    {
        this.setupSourceFrameIfNeeded();
    },

    _contentLoaded: function(content)
    {
        var mimeType = this._canonicalMimeType(this.resource);
        this.sourceFrame.setContent(mimeType, content, this.resource.url);
        this._sourceFrameSetupFinished();
        var breakpoints = WebInspector.breakpointManager.breakpointsForURL(this.resource.url);
        for (var i = 0; i < breakpoints.length; ++i)
            this.sourceFrame.addBreakpoint(breakpoints[i]);
    },

    _canonicalMimeType: function(resource)
    {
        return WebInspector.SourceView.DefaultMIMETypeForResourceType[resource.type] || resource.mimeType;
    },

    _resourceLoadingFinished: function(event)
    {
        this._frameNeedsSetup = true;
        this._sourceFrameSetup = false;
        if (this.visible)
            this.setupSourceFrameIfNeeded();
        this.resource.removeEventListener("finished", this._resourceLoadingFinished, this);
    },

    _continueToLine: function(line)
    {
        var scriptsPanel = WebInspector.panels.scripts;
        if (scriptsPanel) {
            var sourceID = this._sourceIDForLine(line);
            scriptsPanel.continueToLine(sourceID, line);
        }
    },

    _addBreakpoint: function(line)
    {
        var sourceID = this._sourceIDForLine(line);
        WebInspector.breakpointManager.setBreakpoint(sourceID, this.resource.url, line, true, "");
        if (!WebInspector.panels.scripts.breakpointsActivated)
            WebInspector.panels.scripts.toggleBreakpointsClicked();
    },

    _editLine: function(line, newContent, cancelEditingCallback)
    {
        var lines = [];
        var textModel = this.sourceFrame.textModel;
        for (var i = 0; i < textModel.linesCount; ++i) {
            if (i === line)
                lines.push(newContent);
            else
                lines.push(textModel.line(i));
        }

        var linesCountToShift = newContent.split("\n").length - 1;
        WebInspector.panels.scripts.editScriptSource(this._sourceIDForLine(line), lines.join("\n"), line, linesCountToShift, this._editLineComplete.bind(this), cancelEditingCallback);
    },

    _editLineComplete: function(newBody)
    {
        this.sourceFrame.updateContent(newBody);
    },

    _sourceIDForLine: function(line)
    {
        var sourceID = null;
        var closestStartingLine = 0;
        var scripts = this.resource.scripts;
        for (var i = 0; i < scripts.length; ++i) {
            var script = scripts[i];
            if (script.startingLine <= line && script.startingLine >= closestStartingLine) {
                closestStartingLine = script.startingLine;
                sourceID = script.sourceID;
            }
        }
        return sourceID;
    },

    // The rest of the methods in this prototype need to be generic enough to work with a ScriptView.
    // The ScriptView prototype pulls these methods into it's prototype to avoid duplicate code.

    searchCanceled: function()
    {
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
        this.sourceFrame.clearMarkedRange();
        delete this._delayedFindSearchMatches;
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        this._searchFinishedCallback = finishedCallback;

        function findSearchMatches(query, finishedCallback)
        {
            this._searchResults = this.sourceFrame.findSearchMatches(query);
            if (this._searchResults)
                finishedCallback(this, this._searchResults.length);
        }

        if (!this._sourceFrameSetup) {
            // The search is performed in _sourceFrameSetupFinished by calling _delayedFindSearchMatches.
            this._delayedFindSearchMatches = findSearchMatches.bind(this, query, finishedCallback);
            this.setupSourceFrameIfNeeded();
            return;
        }

        findSearchMatches.call(this, query, finishedCallback);
    },

    updateLocalContent: function(content, mimeType)
    {
        if (!this.localContentElement) {
            this.localContentElement = document.createElement("div");
            this.localContentElement.className = "resource-view-content";
            this.tabbedPane.appendTab("local", WebInspector.UIString("Local"), this.localContentElement, this.selectLocalContentTab.bind(this));
            this.localSourceFrame = new WebInspector.SourceFrame(this.localContentElement, this._addBreakpoint.bind(this), null, this._continueToLine.bind(this));
        }
        this.localSourceFrame.setContent(mimeType, content, "");
    },

    selectLocalContentTab: function()
    {
        this.tabbedPane.selectTabById("local");
        this.localSourceFrame.visible = true;
        if ("resize" in this)
            this.resize();
    },

    jumpToFirstSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToLastSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    showingFirstSearchResult: function()
    {
        return (this._currentSearchResultIndex === 0);
    },

    showingLastSearchResult: function()
    {
        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
    },

    revealLine: function(lineNumber)
    {
        this.setupSourceFrameIfNeeded();
        this.sourceFrame.revealLine(lineNumber);
    },

    highlightLine: function(lineNumber)
    {
        this.setupSourceFrameIfNeeded();
        this.sourceFrame.highlightLine(lineNumber);
    },

    addMessage: function(msg)
    {
        this.sourceFrame.addMessage(msg);
    },

    clearMessages: function()
    {
        this.sourceFrame.clearMessages();
    },

    _jumpToSearchResult: function(index)
    {
        var foundRange = this._searchResults[index];
        if (!foundRange)
            return;

        this.sourceFrame.markAndRevealRange(foundRange);
    },

    _sourceFrameSetupFinished: function()
    {
        this._sourceFrameSetup = true;
        this.resize();
        if (this._delayedFindSearchMatches) {
            this._delayedFindSearchMatches();
            delete this._delayedFindSearchMatches;
        }
    }
}

WebInspector.SourceView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* FontView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.FontView = function(resource)
{
    WebInspector.ResourceView.call(this, resource);

    this.element.addStyleClass("font");
}

WebInspector.FontView.prototype = {
    hasContentTab: function()
    {
        return true;
    },

    contentTabSelected: function()
    {
        if (this.fontPreviewElement)
            return;

        var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier;

        this.fontStyleElement = document.createElement("style");
        this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.resource.url + "); }";
        document.head.appendChild(this.fontStyleElement);

        this.fontPreviewElement = document.createElement("div");
        this.contentElement.appendChild(this.fontPreviewElement);

        this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null);
        this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890";
        this._lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;

        this.updateFontPreviewSize();
    },

    show: function(parentElement)
    {
        WebInspector.ResourceView.prototype.show.call(this, parentElement);
        this.updateFontPreviewSize();
    },

    resize: function()
    {
        this.updateFontPreviewSize();
    },

    updateFontPreviewSize: function()
    {
        if (!this.fontPreviewElement || !this.visible)
            return;

        const measureFontSize = 50;
        this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
        this.fontPreviewElement.style.setProperty("position", "absolute", null);
        this.fontPreviewElement.style.removeProperty("height");

        const height = this.fontPreviewElement.offsetHeight;
        const width = this.fontPreviewElement.offsetWidth;

        // Subtract some padding. This should match the padding in the CSS plus room for the scrollbar.
        const containerWidth = this.contentElement.offsetWidth - 50;

        if (!height || !width || !containerWidth) {
            this.fontPreviewElement.style.removeProperty("font-size");
            this.fontPreviewElement.style.removeProperty("position");
            return;
        }

        var realLineHeight = Math.floor(height / this._lineCount);
        var fontSizeLineRatio = measureFontSize / realLineHeight;
        var widthRatio = containerWidth / width;
        var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 2;

        this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
        this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
        this.fontPreviewElement.style.removeProperty("position");
    }
}

WebInspector.FontView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* ImageView.js */

/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ImageView = function(resource)
{
    WebInspector.ResourceView.call(this, resource);

    this.element.addStyleClass("image");
}

WebInspector.ImageView.prototype = {
    hasContentTab: function()
    {
        return true;
    },

    contentTabSelected: function()
    {
        if (this._container)
            return;
        this._container = document.createElement("div");
        this._container.className = "image";
        this.contentElement.appendChild(this._container);

        this.imagePreviewElement = document.createElement("img");
        this.imagePreviewElement.addStyleClass("resource-image-view");
        this.imagePreviewElement.setAttribute("src", this.resource.url);

        this._container.appendChild(this.imagePreviewElement);

        this._container = document.createElement("div");
        this._container.className = "info";
        this.contentElement.appendChild(this._container);

        var imageNameElement = document.createElement("h1");
        imageNameElement.className = "title";
        imageNameElement.textContent = this.resource.displayName;
        this._container.appendChild(imageNameElement);

        var infoListElement = document.createElement("dl");
        infoListElement.className = "infoList";

        var imageProperties = [
            { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) },
            { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.resourceSize, WebInspector.UIString) },
            { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType }
        ];

        var listHTML = '';
        for (var i = 0; i < imageProperties.length; ++i)
            listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>";

        infoListElement.innerHTML = listHTML;
        this._container.appendChild(infoListElement);
    }
}

WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype;

/* DatabaseTableView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DatabaseTableView = function(database, tableName)
{
    WebInspector.View.call(this);

    this.database = database;
    this.tableName = tableName;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("table");

    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
}

WebInspector.DatabaseTableView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.update();
    },

    get statusBarItems()
    {
        return [this.refreshButton.element];
    },

    update: function()
    {
        this.database.executeSql("SELECT * FROM " + this.tableName, this._queryFinished.bind(this), this._queryError.bind(this));
    },

    _queryFinished: function(columnNames, values)
    {
        this.element.removeChildren();

        var dataGrid = WebInspector.panels.storage.dataGridForResult(columnNames, values);
        if (!dataGrid) {
            var emptyMsgElement = document.createElement("div");
            emptyMsgElement.className = "storage-table-empty";
            emptyMsgElement.textContent = WebInspector.UIString("The “%s”\ntable is empty.", this.tableName);
            this.element.appendChild(emptyMsgElement);
            return;
        }

        this.element.appendChild(dataGrid.element);
        dataGrid.autoSizeColumns(5);
    },

    _queryError: function(error)
    {
        this.element.removeChildren();

        var errorMsgElement = document.createElement("div");
        errorMsgElement.className = "storage-table-error";
        errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.tableName);
        this.element.appendChild(errorMsgElement);
    },

    _refreshButtonClicked: function(event)
    {
        this.update();
    }
}

WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype;

/* DatabaseQueryView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DatabaseQueryView = function(database)
{
    WebInspector.View.call(this);

    this.database = database;

    this.element.addStyleClass("storage-view");
    this.element.addStyleClass("query");
    this.element.addStyleClass("monospace");
    this.element.tabIndex = 0;

    this.element.addEventListener("selectstart", this._selectStart.bind(this), false);

    this.promptElement = document.createElement("div");
    this.promptElement.className = "database-query-prompt";
    this.promptElement.appendChild(document.createElement("br"));
    this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
    this.element.appendChild(this.promptElement);

    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " ");
}

WebInspector.DatabaseQueryView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);

        function moveBackIfOutside()
        {
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
        }

        setTimeout(moveBackIfOutside.bind(this), 0);
    },

    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    {
        var prefix = wordRange.toString().toLowerCase();
        if (!prefix.length)
            return;

        var results = [];

        function accumulateMatches(textArray)
        {
            if (bestMatchOnly && results.length)
                return;
            for (var i = 0; i < textArray.length; ++i) {
                var text = textArray[i].toLowerCase();
                if (text.length < prefix.length)
                    continue;
                if (text.indexOf(prefix) !== 0)
                    continue;
                results.push(textArray[i]);
                if (bestMatchOnly)
                    return;
            }
        }
        
        function tableNamesCallback(tableNames)
        {
            accumulateMatches(tableNames.map(function(name) { return name + " " }));
            accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]);

            completionsReadyCallback(results);
        }
        this.database.getTableNames(tableNamesCallback);
    },

    _promptKeyDown: function(event)
    {
        if (isEnterKey(event)) {
            this._enterKeyPressed(event);
            return;
        }
    },

    _selectStart: function(event)
    {
        if (this._selectionTimeout)
            clearTimeout(this._selectionTimeout);

        this.prompt.clearAutoComplete();

        function moveBackIfOutside()
        {
            delete this._selectionTimeout;
            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
                this.prompt.moveCaretToEndOfPrompt();
            this.prompt.autoCompleteSoon();
        }

        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    },

    _enterKeyPressed: function(event)
    {
        event.preventDefault();
        event.stopPropagation();

        this.prompt.clearAutoComplete(true);

        var query = this.prompt.text;
        if (!query.length)
            return;

        this.prompt.history.push(query);
        this.prompt.historyOffset = 0;
        this.prompt.text = "";

        this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query));
    },

    _queryFinished: function(query, columnNames, values)
    {
        var dataGrid = WebInspector.panels.storage.dataGridForResult(columnNames, values);
        var trimmedQuery = query.trim();

        if (dataGrid) {
            dataGrid.element.addStyleClass("inline");
            this._appendQueryResult(trimmedQuery, dataGrid.element);
            dataGrid.autoSizeColumns(5);            
        }

        if (trimmedQuery.match(/^create /i) || trimmedQuery.match(/^drop table /i))
            WebInspector.panels.storage.updateDatabaseTables(this.database);
    },

    _queryError: function(query, error)
    {
        if (error.message)
            var message = error.message;
        else if (error.code == 2)
            var message = WebInspector.UIString("Database no longer has expected version.");
        else
            var message = WebInspector.UIString("An unexpected error %s occurred.", error.code);

        this._appendQueryResult(query, message, "error");
    },

    _appendQueryResult: function(query, result, resultClassName)
    {
        var element = document.createElement("div");
        element.className = "database-user-query";

        var commandTextElement = document.createElement("span");
        commandTextElement.className = "database-query-text";
        commandTextElement.textContent = query;
        element.appendChild(commandTextElement);

        var resultElement = document.createElement("div");
        resultElement.className = "database-query-result";

        if (resultClassName)
            resultElement.addStyleClass(resultClassName);

        if (typeof result === "string" || result instanceof String)
            resultElement.textContent = result;
        else if (result && result.nodeName)
            resultElement.appendChild(result);

        if (resultElement.childNodes.length)
            element.appendChild(resultElement);

        this.element.insertBefore(element, this.promptElement);
        this.promptElement.scrollIntoView(false);
    }
}

WebInspector.DatabaseQueryView.prototype.__proto__ = WebInspector.View.prototype;

/* ScriptView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ScriptView = function(script)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("script-view");

    this.script = script;

    this._frameNeedsSetup = true;
    this._sourceFrameSetup = false;
    var canEditScripts = WebInspector.panels.scripts.canEditScripts();
    this.sourceFrame = new WebInspector.SourceFrame(this.element, this._addBreakpoint.bind(this), canEditScripts ? this._editLine.bind(this) : null, this._continueToLine.bind(this));
}

WebInspector.ScriptView.prototype = {
    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.setupSourceFrameIfNeeded();
        this.sourceFrame.visible = true;
        this.resize();
    },

    setupSourceFrameIfNeeded: function()
    {
        if (!this._frameNeedsSetup)
            return;
        delete this._frameNeedsSetup;

        this.attach();

        if (this.script.source)
            this._sourceFrameSetupFinished();
        else
            InspectorBackend.getScriptSource(this.script.sourceID, this._didGetScriptSource.bind(this));
    },

    _didGetScriptSource: function(source)
    {
        this.script.source = source || WebInspector.UIString("<source is not available>");
        this._sourceFrameSetupFinished();
    },

    _sourceFrameSetupFinished: function()
    {
        this.sourceFrame.setContent("text/javascript", this._prependWhitespace(this.script.source));
        this._sourceFrameSetup = true;
    },

    _prependWhitespace: function(content) {
        var prefix = "";
        for (var i = 0; i < this.script.startingLine - 1; ++i)
            prefix += "\n";
        return prefix + content;
    },

    attach: function()
    {
        if (!this.element.parentNode)
            document.getElementById("script-resource-views").appendChild(this.element);
    },

    _continueToLine: function(line)
    {
        var scriptsPanel = WebInspector.panels.scripts;
        if (scriptsPanel)
            scriptsPanel.continueToLine(this.script.sourceID, line);
    },

    _addBreakpoint: function(line)
    {
        WebInspector.breakpointManager.setBreakpoint(this.script.sourceID, this.script.sourceURL, line, true, "");
        if (!WebInspector.panels.scripts.breakpointsActivated)
            WebInspector.panels.scripts.toggleBreakpointsClicked();
    },

    _editLineComplete: function(newBody)
    {
        this.script.source = newBody;
        this.sourceFrame.updateContent(this._prependWhitespace(newBody));
    },

    _sourceIDForLine: function(line)
    {
        return this.script.sourceID;
    },

    // The following methods are pulled from SourceView, since they are
    // generic and work with ScriptView just fine.

    hide: WebInspector.SourceView.prototype.hide,
    revealLine: WebInspector.SourceView.prototype.revealLine,
    highlightLine: WebInspector.SourceView.prototype.highlightLine,
    addMessage: WebInspector.SourceView.prototype.addMessage,
    clearMessages: WebInspector.SourceView.prototype.clearMessages,
    searchCanceled: WebInspector.SourceView.prototype.searchCanceled,
    performSearch: WebInspector.SourceView.prototype.performSearch,
    jumpToFirstSearchResult: WebInspector.SourceView.prototype.jumpToFirstSearchResult,
    jumpToLastSearchResult: WebInspector.SourceView.prototype.jumpToLastSearchResult,
    jumpToNextSearchResult: WebInspector.SourceView.prototype.jumpToNextSearchResult,
    jumpToPreviousSearchResult: WebInspector.SourceView.prototype.jumpToPreviousSearchResult,
    showingFirstSearchResult: WebInspector.SourceView.prototype.showingFirstSearchResult,
    showingLastSearchResult: WebInspector.SourceView.prototype.showingLastSearchResult,
    _jumpToSearchResult: WebInspector.SourceView.prototype._jumpToSearchResult,
    _editLine: WebInspector.SourceView.prototype._editLine,
    resize: WebInspector.SourceView.prototype.resize
}

WebInspector.ScriptView.prototype.__proto__ = WebInspector.View.prototype;


/* ProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren)
{
    this.profileView = profileView;
    this.profileNode = profileNode;

    WebInspector.DataGridNode.call(this, null, hasChildren);

    this.addEventListener("populate", this._populate, this);

    this.tree = owningTree;

    this.childrenByCallUID = {};
    this.lastComparator = null;

    this.callUID = profileNode.callUID;
    this.selfTime = profileNode.selfTime;
    this.totalTime = profileNode.totalTime;
    this.functionName = profileNode.functionName;
    this.numberOfCalls = profileNode.numberOfCalls;
    this.url = profileNode.url;
}

WebInspector.ProfileDataGridNode.prototype = {
    get data()
    {
        function formatMilliseconds(time)
        {
            return Number.secondsToString(time / 1000, WebInspector.UIString, !Preferences.samplingCPUProfiler);
        }

        var data = {};

        data["function"] = this.functionName;
        data["calls"] = this.numberOfCalls;

        if (this.profileView.showSelfTimeAsPercent)
            data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent);
        else
            data["self"] = formatMilliseconds(this.selfTime);

        if (this.profileView.showTotalTimeAsPercent)
            data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent);
        else
            data["total"] = formatMilliseconds(this.totalTime);

        if (this.profileView.showAverageTimeAsPercent)
            data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent);
        else
            data["average"] = formatMilliseconds(this.averageTime);

        return data;
    },

    createCell: function(columnIdentifier)
    {
        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);

        if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
            cell.addStyleClass("highlight");
        else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
            cell.addStyleClass("highlight");

        if (columnIdentifier !== "function")
            return cell;

        if (this.profileNode._searchMatchedFunctionColumn)
            cell.addStyleClass("highlight");

        if (this.profileNode.url) {
            var lineNumber;
            if (this.profileNode.lineNumber > 0)
                lineNumber = this.profileNode.lineNumber;
            var urlElement = WebInspector.linkifyResourceAsNode(this.profileNode.url, "scripts", lineNumber, "profile-node-file");
            cell.insertBefore(urlElement, cell.firstChild);
        }

        return cell;
    },

    select: function(supressSelectedEvent)
    {
        WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
        this.profileView._dataGridNodeSelected(this);
    },

    deselect: function(supressDeselectedEvent)
    {
        WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
        this.profileView._dataGridNodeDeselected(this);
    },

    sort: function(/*Function*/ comparator, /*Boolean*/ force)
    {
        var gridNodeGroups = [[this]];

        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
            var count = gridNodes.length;

            for (var index = 0; index < count; ++index) {
                var gridNode = gridNodes[index];

                // If the grid node is collapsed, then don't sort children (save operation for later).
                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
                if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
                    if (gridNode.children.length)
                        gridNode.shouldRefreshChildren = true;
                    continue;
                }

                gridNode.lastComparator = comparator;

                var children = gridNode.children;
                var childCount = children.length;

                if (childCount) {
                    children.sort(comparator);

                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
                        children[childIndex]._recalculateSiblings(childIndex);

                    gridNodeGroups.push(children);
                }
            }
        }
    },

    insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index)
    {
        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);

        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
    },

    removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);

        delete this.childrenByCallUID[profileDataGridNode.callUID];
    },

    removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        WebInspector.DataGridNode.prototype.removeChildren.call(this);

        this.childrenByCallUID = {};
    },

    findChild: function(/*Node*/ node)
    {
        if (!node)
            return null;
        return this.childrenByCallUID[node.callUID];
    },

    get averageTime()
    {
        return this.selfTime / Math.max(1, this.numberOfCalls);
    },

    get averagePercent()
    {
        return this.averageTime / this.tree.totalTime * 100.0;
    },

    get selfPercent()
    {
        return this.selfTime / this.tree.totalTime * 100.0;
    },

    get totalPercent()
    {
        return this.totalTime / this.tree.totalTime * 100.0;
    },

    get _parent()
    {
        return this.parent !== this.dataGrid ? this.parent : this.tree;
    },

    _populate: function(event)
    {
        this._sharedPopulate();

        if (this._parent) {
            var currentComparator = this._parent.lastComparator;

            if (currentComparator)
                this.sort(currentComparator, true);
        }

        if (this.removeEventListener)
            this.removeEventListener("populate", this._populate, this);
    },

    // When focusing and collapsing we modify lots of nodes in the tree.
    // This allows us to restore them all to their original state when we revert.
    _save: function()
    {
        if (this._savedChildren)
            return;

        this._savedSelfTime = this.selfTime;
        this._savedTotalTime = this.totalTime;
        this._savedNumberOfCalls = this.numberOfCalls;

        this._savedChildren = this.children.slice();
    },

    // When focusing and collapsing we modify lots of nodes in the tree.
    // This allows us to restore them all to their original state when we revert.
    _restore: function()
    {
        if (!this._savedChildren)
            return;

        this.selfTime = this._savedSelfTime;
        this.totalTime = this._savedTotalTime;
        this.numberOfCalls = this._savedNumberOfCalls;

        this.removeChildren();

        var children = this._savedChildren;
        var count = children.length;

        for (var index = 0; index < count; ++index) {
            children[index]._restore();
            this.appendChild(children[index]);
        }
    },

    _merge: function(child, shouldAbsorb)
    {
        this.selfTime += child.selfTime;

        if (!shouldAbsorb) {
            this.totalTime += child.totalTime;
            this.numberOfCalls += child.numberOfCalls;
        }

        var children = this.children.slice();

        this.removeChildren();

        var count = children.length;

        for (var index = 0; index < count; ++index) {
            if (!shouldAbsorb || children[index] !== child)
                this.appendChild(children[index]);
        }

        children = child.children.slice();
        count = children.length;

        for (var index = 0; index < count; ++index) {
            var orphanedChild = children[index],
                existingChild = this.childrenByCallUID[orphanedChild.callUID];

            if (existingChild)
                existingChild._merge(orphanedChild, false);
            else
                this.appendChild(orphanedChild);
        }
    }
}

WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.ProfileDataGridTree = function(profileView, profileNode)
{
    this.tree = this;
    this.children = [];

    this.profileView = profileView;

    this.totalTime = profileNode.totalTime;
    this.lastComparator = null;

    this.childrenByCallUID = {};
}

WebInspector.ProfileDataGridTree.prototype = {
    get expanded()
    {
        return true;
    },

    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        this.children.splice(index, 0, child);
        this.childrenByCallUID[child.callUID] = child;
    },

    removeChildren: function()
    {
        this.children = [];
        this.childrenByCallUID = {};
    },

    findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
    sort: WebInspector.ProfileDataGridNode.prototype.sort,

    _save: function()
    {
        if (this._savedChildren)
            return;

        this._savedTotalTime = this.totalTime;
        this._savedChildren = this.children.slice();
    },

    restore: function()
    {
        if (!this._savedChildren)
            return;

        this.children = this._savedChildren;
        this.totalTime = this._savedTotalTime;

        var children = this.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            children[index]._restore();

        this._savedChildren = null;
    }
}

WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];

WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending)
{
    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];

    if (!comparator) {
        if (isAscending) {
            comparator = function(lhs, rhs)
            {
                if (lhs[property] < rhs[property])
                    return -1;

                if (lhs[property] > rhs[property])
                    return 1;

                return 0;
            }
        } else {
            comparator = function(lhs, rhs)
            {
                if (lhs[property] > rhs[property])
                    return -1;

                if (lhs[property] < rhs[property])
                    return 1;

                return 0;
            }
        }

        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
    }

    return comparator;
}

/* BottomUpProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Bottom Up Profiling shows the entire callstack backwards:
// The root node is a representation of each individual function called, and each child of that node represents
// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in
// each child still represent the root node. We have to be particularly careful of recursion with this mode
// because a root node can represent itself AND an ancestor.

WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree)
{
    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, this._willHaveChildren(profileNode));

    this._remainingNodeInfos = [];
}

WebInspector.BottomUpProfileDataGridNode.prototype = {
    _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        this._save();

        this.selfTime = profileDataGridNode.selfTime;
        this.totalTime = profileDataGridNode.totalTime;
        this.numberOfCalls = profileDataGridNode.numberOfCalls;
    },

    // When focusing, we keep just the members of the callstack.
    _keepOnlyChild: function(/*ProfileDataGridNode*/ child)
    {
        this._save();

        this.removeChildren();
        this.appendChild(child);
    },

    _exclude: function(aCallUID)
    {
        if (this._remainingNodeInfos)
            this._populate();

        this._save();

        var children = this.children;
        var index = this.children.length;

        while (index--)
            children[index]._exclude(aCallUID);

        var child = this.childrenByCallUID[aCallUID];

        if (child)
            this._merge(child, true);
    },

    _restore: function()
    {
        WebInspector.ProfileDataGridNode.prototype._restore();

        if (!this.children.length)
            this.hasChildren = this._willHaveChildren();
    },

    _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb)
    {
        this.selfTime -= child.selfTime;

        WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
    },

    _sharedPopulate: function()
    {
        var remainingNodeInfos = this._remainingNodeInfos;
        var count = remainingNodeInfos.length;

        for (var index = 0; index < count; ++index) {
            var nodeInfo = remainingNodeInfos[index];
            var ancestor = nodeInfo.ancestor;
            var focusNode = nodeInfo.focusNode;
            var child = this.findChild(ancestor);

            // If we already have this child, then merge the data together.
            if (child) {
                var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;

                child.selfTime += focusNode.selfTime;
                child.numberOfCalls += focusNode.numberOfCalls;

                if (!totalTimeAccountedFor)
                    child.totalTime += focusNode.totalTime;
            } else {
                // If not, add it as a true ancestor.
                // In heavy mode, we take our visual identity from ancestor node...
                var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree);

                if (ancestor !== focusNode) {
                    // but the actual statistics from the "root" node (bottom of the callstack).
                    child.selfTime = focusNode.selfTime;
                    child.totalTime = focusNode.totalTime;
                    child.numberOfCalls = focusNode.numberOfCalls;
                }

                this.appendChild(child);
            }

            var parent = ancestor.parent;
            if (parent && parent.parent) {
                nodeInfo.ancestor = parent;
                child._remainingNodeInfos.push(nodeInfo);
            }
        }

        delete this._remainingNodeInfos;
    },

    _willHaveChildren: function(profileNode)
    {
        profileNode = profileNode || this.profileNode;
        // In bottom up mode, our parents are our children since we display an inverted tree.
        // However, we don't want to show the very top parent since it is redundant.
        return !!(profileNode.parent && profileNode.parent.parent);
    }
}

WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;

WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode)
{
    WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode);

    // Iterate each node in pre-order.
    var profileNodeUIDs = 0;
    var profileNodeGroups = [[], [aProfileNode]];
    var visitedProfileNodesForCallUID = {};

    this._remainingNodeInfos = [];

    for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
        var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
        var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
        var count = profileNodes.length;

        for (var index = 0; index < count; ++index) {
            var profileNode = profileNodes[index];

            if (!profileNode.UID)
                profileNode.UID = ++profileNodeUIDs;

            if (profileNode.head && profileNode !== profileNode.head) {
                // The total time of this ancestor is accounted for if we're in any form of recursive cycle.
                var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
                var totalTimeAccountedFor = false;

                if (!visitedNodes) {
                    visitedNodes = {}
                    visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
                } else {
                    // The total time for this node has already been accounted for iff one of it's parents has already been visited.
                    // We can do this check in this style because we are traversing the tree in pre-order.
                    var parentCount = parentProfileNodes.length;
                    for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
                        if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
                            totalTimeAccountedFor = true;
                            break;
                        }
                    }
                }

                visitedNodes[profileNode.UID] = true;

                this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
            }

            var children = profileNode.children;
            if (children.length) {
                profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
                profileNodeGroups.push(children);
            }
        }
    }

    // Populate the top level nodes.
    WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this);

    return this;
}

WebInspector.BottomUpProfileDataGridTree.prototype = {
    // When focusing, we keep the entire callstack up to this ancestor.
    focus: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        if (!profileDataGridNode)
            return;

        this._save();

        var currentNode = profileDataGridNode;
        var focusNode = profileDataGridNode;

        while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
            currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);

            focusNode = currentNode;
            currentNode = currentNode.parent;

            if (currentNode instanceof WebInspector.ProfileDataGridNode)
                currentNode._keepOnlyChild(focusNode);
        }

        this.children = [focusNode];
        this.totalTime = profileDataGridNode.totalTime;
    },

    exclude: function(/*ProfileDataGridNode*/ profileDataGridNode)
    {
        if (!profileDataGridNode)
            return;

        this._save();

        var excludedCallUID = profileDataGridNode.callUID;
        var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];

        // If we have a top level node that is excluded, get rid of it completely (not keeping children),
        // since bottom up data relies entirely on the root node.
        if (excludedTopLevelChild)
            this.children.remove(excludedTopLevelChild);

        var children = this.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            children[index]._exclude(excludedCallUID);

        if (this.lastComparator)
            this.sort(this.lastComparator, true);
    },

    _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate
}

WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;


/* TopDownProfileDataGridTree.js */

/*
 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TopDownProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*TopDownProfileDataGridTree*/ owningTree)
{
    var hasChildren = (profileNode.children && profileNode.children.length);

    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren);

    this._remainingChildren = profileNode.children;
}

WebInspector.TopDownProfileDataGridNode.prototype = {
    _sharedPopulate: function()
    {
        var children = this._remainingChildren;
        var childrenLength = children.length;

        for (var i = 0; i < childrenLength; ++i)
            this.appendChild(new WebInspector.TopDownProfileDataGridNode(this.profileView, children[i], this.tree));

        this._remainingChildren = null;
    },

    _exclude: function(aCallUID)
    {
        if (this._remainingChildren)
            this._populate();

        this._save();

        var children = this.children;
        var index = this.children.length;

        while (index--)
            children[index]._exclude(aCallUID);

        var child = this.childrenByCallUID[aCallUID];

        if (child)
            this._merge(child, true);
    }
}

WebInspector.TopDownProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;

WebInspector.TopDownProfileDataGridTree = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode)
{
    WebInspector.ProfileDataGridTree.call(this, profileView, profileNode);

    this._remainingChildren = profileNode.children;

    WebInspector.TopDownProfileDataGridNode.prototype._populate.call(this);
}

WebInspector.TopDownProfileDataGridTree.prototype = {
    focus: function(/*ProfileDataGridNode*/ profileDataGrideNode)
    {
        if (!profileDataGrideNode)
            return;

        this._save();
        profileDataGrideNode.savePosition();

        this.children = [profileDataGrideNode];
        this.totalTime = profileDataGrideNode.totalTime;
    },

    exclude: function(/*ProfileDataGridNode*/ profileDataGrideNode)
    {
        if (!profileDataGrideNode)
            return;

        this._save();

        var excludedCallUID = profileDataGrideNode.callUID;

        WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(this, excludedCallUID);

        if (this.lastComparator)
            this.sort(this.lastComparator, true);
    },

    restore: function()
    {
        if (!this._savedChildren)
            return;

        this.children[0].restorePosition();

        WebInspector.ProfileDataGridTree.prototype.restore.call(this);
    },

    _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge,

    _sharedPopulate: WebInspector.TopDownProfileDataGridNode.prototype._sharedPopulate
}

WebInspector.TopDownProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;

/* ProfileView.js */

/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// FIXME: Rename the file.

WebInspector.CPUProfileView = function(profile)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("profile-view");

    this.showSelfTimeAsPercent = true;
    this.showTotalTimeAsPercent = true;
    this.showAverageTimeAsPercent = true;

    var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true },
                    "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true },
                    "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true },
                    "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
                    "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };

    if (Preferences.samplingCPUProfiler) {
        delete columns.average;
        delete columns.calls;
    }

    this.dataGrid = new WebInspector.DataGrid(columns);
    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    this.element.appendChild(this.dataGrid.element);

    this.viewSelectElement = document.createElement("select");
    this.viewSelectElement.className = "status-bar-item";
    this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
    this.view = "Heavy";

    var heavyViewOption = document.createElement("option");
    heavyViewOption.label = WebInspector.UIString("Heavy (Bottom Up)");
    var treeViewOption = document.createElement("option");
    treeViewOption.label = WebInspector.UIString("Tree (Top Down)");
    this.viewSelectElement.appendChild(heavyViewOption);
    this.viewSelectElement.appendChild(treeViewOption);

    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);

    this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
    this.focusButton.disabled = true;
    this.focusButton.addEventListener("click", this._focusClicked.bind(this), false);

    this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
    this.excludeButton.disabled = true;
    this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false);

    this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
    this.resetButton.visible = false;
    this.resetButton.addEventListener("click", this._resetClicked.bind(this), false);

    this.profile = profile;

    var self = this;
    function profileCallback(profile)
    {
        self.profile.head = profile.head;
        self._assignParentsInProfile();
      
        self.profileDataGridTree = self.bottomUpProfileDataGridTree;
        self.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator("selfTime", false));
     
        self.refresh();
     
        self._updatePercentButton();
    }

    InspectorBackend.getProfile(this.profile.typeId, this.profile.uid, profileCallback);
}

WebInspector.CPUProfileView.prototype = {
    get statusBarItems()
    {
        return [this.viewSelectElement, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
    },

    get profile()
    {
        return this._profile;
    },

    set profile(profile)
    {
        this._profile = profile;
    },

    get bottomUpProfileDataGridTree()
    {
        if (!this._bottomUpProfileDataGridTree)
            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profile.head);
        return this._bottomUpProfileDataGridTree;
    },

    get topDownProfileDataGridTree()
    {
        if (!this._topDownProfileDataGridTree)
            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.head);
        return this._topDownProfileDataGridTree;
    },

    get currentTree()
    {
        return this._currentTree;
    },

    set currentTree(tree)
    {
        this._currentTree = tree;
        this.refresh();
    },

    get topDownTree()
    {
        if (!this._topDownTree) {
            this._topDownTree = WebInspector.TopDownTreeFactory.create(this.profile.head);
            this._sortProfile(this._topDownTree);
        }

        return this._topDownTree;
    },

    get bottomUpTree()
    {
        if (!this._bottomUpTree) {
            this._bottomUpTree = WebInspector.BottomUpTreeFactory.create(this.profile.head);
            this._sortProfile(this._bottomUpTree);
        }

        return this._bottomUpTree;
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.dataGrid.updateWidths();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.dataGrid)
            this.dataGrid.updateWidths();
    },

    refresh: function()
    {
        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;

        this.dataGrid.removeChildren();

        var children = this.profileDataGridTree.children;
        var count = children.length;

        for (var index = 0; index < count; ++index)
            this.dataGrid.appendChild(children[index]);

        if (selectedProfileNode)
            selectedProfileNode.selected = true;
    },

    refreshVisibleData: function()
    {
        var child = this.dataGrid.children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
    },

    refreshShowAsPercents: function()
    {
        this._updatePercentButton();
        this.refreshVisibleData();
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var profileNode = this._searchResults[i].profileNode;

                delete profileNode._searchMatchedSelfColumn;
                delete profileNode._searchMatchedTotalColumn;
                delete profileNode._searchMatchedCallsColumn;
                delete profileNode._searchMatchedFunctionColumn;

                profileNode.refresh();
            }
        }

        delete this._searchFinishedCallback;
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        query = query.trim();

        if (!query.length)
            return;

        this._searchFinishedCallback = finishedCallback;

        var greaterThan = (query.indexOf(">") === 0);
        var lessThan = (query.indexOf("<") === 0);
        var equalTo = (query.indexOf("=") === 0 || ((greaterThan || lessThan) && query.indexOf("=") === 1));
        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));

        var queryNumber = parseFloat(query);
        if (greaterThan || lessThan || equalTo) {
            if (equalTo && (greaterThan || lessThan))
                queryNumber = parseFloat(query.substring(2));
            else
                queryNumber = parseFloat(query.substring(1));
        }

        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);

        // Make equalTo implicitly true if it wasn't specified there is no other operator.
        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
            equalTo = true;

        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
        {
            delete profileDataGridNode._searchMatchedSelfColumn;
            delete profileDataGridNode._searchMatchedTotalColumn;
            delete profileDataGridNode._searchMatchedAverageColumn;
            delete profileDataGridNode._searchMatchedCallsColumn;
            delete profileDataGridNode._searchMatchedFunctionColumn;

            if (percentUnits) {
                if (lessThan) {
                    if (profileDataGridNode.selfPercent < queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent < queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                } else if (greaterThan) {
                    if (profileDataGridNode.selfPercent > queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent > queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }

                if (equalTo) {
                    if (profileDataGridNode.selfPercent == queryNumber)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalPercent == queryNumber)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }
            } else if (millisecondsUnits || secondsUnits) {
                if (lessThan) {
                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime < queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                } else if (greaterThan) {
                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime > queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }

                if (equalTo) {
                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedSelfColumn = true;
                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedTotalColumn = true;
                    if (profileDataGridNode.averageTime == queryNumberMilliseconds)
                        profileDataGridNode._searchMatchedAverageColumn = true;
                }
            } else {
                if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
                if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
                if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
                    profileDataGridNode._searchMatchedCallsColumn = true;
            }

            if (profileDataGridNode.functionName.hasSubstring(query, true) || profileDataGridNode.url.hasSubstring(query, true))
                profileDataGridNode._searchMatchedFunctionColumn = true;

            if (profileDataGridNode._searchMatchedSelfColumn ||
                profileDataGridNode._searchMatchedTotalColumn ||
                profileDataGridNode._searchMatchedAverageColumn ||
                profileDataGridNode._searchMatchedCallsColumn ||
                profileDataGridNode._searchMatchedFunctionColumn)
            {
                profileDataGridNode.refresh();
                return true;
            }

            return false;
        }

        var current = this.profileDataGridTree.children[0];

        while (current) {
            if (matchesQuery(current)) {
                this._searchResults.push({ profileNode: current });
            }

            current = current.traverseNextNode(false, null, false);
        }

        finishedCallback(this, this._searchResults.length);
    },

    jumpToFirstSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToLastSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToNextSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (++this._currentSearchResultIndex >= this._searchResults.length)
            this._currentSearchResultIndex = 0;
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    jumpToPreviousSearchResult: function()
    {
        if (!this._searchResults || !this._searchResults.length)
            return;
        if (--this._currentSearchResultIndex < 0)
            this._currentSearchResultIndex = (this._searchResults.length - 1);
        this._jumpToSearchResult(this._currentSearchResultIndex);
    },

    showingFirstSearchResult: function()
    {
        return (this._currentSearchResultIndex === 0);
    },

    showingLastSearchResult: function()
    {
        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
    },

    _jumpToSearchResult: function(index)
    {
        var searchResult = this._searchResults[index];
        if (!searchResult)
            return;

        var profileNode = searchResult.profileNode;
        profileNode.reveal();
        profileNode.select();
    },

    _changeView: function(event)
    {
        if (!event || !this.profile)
            return;

        if (event.target.selectedIndex == 1 && this.view == "Heavy") {
            this.profileDataGridTree = this.topDownProfileDataGridTree;
            this._sortProfile();
            this.view = "Tree";
        } else if (event.target.selectedIndex == 0 && this.view == "Tree") {
            this.profileDataGridTree = this.bottomUpProfileDataGridTree;
            this._sortProfile();
            this.view = "Heavy";
        }

        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again the with same query and callback.
        this._searchFinishedCallback(this, -this._searchResults.length);
        this.performSearch(this.currentQuery, this._searchFinishedCallback);
    },

    _percentClicked: function(event)
    {
        var currentState = this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent;
        this.showSelfTimeAsPercent = !currentState;
        this.showTotalTimeAsPercent = !currentState;
        this.showAverageTimeAsPercent = !currentState;
        this.refreshShowAsPercents();
    },

    _updatePercentButton: function()
    {
        if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent) {
            this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
            this.percentButton.toggled = true;
        } else {
            this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
            this.percentButton.toggled = false;
        }
    },

    _focusClicked: function(event)
    {
        if (!this.dataGrid.selectedNode)
            return;

        this.resetButton.visible = true;
        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
        this.refresh();
        this.refreshVisibleData();
    },

    _excludeClicked: function(event)
    {
        var selectedNode = this.dataGrid.selectedNode

        if (!selectedNode)
            return;

        selectedNode.deselect();

        this.resetButton.visible = true;
        this.profileDataGridTree.exclude(selectedNode);
        this.refresh();
        this.refreshVisibleData();
    },

    _resetClicked: function(event)
    {
        this.resetButton.visible = false;
        this.profileDataGridTree.restore();
        this.refresh();
        this.refreshVisibleData();
    },

    _dataGridNodeSelected: function(node)
    {
        this.focusButton.disabled = false;
        this.excludeButton.disabled = false;
    },

    _dataGridNodeDeselected: function(node)
    {
        this.focusButton.disabled = true;
        this.excludeButton.disabled = true;
    },

    _sortData: function(event)
    {
        this._sortProfile(this.profile);
    },

    _sortProfile: function()
    {
        var sortAscending = this.dataGrid.sortOrder === "ascending";
        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
        var sortProperty = {
                "average": "averageTime",
                "self": "selfTime",
                "total": "totalTime",
                "calls": "numberOfCalls",
                "function": "functionName"
            }[sortColumnIdentifier];

        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));

        this.refresh();
    },

    _mouseDownInDataGrid: function(event)
    {
        if (event.detail < 2)
            return;

        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
            return;

        if (cell.hasStyleClass("total-column"))
            this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent;
        else if (cell.hasStyleClass("self-column"))
            this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent;
        else if (cell.hasStyleClass("average-column"))
            this.showAverageTimeAsPercent = !this.showAverageTimeAsPercent;

        this.refreshShowAsPercents();

        event.preventDefault();
        event.stopPropagation();
    },

    _assignParentsInProfile: function()
    {
        var head = this.profile.head;
        head.parent = null;
        head.head = null;
        var nodesToTraverse = [ { parent: head, children: head.children } ];
        while (nodesToTraverse.length > 0) {
            var pair = nodesToTraverse.shift();
            var parent = pair.parent;
            var children = pair.children;
            var length = children.length;
            for (var i = 0; i < length; ++i) {
                children[i].head = head;
                children[i].parent = parent;
                if (children[i].children.length > 0)
                    nodesToTraverse.push({ parent: children[i], children: children[i].children });
            }
        }
    }
}

WebInspector.CPUProfileView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.CPUProfileType = function()
{
    WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("CPU PROFILES"));
    this._recording = false;
}

WebInspector.CPUProfileType.TypeId = "CPU";

WebInspector.CPUProfileType.prototype = {
    get buttonTooltip()
    {
        return this._recording ? WebInspector.UIString("Stop profiling.") : WebInspector.UIString("Start profiling.");
    },

    get buttonStyle()
    {
        return this._recording ? "record-profile-status-bar-item status-bar-item toggled-on" : "record-profile-status-bar-item status-bar-item";
    },

    buttonClicked: function()
    {
        this._recording = !this._recording;

        if (this._recording)
            InspectorBackend.startProfiling();
        else
            InspectorBackend.stopProfiling();
    },

    get welcomeMessage()
    {
        return WebInspector.UIString("Control CPU profiling by pressing the %s button on the status bar.");
    },

    setRecordingProfile: function(isProfiling)
    {
        this._recording = isProfiling;
    },

    createSidebarTreeElementForProfile: function(profile)
    {
        return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item");
    },

    createView: function(profile)
    {
        return new WebInspector.CPUProfileView(profile);
    }
}

WebInspector.CPUProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;

/* HeapSnapshotView.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.HeapSnapshotView = function(parent, profile)
{
    WebInspector.View.call(this);

    this.element.addStyleClass("heap-snapshot-view");

    this.parent = parent;
    this.parent.addEventListener("profile added", this._updateBaseOptions, this);

    this.showCountAsPercent = false;
    this.showSizeAsPercent = false;
    this.showCountDeltaAsPercent = false;
    this.showSizeDeltaAsPercent = false;

    this.categories = {
        code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
        data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
    };

    var summaryContainer = document.createElement("div");
    summaryContainer.id = "heap-snapshot-summary-container";

    this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
    this.countsSummaryBar.element.className = "heap-snapshot-summary";
    this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
    var countsLabel = document.createElement("div");
    countsLabel.className = "heap-snapshot-summary-label";
    countsLabel.textContent = WebInspector.UIString("Count");
    this.countsSummaryBar.element.appendChild(countsLabel);
    summaryContainer.appendChild(this.countsSummaryBar.element);

    this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
    this.sizesSummaryBar.element.className = "heap-snapshot-summary";
    this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
    var sizesLabel = document.createElement("label");
    sizesLabel.className = "heap-snapshot-summary-label";
    sizesLabel.textContent = WebInspector.UIString("Size");
    this.sizesSummaryBar.element.appendChild(sizesLabel);
    summaryContainer.appendChild(this.sizesSummaryBar.element);

    this.element.appendChild(summaryContainer);

    var columns = {
        cons: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
        count: { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
        size: { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
        // \xb1 is a "plus-minus" sign.
        countDelta: { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
        sizeDelta: { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true }
    };

    this.dataGrid = new WebInspector.DataGrid(columns);
    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    this.element.appendChild(this.dataGrid.element);

    this.profile = profile;

    this.baseSelectElement = document.createElement("select");
    this.baseSelectElement.className = "status-bar-item";
    this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
    this._updateBaseOptions();

    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);

    this._loadProfile(this.profile, profileCallback.bind(this));

    function profileCallback(profile)
    {
        var list = this._getProfiles();
        var profileIndex;
        for (var i = 0; i < list.length; ++i)
            if (list[i].uid === profile.uid) {
                profileIndex = i;
                break;
            }
        if (profileIndex > 0)
            this.baseSelectElement.selectedIndex = profileIndex - 1;
        else
            this.baseSelectElement.selectedIndex = profileIndex;
        this._resetDataGridList(resetCompleted.bind(this));
    }

    function resetCompleted()
    {
        this.refresh();
        this._updatePercentButton();
    }
}

WebInspector.HeapSnapshotView.prototype = {
    get statusBarItems()
    {
        return [this.baseSelectElement, this.percentButton.element];
    },

    get profile()
    {
        return this._profile;
    },

    set profile(profile)
    {
        this._profile = profile;
    },

    show: function(parentElement)
    {
        WebInspector.View.prototype.show.call(this, parentElement);
        this.dataGrid.updateWidths();
    },

    hide: function()
    {
        WebInspector.View.prototype.hide.call(this);
        this._currentSearchResultIndex = -1;
    },

    resize: function()
    {
        if (this.dataGrid)
            this.dataGrid.updateWidths();
    },

    refresh: function()
    {
        this.dataGrid.removeChildren();

        var children = this.snapshotDataGridList.children;
        var count = children.length;
        for (var index = 0; index < count; ++index)
            this.dataGrid.appendChild(children[index]);

        this._updateSummaryGraph();
    },

    refreshShowAsPercents: function()
    {
        this._updatePercentButton();
        this.refreshVisibleData();
    },

    _deleteSearchMatchedFlags: function(node)
    {
        delete node._searchMatchedConsColumn;
        delete node._searchMatchedCountColumn;
        delete node._searchMatchedSizeColumn;
        delete node._searchMatchedCountDeltaColumn;
        delete node._searchMatchedSizeDeltaColumn;
    },

    searchCanceled: function()
    {
        if (this._searchResults) {
            for (var i = 0; i < this._searchResults.length; ++i) {
                var profileNode = this._searchResults[i].profileNode;
                this._deleteSearchMatchedFlags(profileNode);
                profileNode.refresh();
            }
        }

        delete this._searchFinishedCallback;
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
    },

    performSearch: function(query, finishedCallback)
    {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        query = query.trim();

        if (!query.length)
            return;

        this._searchFinishedCallback = finishedCallback;

        var helper = WebInspector.HeapSnapshotView.SearchHelper;

        var operationAndNumber = helper.parseOperationAndNumber(query);
        var operation = operationAndNumber[0];
        var queryNumber = operationAndNumber[1];

        var percentUnits = helper.percents.test(query);
        var megaBytesUnits = helper.megaBytes.test(query);
        var kiloBytesUnits = helper.kiloBytes.test(query);
        var bytesUnits = helper.bytes.test(query);

        var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));

        function matchesQuery(heapSnapshotDataGridNode)
        {
            WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);

            if (percentUnits) {
                heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
            } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
                heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
            } else {
                heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
            }

            if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
                heapSnapshotDataGridNode._searchMatchedConsColumn = true;

            if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
                heapSnapshotDataGridNode._searchMatchedCountColumn ||
                heapSnapshotDataGridNode._searchMatchedSizeColumn ||
                heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
                heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
                heapSnapshotDataGridNode.refresh();
                return true;
            }

            return false;
        }

        var current = this.snapshotDataGridList.children[0];
        var depth = 0;
        var info = {};

        // The second and subsequent levels of heap snapshot nodes represent retainers,
        // so recursive expansion will be infinite, since a graph is being traversed.
        // So default to a recursion cap of 2 levels.
        const maxDepth = 2;

        while (current) {
            if (matchesQuery(current))
                this._searchResults.push({ profileNode: current });
            current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
            depth += info.depthChange;
        }

        finishedCallback(this, this._searchResults.length);
    },

    // FIXME: move these methods to a superclass, inherit both views from it.
    jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
    jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
    jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
    jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
    showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
    showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
    _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,

    refreshVisibleData: function()
    {
        var child = this.dataGrid.children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
        this._updateSummaryGraph();
    },

    _changeBase: function()
    {
        if (this.baseSnapshot.uid === this._getProfiles()[this.baseSelectElement.selectedIndex].uid)
            return;

        this._resetDataGridList(resetCompleted.bind(this));

        function resetCompleted() {
            this.refresh();

            if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
                return;

            // The current search needs to be performed again. First negate out previous match
            // count by calling the search finished callback with a negative number of matches.
            // Then perform the search again with the same query and callback.
            this._searchFinishedCallback(this, -this._searchResults.length);
            this.performSearch(this.currentQuery, this._searchFinishedCallback);
        }
    },

    _createSnapshotDataGridList: function()
    {
        if (this._snapshotDataGridList)
          delete this._snapshotDataGridList;

        this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
        return this._snapshotDataGridList;
    },

    _getProfiles: function()
    {
        return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId);
    },

    _loadProfile: function(profile, callback)
    {
        if (profile._loaded) {
            callback(profile);
            return;
        }

        InspectorBackend.getProfile(profile.typeId, profile.uid, loadedCallback.bind(this));

        function loadedCallback(loadedSnapshot) {
            profile.children = loadedSnapshot.head.children;
            profile.entries = loadedSnapshot.head.entries;
            profile.lowlevels = loadedSnapshot.head.lowlevels;
            this._prepareProfile(profile);
            profile._loaded = true;
            this.parent.updateProfile(profile);
            callback(profile);
        }
    },

    _mouseDownInDataGrid: function(event)
    {
        if (event.detail < 2)
            return;

        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
        if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
            return;

        if (cell.hasStyleClass("count-column"))
            this.showCountAsPercent = !this.showCountAsPercent;
        else if (cell.hasStyleClass("size-column"))
            this.showSizeAsPercent = !this.showSizeAsPercent;
        else if (cell.hasStyleClass("countDelta-column"))
            this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
        else if (cell.hasStyleClass("sizeDelta-column"))
            this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;

        this.refreshShowAsPercents();

        event.preventDefault();
        event.stopPropagation();
    },

    get _isShowingAsPercent()
    {
        return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
    },

    _percentClicked: function(event)
    {
        var currentState = this._isShowingAsPercent;
        this.showCountAsPercent = !currentState;
        this.showSizeAsPercent = !currentState;
        this.showCountDeltaAsPercent = !currentState;
        this.showSizeDeltaAsPercent = !currentState;
        this.refreshShowAsPercents();
    },

    _prepareProfile: function(profile)
    {
        for (var profileEntry in profile.entries)
            profile.entries[profileEntry].retainers = {};
        profile.clusters = {};

        for (var addr in profile.children) {
            var retainer = profile.children[addr];
            var retainerId = retainer.constructorName + ':' + addr;
            for (var childAddr in retainer) {
                if (childAddr === 'constructorName') continue;
                var item = retainer[childAddr];
                var itemId = item.constructorName + ':' + childAddr;
                if ((item.constructorName === 'Object' || item.constructorName === 'Array')) {
                    if (!(itemId in profile.clusters))
                        profile.clusters[itemId] = { constructorName: itemId, retainers: {} };
                    mergeRetainers(profile.clusters[itemId], item);
                }
                mergeRetainers(profile.entries[item.constructorName], item);
            }
        }

        function mergeRetainers(entry, item)
        {
            if (!(retainer.constructorName in entry.retainers))
               entry.retainers[retainer.constructorName] = { constructorName: retainer.constructorName, count: 0, clusters: {} };
            var retainerEntry = entry.retainers[retainer.constructorName];
            retainerEntry.count += item.count;
            if (retainer.constructorName === 'Object' || retainer.constructorName === 'Array')
                retainerEntry.clusters[retainerId] = true;
        }
    },

    _resetDataGridList: function(callback)
    {
        this._loadProfile(this._getProfiles()[this.baseSelectElement.selectedIndex], profileLoaded.bind(this));

        function profileLoaded(profile)
        {
            this.baseSnapshot = profile;
            var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
            if (this.snapshotDataGridList)
                lastComparator = this.snapshotDataGridList.lastComparator;
            this.snapshotDataGridList = this._createSnapshotDataGridList();
            this.snapshotDataGridList.sort(lastComparator, true);
            callback();
        }
    },

    _sortData: function()
    {
        var sortAscending = this.dataGrid.sortOrder === "ascending";
        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
        var sortProperty = {
            cons: ["cons", null],
            count: ["count", null],
            size: ["size", "count"],
            countDelta: this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null],
            sizeDelta: this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"]
        }[sortColumnIdentifier];

        this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));

        this.refresh();
    },

    _updateBaseOptions: function()
    {
        var list = this._getProfiles();
        // We're assuming that snapshots can only be added.
        if (this.baseSelectElement.length === list.length)
            return;

        for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
            var baseOption = document.createElement("option");
            var title = list[i].title;
            if (!title.indexOf(UserInitiatedProfileName))
                title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
            baseOption.label = WebInspector.UIString("Compared to %s", title);
            this.baseSelectElement.appendChild(baseOption);
        }
    },

    _updatePercentButton: function()
    {
        if (this._isShowingAsPercent) {
            this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
            this.percentButton.toggled = true;
        } else {
            this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
            this.percentButton.toggled = false;
        }
    },

    _updateSummaryGraph: function()
    {
        this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
        this.countsSummaryBar.update(this.profile.lowlevels);

        this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
        this.sizesSummaryBar.update(this.profile.lowlevels);
    }
};

WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;

WebInspector.HeapSnapshotView.SearchHelper = {
    // In comparators, we assume that a value from a node is passed as the first parameter.
    operations: {
        LESS: function (a, b) { return a !== null && a < b; },
        LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
        EQUAL: function (a, b) { return a !== null && a === b; },
        GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
        GREATER: function (a, b) { return a !== null && a > b; }
    },

    operationParsers: {
        LESS: /^<(\d+)/,
        LESS_OR_EQUAL: /^<=(\d+)/,
        GREATER_OR_EQUAL: /^>=(\d+)/,
        GREATER: /^>(\d+)/
    },

    parseOperationAndNumber: function(query)
    {
        var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
        var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
        for (var operation in parsers) {
            var match = query.match(parsers[operation]);
            if (match !== null)
                return [operations[operation], parseFloat(match[1])];
        }
        return [operations.EQUAL, parseFloat(query)];
    },

    percents: /%$/,

    megaBytes: /MB$/i,

    kiloBytes: /KB$/i,

    bytes: /B$/i
}

WebInspector.HeapSummaryCalculator = function(lowLevelField)
{
    this.total = 1;
    this.lowLevelField = lowLevelField;
}

WebInspector.HeapSummaryCalculator.prototype = {
    computeSummaryValues: function(lowLevels)
    {
        var highLevels = { data: 0, code: 0 };
        this.total = 0;
        for (var item in lowLevels) {
            var highItem = this._highFromLow(item);
            if (highItem) {
                var value = lowLevels[item][this.lowLevelField];
                highLevels[highItem] += value;
                this.total += value;
            }
        }
        var result = { categoryValues: highLevels };
        if (!this.showAsPercent)
            result.total = this.total;
        return result;
    },

    formatValue: function(value)
    {
        if (this.showAsPercent)
            return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
        else
            return this._valueToString(value);
    },

    get showAsPercent()
    {
        return this._showAsPercent;
    },

    set showAsPercent(x)
    {
        this._showAsPercent = x;
    }
}

WebInspector.HeapSummaryCountCalculator = function()
{
    WebInspector.HeapSummaryCalculator.call(this, "count");
}

WebInspector.HeapSummaryCountCalculator.prototype = {
    _highFromLow: function(type)
    {
        if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
        if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
        return null;
    },

    _valueToString: function(value)
    {
        return value.toString();
    }
}

WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;

WebInspector.HeapSummarySizeCalculator = function()
{
    WebInspector.HeapSummaryCalculator.call(this, "size");
}

WebInspector.HeapSummarySizeCalculator.prototype = {
    _highFromLow: function(type)
    {
        if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE")
            return "code";
        if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/))
            return "data";
        return null;
    },

    _valueToString: Number.bytesToString
}

WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;

WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
{
    this.tree = owningTree;

    WebInspector.DataGridNode.call(this, null, this._hasRetainers);

    this.addEventListener("populate", this._populate, this);
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
    isEmptySet: function(set)
    {
        for (var x in set)
            return false;
        return true;
    },

    get _hasRetainers()
    {
        return !this.isEmptySet(this.retainers);
    },

    get _parent()
    {
        // For top-level nodes, return owning tree as a parent, not data grid.
        return this.parent !== this.dataGrid ? this.parent : this.tree;
    },

    _populate: function(event)
    {
        function appendDiffEntry(baseItem, snapshotItem)
        {
            this.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(this.snapshotView, baseItem, snapshotItem, this.tree));
        }

        this.produceDiff(this.baseRetainers, this.retainers, appendDiffEntry.bind(this));

        if (this._parent) {
            var currentComparator = this._parent.lastComparator;
            if (currentComparator)
                this.sort(currentComparator, true);
        }

        this.removeEventListener("populate", this._populate, this);
    },

    produceDiff: function(baseEntries, currentEntries, callback)
    {
        for (var item in currentEntries)
            callback(baseEntries[item], currentEntries[item]);

        for (item in baseEntries) {
            if (!(item in currentEntries))
                callback(baseEntries[item], null);
        }
    },

    sort: function(comparator, force) {
        if (!force && this.lastComparator === comparator)
            return;

        this.children.sort(comparator);
        var childCount = this.children.length;
        for (var childIndex = 0; childIndex < childCount; ++childIndex)
            this.children[childIndex]._recalculateSiblings(childIndex);
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            if (!force && (!child.expanded || child.lastComparator === comparator))
                continue;
            child.sort(comparator, force);
        }
        this.lastComparator = comparator;
    },

    signForDelta: function(delta) {
        if (delta === 0)
            return "";
        if (delta > 0)
            return "+";
        else
            return "\u2212";  // Math minus sign, same width as plus.
    },

    showDeltaAsPercent: function(value)
    {
        if (value === Number.POSITIVE_INFINITY)
            return WebInspector.UIString("new");
        else if (value === Number.NEGATIVE_INFINITY)
            return WebInspector.UIString("deleted");
        if (value > 1000.0)
            return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
        return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
    },

    getTotalCount: function()
    {
        if (!this._count) {
            this._count = 0;
            for (var i = 0, n = this.children.length; i < n; ++i)
                this._count += this.children[i].count;
        }
        return this._count;
    },

    getTotalSize: function()
    {
        if (!this._size) {
            this._size = 0;
            for (var i = 0, n = this.children.length; i < n; ++i)
                this._size += this.children[i].size;
        }
        return this._size;
    },

    get countPercent()
    {
        return this.count / this._parent.getTotalCount() * 100.0;
    },

    get sizePercent()
    {
        return this.size / this._parent.getTotalSize() * 100.0;
    },

    get countDeltaPercent()
    {
        if (this.baseCount > 0) {
            if (this.count > 0)
                return this.countDelta / this.baseCount * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    },

    get sizeDeltaPercent()
    {
        if (this.baseSize > 0) {
            if (this.size > 0)
                return this.sizeDelta / this.baseSize * 100.0;
            else
                return Number.NEGATIVE_INFINITY;
        } else
            return Number.POSITIVE_INFINITY;
    },

    get data()
    {
        var data = {};

        data["cons"] = this.constructorName;

        if (this.snapshotView.showCountAsPercent)
            data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
        else
            data["count"] = this.count;

        if (this.size !== null) {
            if (this.snapshotView.showSizeAsPercent)
                data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
            else
                data["size"] = Number.bytesToString(this.size);
        } else
            data["size"] = "";

        if (this.snapshotView.showCountDeltaAsPercent)
            data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
        else
            data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));

        if (this.sizeDelta !== null) {
            if (this.snapshotView.showSizeDeltaAsPercent)
                data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
            else
                data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
        } else
            data["sizeDelta"] = "";

        return data;
    },

    createCell: function(columnIdentifier)
    {
        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);

        if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
            (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
            (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
            (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
            (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
            cell.addStyleClass("highlight");

        return cell;
    }
};

WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;

WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, size: 0, retainers: {} };
    this.constructorName = snapshotEntry.constructorName;
    this.count = snapshotEntry.count;
    this.size = snapshotEntry.size;
    this.retainers = snapshotEntry.retainers;

    if (!baseEntry)
        baseEntry = { count: 0, size: 0, retainers: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;
    this.baseSize = baseEntry.size;
    this.sizeDelta = this.size - this.baseSize;
    this.baseRetainers = baseEntry.retainers;

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
};

WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;

WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
{
    this.tree = this;
    this.snapshotView = snapshotView;
    this.children = [];
    this.lastComparator = null;
    this.populateChildren(baseEntries, snapshotEntries);
};

WebInspector.HeapSnapshotDataGridList.prototype = {
    appendChild: function(child)
    {
        this.insertChild(child, this.children.length);
    },

    insertChild: function(child, index)
    {
        this.children.splice(index, 0, child);
    },

    removeChildren: function()
    {
        this.children = [];
    },

    populateChildren: function(baseEntries, snapshotEntries)
    {
        function appendListEntry(baseItem, snapshotItem)
        {
            this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseItem, snapshotItem, this));
        }
        this.produceDiff(baseEntries, snapshotEntries, appendListEntry.bind(this));
    },

    produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
    sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
    getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
    getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
};

WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];

WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
{
    var propertyHash = property + "#" + property2;
    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
    if (!comparator) {
        comparator = function(lhs, rhs) {
            var l = lhs[property], r = rhs[property];
            if ((l === null || r === null) && property2 !== null)
                l = lhs[property2], r = rhs[property2];
            var result = l < r ? -1 : (l > r ? 1 : 0);
            return isAscending ? result : -result;
        };
        this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
    }
    return comparator;
};

WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
{
    this.snapshotView = snapshotView;

    if (!snapshotEntry)
        snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, clusters: {} };
    this.constructorName = snapshotEntry.constructorName;
    this.count = snapshotEntry.count;
    this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);

    if (!baseEntry)
        baseEntry = { count: 0, clusters: {} };
    this.baseCount = baseEntry.count;
    this.countDelta = this.count - this.baseCount;
    this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);

    this.size = null;
    this.sizeDelta = null;

    WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
}

WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
    get sizePercent()
    {
        return null;
    },

    get sizeDeltaPercent()
    {
        return null;
    },

    _calculateRetainers: function(snapshot, clusters)
    {
        var retainers = {};
        if (this.isEmptySet(clusters)) {
            if (this.constructorName in snapshot.entries)
                return snapshot.entries[this.constructorName].retainers;
        } else {
            // In case when an entry is retained by clusters, we need to gather up the list
            // of retainers by merging retainers of every cluster.
            // E.g. having such a tree:
            //   A
            //     Object:1  10
            //       X       3
            //       Y       4
            //     Object:2  5
            //       X       6
            //
            // will result in a following retainers list: X 9, Y 4.
            for (var clusterName in clusters) {
                if (clusterName in snapshot.clusters) {
                    var clusterRetainers = snapshot.clusters[clusterName].retainers;
                    for (var clusterRetainer in clusterRetainers) {
                        var clusterRetainerEntry = clusterRetainers[clusterRetainer];
                        if (!(clusterRetainer in retainers))
                            retainers[clusterRetainer] = { constructorName: clusterRetainerEntry.constructorName, count: 0, clusters: {} };
                        retainers[clusterRetainer].count += clusterRetainerEntry.count;
                        for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
                            retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
                    }
                }
            }
        }
        return retainers;
    }
};

WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;


WebInspector.HeapSnapshotProfileType = function()
{
    WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
}

WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";

WebInspector.HeapSnapshotProfileType.prototype = {
    get buttonTooltip()
    {
        return WebInspector.UIString("Take heap snapshot.");
    },

    get buttonStyle()
    {
        return "heap-snapshot-status-bar-item status-bar-item";
    },

    buttonClicked: function()
    {
        InspectorBackend.takeHeapSnapshot();
    },

    get welcomeMessage()
    {
        return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
    },

    createSidebarTreeElementForProfile: function(profile)
    {
        return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
    },

    createView: function(profile)
    {
        return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
    }
}

WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;

/* DOMAgent.js */

/*
 * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMNode = function(doc, payload) {
    this.ownerDocument = doc;

    this.id = payload.id;
    this.nodeType = payload.nodeType;
    this.nodeName = payload.nodeName;
    this.localName = payload.localName;
    this._nodeValue = payload.nodeValue;
    this.textContent = this.nodeValue;

    this.attributes = [];
    this._attributesMap = {};
    if (payload.attributes)
        this._setAttributesPayload(payload.attributes);

    this._childNodeCount = payload.childNodeCount;
    this.children = null;

    this.nextSibling = null;
    this.prevSibling = null;
    this.firstChild = null;
    this.lastChild = null;
    this.parentNode = null;

    if (payload.children)
        this._setChildrenPayload(payload.children);

    this._computedStyle = null;
    this.style = null;
    this._matchedCSSRules = [];

    if (this.nodeType === Node.ELEMENT_NODE) {
        // HTML and BODY from internal iframes should not overwrite top-level ones.
        if (!this.ownerDocument.documentElement && this.nodeName === "HTML")
            this.ownerDocument.documentElement = this;
        if (!this.ownerDocument.body && this.nodeName === "BODY")
            this.ownerDocument.body = this;
        if (payload.documentURL)
            this.documentURL = payload.documentURL;
    } else if (this.nodeType === Node.DOCUMENT_TYPE_NODE) {
        this.publicId = payload.publicId;
        this.systemId = payload.systemId;
        this.internalSubset = payload.internalSubset;
    } else if (this.nodeType === Node.DOCUMENT_NODE) {
        this.documentURL = payload.documentURL;
    } else if (this.nodeType === Node.ATTRIBUTE_NODE) {
        this.name = payload.name;
        this.value = payload.value;
    }
}

WebInspector.DOMNode.prototype = {
    hasAttributes: function()
    {
        return this.attributes.length > 0;
    },

    hasChildNodes: function()
    {
        return this._childNodeCount > 0;
    },

    get nodeValue() {
        return this._nodeValue;
    },

    set nodeValue(value) {
        if (this.nodeType != Node.TEXT_NODE)
            return;
        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, function() {});
    },

    getAttribute: function(name)
    {
        var attr = this._attributesMap[name];
        return attr ? attr.value : undefined;
    },

    setAttribute: function(name, value)
    {
        var self = this;
        var callback = function()
        {
            var attr = self._attributesMap[name];
            if (attr)
                attr.value = value;
            else
                attr = self._addAttribute(name, value);
        };
        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
    },

    removeAttribute: function(name)
    {
        var self = this;
        var callback = function()
        {
            delete self._attributesMap[name];
            for (var i = 0;  i < self.attributes.length; ++i) {
                if (self.attributes[i].name == name) {
                    self.attributes.splice(i, 1);
                    break;
                }
            }
        };
        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
    },

    path: function()
    {
        var path = [];
        var node = this;
        while (node && "index" in node && node.nodeName.length) {
            path.push([node.index, node.nodeName]);
            node = node.parentNode;
        }
        path.reverse();
        return path.join(",");
    },

    setBreakpoint: function(type)
    {
        return WebInspector.domBreakpointManager.setBreakpoint(this.id, type, true, this.path());
    },

    hasBreakpoint: function(type)
    {
        return !!WebInspector.domBreakpointManager.findBreakpoint(this.id, type);
    },

    removeBreakpoint: function(type)
    {
        var breakpoint = WebInspector.domBreakpointManager.findBreakpoint(this.id, type);
        if (breakpoint)
            breakpoint.remove();
    },

    removeBreakpoints: function()
    {
        WebInspector.domBreakpointManager.removeBreakpointsForNode(this.id);
    },

    _setAttributesPayload: function(attrs)
    {
        this.attributes = [];
        this._attributesMap = {};
        for (var i = 0; i < attrs.length; i += 2)
            this._addAttribute(attrs[i], attrs[i + 1]);
    },

    _insertChild: function(prev, payload)
    {
        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
        if (!prev) {
            if (!this.children) {
                // First node
                this.children = [ node ];
            } else
                this.children.unshift(node);
        } else
            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
        this._renumber();
        return node;
    },

    removeChild_: function(node)
    {
        this.children.splice(this.children.indexOf(node), 1);
        node.parentNode = null;
        this._renumber();
    },

    _setChildrenPayload: function(payloads)
    {
        this.children = [];
        for (var i = 0; i < payloads.length; ++i) {
            var payload = payloads[i];
            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
            this.children.push(node);
        }
        this._renumber();
    },

    _renumber: function()
    {
        this._childNodeCount = this.children.length;
        if (this._childNodeCount == 0) {
            this.firstChild = null;
            this.lastChild = null;
            return;
        }
        this.firstChild = this.children[0];
        this.lastChild = this.children[this._childNodeCount - 1];
        for (var i = 0; i < this._childNodeCount; ++i) {
            var child = this.children[i];
            child.index = i;
            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
            child.parentNode = this;
        }
    },

    _addAttribute: function(name, value)
    {
        var attr = {
            "name": name,
            "value": value,
            "_node": this
        };
        this._attributesMap[name] = attr;
        this.attributes.push(attr);
    }
}

WebInspector.DOMDocument = function(domAgent, defaultView, payload)
{
    WebInspector.DOMNode.call(this, this, payload);
    this._listeners = {};
    this._domAgent = domAgent;
    this.defaultView = defaultView;
}

WebInspector.DOMDocument.prototype = {

    addEventListener: function(name, callback)
    {
        var listeners = this._listeners[name];
        if (!listeners) {
            listeners = [];
            this._listeners[name] = listeners;
        }
        listeners.push(callback);
    },

    removeEventListener: function(name, callback)
    {
        var listeners = this._listeners[name];
        if (!listeners)
            return;

        var index = listeners.indexOf(callback);
        if (index != -1)
            listeners.splice(index, 1);
    },

    _fireDomEvent: function(name, event)
    {
        var listeners = this._listeners[name];
        if (!listeners)
            return;

        for (var i = 0; i < listeners.length; ++i) {
            var listener = listeners[i];
            listener.call(this, event);
        }
    }
}

WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;


WebInspector.DOMWindow = function(domAgent)
{
    this._domAgent = domAgent;
}

WebInspector.DOMWindow.prototype = {
    get document()
    {
        return this._domAgent.document;
    },

    get Node()
    {
        return WebInspector.DOMNode;
    },

    get Element()
    {
        return WebInspector.DOMNode;
    },

    Object: function()
    {
    }
}

WebInspector.DOMAgent = function() {
    this._window = new WebInspector.DOMWindow(this);
    this._idToDOMNode = null;
    this.document = null;
}

WebInspector.DOMAgent.prototype = {
    get domWindow()
    {
        return this._window;
    },

    getChildNodesAsync: function(parent, callback)
    {
        var children = parent.children;
        if (children) {
            callback(children);
            return;
        }
        function mycallback() {
            callback(parent.children);
        }
        InspectorBackend.getChildNodes(parent.id, mycallback);
    },

    setAttributeAsync: function(node, name, value, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorBackend.setAttribute(node.id, name, value, mycallback);
    },

    removeAttributeAsync: function(node, name, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorBackend.removeAttribute(node.id, name, mycallback);
    },

    setTextNodeValueAsync: function(node, text, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorBackend.setTextNodeValue(node.id, text, mycallback);
    },

    _didApplyDomChange: function(node, callback, success)
    {
        if (!success)
            return;
        callback();
        // TODO(pfeldman): Fix this hack.
        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
        if (elem)
            elem.updateTitle();
    },

    _attributesUpdated: function(nodeId, attrsArray)
    {
        var node = this._idToDOMNode[nodeId];
        node._setAttributesPayload(attrsArray);
        var event = {target: node};
        this.document._fireDomEvent("DOMAttrModified", event);
    },

    _characterDataModified: function(nodeId, newValue)
    {
        var node = this._idToDOMNode[nodeId];
        node._nodeValue = newValue;
        node.textContent = newValue;
        var event = { target : node };
        this.document._fireDomEvent("DOMCharacterDataModified", event);
    },

    nodeForId: function(nodeId)
    {
        return this._idToDOMNode[nodeId];
    },

    _setDocument: function(payload)
    {
        this._idToDOMNode = {};
        if (payload && "id" in payload) {
            this.document = new WebInspector.DOMDocument(this, this._window, payload);
            this._idToDOMNode[payload.id] = this.document;
            this._bindNodes(this.document.children);
            WebInspector.domBreakpointManager.restoreBreakpoints();
        } else
            this.document = null;
        WebInspector.panels.elements.setDocument(this.document);
    },

    _setDetachedRoot: function(payload)
    {
        var root = new WebInspector.DOMNode(this.document, payload);
        this._idToDOMNode[payload.id] = root;
    },

    _setChildNodes: function(parentId, payloads)
    {
        var parent = this._idToDOMNode[parentId];
        parent._setChildrenPayload(payloads);
        this._bindNodes(parent.children);
    },

    _bindNodes: function(children)
    {
        for (var i = 0; i < children.length; ++i) {
            var child = children[i];
            this._idToDOMNode[child.id] = child;
            if (child.children)
                this._bindNodes(child.children);
        }
    },

    _childNodeCountUpdated: function(nodeId, newValue)
    {
        var node = this._idToDOMNode[nodeId];
        node._childNodeCount = newValue;
        var outline = WebInspector.panels.elements.treeOutline;
        var treeElement = outline.findTreeElement(node);
        if (treeElement)
            treeElement.hasChildren = newValue;
    },

    _childNodeInserted: function(parentId, prevId, payload)
    {
        var parent = this._idToDOMNode[parentId];
        var prev = this._idToDOMNode[prevId];
        var node = parent._insertChild(prev, payload);
        this._idToDOMNode[node.id] = node;
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeInserted", event);
    },

    _childNodeRemoved: function(parentId, nodeId)
    {
        var parent = this._idToDOMNode[parentId];
        var node = this._idToDOMNode[nodeId];
        parent.removeChild_(node);
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeRemoved", event);
        delete this._idToDOMNode[nodeId];
        this._removeBreakpoints(node);
    },

    _removeBreakpoints: function(node)
    {
        node.removeBreakpoints();
        if (!node.children)
            return;
        for (var i = 0; i < node.children.length; ++i)
            this._removeBreakpoints(node.children[i]);
     }
}

WebInspector.ApplicationCache = {}

WebInspector.ApplicationCache.getApplicationCachesAsync = function(callback)
{
    function mycallback(applicationCaches)
    {
        // FIXME: Currently, this list only returns a single application cache.
        if (applicationCaches)
            callback(applicationCaches);
    }

    InspectorBackend.getApplicationCaches(mycallback);
}

WebInspector.Cookies = {}

WebInspector.Cookies.getCookiesAsync = function(callback)
{
    function mycallback(cookies, cookiesString)
    {
        if (cookiesString)
            callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
        else
            callback(cookies, true);
    }

    InspectorBackend.getCookies(mycallback);
}

WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
{
    var rawCookies = rawCookieString.split(/;\s*/);
    var cookies = [];

    if (!(/^\s*$/.test(rawCookieString))) {
        for (var i = 0; i < rawCookies.length; ++i) {
            var cookie = rawCookies[i];
            var delimIndex = cookie.indexOf("=");
            var name = cookie.substring(0, delimIndex);
            var value = cookie.substring(delimIndex + 1);
            var size = name.length + value.length;
            cookies.push({ name: name, value: value, size: size });
        }
    }

    return cookies;
}

WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL)
{
    var match = resourceURL.match(WebInspector.GenericURLRegExp);
    if (!match)
        return false;
    // See WebInspector.URLRegExp for definitions of the group index constants.
    if (!this.cookieDomainMatchesResourceDomain(cookie.domain, match[2]))
        return false;
    var resourcePort = match[3] ? match[3] : undefined;
    var resourcePath = match[4] ? match[4] : '/';
    return (resourcePath.indexOf(cookie.path) === 0
        && (!cookie.port || resourcePort == cookie.port)
        && (!cookie.secure || match[1].toLowerCase() === 'https'));
}

WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain)
{
    if (cookieDomain.charAt(0) !== '.')
        return resourceDomain === cookieDomain;
    return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)?" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i");
}

WebInspector.EventListeners = {}

WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
{
    if (!node)
        return;
    InspectorBackend.getEventListenersForNode(node.id, callback);
}

WebInspector.CSSStyleDeclaration = function(payload)
{
    this.id = payload.id;
    this.parentStyleSheetId = payload.parentStyleSheetId;
    this.width = payload.width;
    this.height = payload.height;
    this.__disabledProperties = {};
    this.__disabledPropertyValues = {};
    this.__disabledPropertyPriorities = {};
    if (payload.disabled) {
        for (var i = 0; i < payload.disabled.length; ++i) {
            var property = payload.disabled[i];
            this.__disabledProperties[property.name] = true;
            this.__disabledPropertyValues[property.name] = property.value;
            this.__disabledPropertyPriorities[property.name] = property.priority;
        }
    }

    this._shorthandValues = payload.shorthandValues;
    this._propertyMap = {};
    this._longhandProperties = {};
    this.length = payload.properties.length;

    for (var i = 0; i < this.length; ++i) {
        var property = payload.properties[i];
        var name = property.name;
        this[i] = name;
        this._propertyMap[name] = property;

        // Index longhand properties.
        if (property.shorthand) {
            var longhands = this._longhandProperties[property.shorthand];
            if (!longhands) {
                longhands = [];
                this._longhandProperties[property.shorthand] = longhands;
            }
            longhands.push(name);
        }
    }
}

WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
{
    return new WebInspector.CSSStyleDeclaration(payload);
}

WebInspector.CSSStyleDeclaration.parseRule = function(payload)
{
    var rule = {};
    rule.id = payload.id;
    rule.selectorText = payload.selectorText;
    rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
    rule.style.parentRule = rule;
    rule.isUserAgent = payload.isUserAgent;
    rule.isUser = payload.isUser;
    rule.isViaInspector = payload.isViaInspector;
    rule.sourceLine = payload.sourceLine;
    rule.documentURL = payload.documentURL;
    if (payload.parentStyleSheet)
        rule.parentStyleSheet = { href: payload.parentStyleSheet.href };

    return rule;
}

WebInspector.CSSStyleDeclaration.prototype = {
    getPropertyValue: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.value : "";
    },

    getPropertyPriority: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.priority : "";
    },

    getPropertyShorthand: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.shorthand : "";
    },

    isPropertyImplicit: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.implicit : "";
    },

    styleTextWithShorthands: function()
    {
        var cssText = "";
        var foundProperties = {};
        for (var i = 0; i < this.length; ++i) {
            var individualProperty = this[i];
            var shorthandProperty = this.getPropertyShorthand(individualProperty);
            var propertyName = (shorthandProperty || individualProperty);

            if (propertyName in foundProperties)
                continue;

            if (shorthandProperty) {
                var value = this.getShorthandValue(shorthandProperty);
                var priority = this.getShorthandPriority(shorthandProperty);
            } else {
                var value = this.getPropertyValue(individualProperty);
                var priority = this.getPropertyPriority(individualProperty);
            }

            foundProperties[propertyName] = true;

            cssText += propertyName + ": " + value;
            if (priority)
                cssText += " !" + priority;
            cssText += "; ";
        }

        return cssText;
    },

    getLonghandProperties: function(name)
    {
        return this._longhandProperties[name] || [];
    },

    getShorthandValue: function(shorthandProperty)
    {
        return this._shorthandValues[shorthandProperty];
    },

    getShorthandPriority: function(shorthandProperty)
    {
        var priority = this.getPropertyPriority(shorthandProperty);
        if (priority)
            return priority;

        var longhands = this._longhandProperties[shorthandProperty];
        return longhands ? this.getPropertyPriority(longhands[0]) : null;
    }
}

WebInspector.attributesUpdated = function()
{
    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
}

WebInspector.characterDataModified = function()
{
    this.domAgent._characterDataModified.apply(this.domAgent, arguments);
}

WebInspector.setDocument = function()
{
    this.domAgent._setDocument.apply(this.domAgent, arguments);
}

WebInspector.setDetachedRoot = function()
{
    this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
}

WebInspector.setChildNodes = function()
{
    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
}

WebInspector.childNodeCountUpdated = function()
{
    this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
}

WebInspector.childNodeInserted = function()
{
    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
}

WebInspector.childNodeRemoved = function()
{
    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
}

WebInspector.DOMBreakpointManager = function()
{
    this._breakpoints = {};
    this._pathCache = {};
}

WebInspector.DOMBreakpointManager.prototype = {
    setBreakpoint: function(nodeId, type, enabled, path)
    {
        if (!(nodeId in this._breakpoints))
            this._breakpoints[nodeId] = {};
        else if (type in this._breakpoints[nodeId])
            return;

        var breakpoint = new WebInspector.DOMBreakpoint(nodeId, type, enabled);
        this._breakpoints[nodeId][type] = breakpoint;
        breakpoint.addEventListener("removed", this._breakpointRemoved, this);

        if (!(nodeId in this._pathCache))
            this._pathCache[nodeId] = path;

        this.dispatchEventToListeners("dom-breakpoint-added", breakpoint);
    },

    findBreakpoint: function(nodeId, type)
    {
        var nodeBreakpoints = this._breakpoints[nodeId];
        if (nodeBreakpoints && type in nodeBreakpoints)
            return nodeBreakpoints[type];
    },

    removeBreakpointsForNode: function(nodeId)
    {
        var nodeBreakpoints = this._breakpoints[nodeId];
        for (var type in nodeBreakpoints)
            nodeBreakpoints[type].remove();
    },

    _breakpointRemoved: function(event)
    {
        var breakpoint = event.target;

        var nodeBreakpoints = this._breakpoints[breakpoint.nodeId];
        delete nodeBreakpoints[breakpoint.type];
        for (var type in nodeBreakpoints)
            return;

        delete this._breakpoints[breakpoint.nodeId];
        delete this._pathCache[breakpoint.nodeId];
    },

    restoreBreakpoints: function()
    {
        var breakpoints = this._breakpoints;
        this._breakpoints = {};
        var pathCache = this._pathCache;
        this._pathCache = {};

        for (var oldNodeId in breakpoints) {
            var path = pathCache[oldNodeId];
            InspectorBackend.pushNodeByPathToFrontend(path, restoreBreakpointsForNode.bind(this, breakpoints[oldNodeId], path));
        }

        function restoreBreakpointsForNode(nodeBreakpoints, path, nodeId)
        {
            if (!nodeId)
                return;
            for (var type in nodeBreakpoints) {
                var breakpoint = nodeBreakpoints[type];
                this.setBreakpoint(nodeId, breakpoint.type, breakpoint.enabled, path);
            }
        }
    }
}

WebInspector.DOMBreakpointManager.prototype.__proto__ = WebInspector.Object.prototype;

WebInspector.DOMBreakpoint = function(nodeId, type, enabled)
{
    this._nodeId = nodeId;
    this._type = type;
    this._enabled = enabled;

    if (this.enabled)
        InspectorBackend.setDOMBreakpoint(this.nodeId, this.type);
}

WebInspector.DOMBreakpoint.Types = {
    SubtreeModified: 0,
    AttributeModified: 1,
    NodeRemoved: 2
};

WebInspector.DOMBreakpoint.labelForType = function(type)
{
    if (!WebInspector.DOMBreakpoint._labels) {
        WebInspector.DOMBreakpoint._labels = {};
        WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.SubtreeModified] = WebInspector.UIString("Subtree Modified");
        WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.AttributeModified] = WebInspector.UIString("Attribute Modified");
        WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.NodeRemoved] = WebInspector.UIString("Node Removed");
    }
    return WebInspector.DOMBreakpoint._labels[type];
}

WebInspector.DOMBreakpoint.contextMenuLabelForType = function(type)
{
    if (!WebInspector.DOMBreakpoint._contextMenuLabels) {
        WebInspector.DOMBreakpoint._contextMenuLabels = {};
        WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.SubtreeModified] = WebInspector.UIString("Break on Subtree Modifications");
        WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.AttributeModified] = WebInspector.UIString("Break on Attributes Modifications");
        WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.NodeRemoved] = WebInspector.UIString("Break on Node Removal");
    }
    return WebInspector.DOMBreakpoint._contextMenuLabels[type];
}

WebInspector.DOMBreakpoint.prototype = {
    get nodeId()
    {
        return this._nodeId;
    },

    get type()
    {
        return this._type;
    },

    get enabled()
    {
        return this._enabled;
    },

    set enabled(enabled)
    {
        if (this._enabled === enabled)
            return;

        this._enabled = enabled;
        if (this.enabled)
            InspectorBackend.setDOMBreakpoint(this.nodeId, this.type);
        else
            InspectorBackend.removeDOMBreakpoint(this.nodeId, this.type);

        this.dispatchEventToListeners("enable-changed");
    },

    compareTo: function(other)
    {
        if (this.type != other.type)
            return this.type < other.type ? -1 : 1;
        return 0;
    },

    remove: function()
    {
        if (this.enabled)
            InspectorBackend.removeDOMBreakpoint(this.nodeId, this.type);
        this.dispatchEventToListeners("removed");
    }
}

WebInspector.DOMBreakpoint.prototype.__proto__ = WebInspector.Object.prototype;


/* InjectedScript.js */

/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var injectedScriptConstructor = (function (InjectedScriptHost, inspectedWindow, injectedScriptId, jsEngine) {

var InjectedScript = function()
{
    this._lastBoundObjectId = 1;
    this._idToWrappedObject = {};
    this._objectGroups = {};
}

InjectedScript.prototype = {
    wrapObjectForConsole: function(object, canAccessInspectedWindow)
    {
        if (canAccessInspectedWindow)
            return this._wrapObject(object, "console");
        var result = {};
        result.type = typeof object;
        result.description = this._toString(object);
        return result;
    },

    _wrapObject: function(object, objectGroupName, abbreviate)
    {
        try {
            var objectId;
            if (typeof object === "object" || typeof object === "function" || this._isHTMLAllCollection(object)) {
                var id = this._lastBoundObjectId++;
                objectId = id;
                this._idToWrappedObject[id] = object;
    
                var group = this._objectGroups[objectGroupName];
                if (!group) {
                    group = [];
                    this._objectGroups[objectGroupName] = group;
                }
                group.push(id);
                objectId = this._serializeObjectId(id, objectGroupName);
            }
            return InjectedScript.RemoteObject.fromObject(object, objectId, abbreviate);
        } catch (e) {
            return InjectedScript.RemoteObject.fromObject("[ Exception: " + e.toString() + " ]");
        }
    },

    _serializeObjectId: function(id, groupName)
    {
        return injectedScriptId + ":" + id + ":" + groupName;
    },

    _parseObjectId: function(objectId)
    {
        var tokens = objectId.split(":");
        var parsedObjectId = {};
        parsedObjectId.id = parseInt(tokens[1]);
        parsedObjectId.groupName = tokens[2];
        return parsedObjectId;
    },

    releaseWrapperObjectGroup: function(objectGroupName)
    {
        var group = this._objectGroups[objectGroupName];
        if (!group)
            return;
        for (var i = 0; i < group.length; i++)
            delete this._idToWrappedObject[group[i]];
        delete this._objectGroups[objectGroupName];
    },

    dispatch: function(methodName, args)
    {
        var argsArray = eval("(" + args + ")");
        var result = this[methodName].apply(this, argsArray);
        if (typeof result === "undefined") {
            inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
            result = null;
        }
        return result;
    },

    getPrototypes: function(nodeId)
    {
        this.releaseWrapperObjectGroup("prototypes");
        var node = this._nodeForId(nodeId);
        if (!node)
            return false;

        var result = [];
        var prototype = node;
        do {
            result.push(this._wrapObject(prototype, "prototypes"));
            prototype = prototype.__proto__;
        } while (prototype)
        return result;
    },

    getProperties: function(objectId, ignoreHasOwnProperty, abbreviate)
    {
        var parsedObjectId = this._parseObjectId(objectId);
        var object = this._objectForId(parsedObjectId);
        if (!this._isDefined(object))
            return false;
        var properties = [];

        var propertyNames = ignoreHasOwnProperty ? this._getPropertyNames(object) : Object.getOwnPropertyNames(object);
        if (!ignoreHasOwnProperty && object.__proto__)
            propertyNames.push("__proto__");
    
        // Go over properties, prepare results.
        for (var i = 0; i < propertyNames.length; ++i) {
            var propertyName = propertyNames[i];
    
            var property = {};
            property.name = propertyName + "";
            var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
            if (!isGetter) {
                try {
                    property.value = this._wrapObject(object[propertyName], parsedObjectId.groupName, abbreviate);
                } catch(e) {
                    property.value = new InjectedScript.RemoteObject.fromException(e);
                }
            } else {
                // FIXME: this should show something like "getter" (bug 16734).
                property.value = new InjectedScript.RemoteObject.fromObject("\u2014"); // em dash
                property.isGetter = true;
            }
            properties.push(property);
        }
        return properties;
    },

    setPropertyValue: function(objectId, propertyName, expression)
    {
        var parsedObjectId = this._parseObjectId(objectId);
        var object = this._objectForId(parsedObjectId);
        if (!this._isDefined(object))
            return false;
    
        var expressionLength = expression.length;
        if (!expressionLength) {
            delete object[propertyName];
            return !(propertyName in object);
        }
    
        try {
            // Surround the expression in parenthesis so the result of the eval is the result
            // of the whole expression not the last potential sub-expression.
    
            // There is a regression introduced here: eval is now happening against global object,
            // not call frame while on a breakpoint.
            // TODO: bring evaluation against call frame back.
            var result = inspectedWindow.eval("(" + expression + ")");
            // Store the result in the property.
            object[propertyName] = result;
            return true;
        } catch(e) {
            try {
                var result = inspectedWindow.eval("\"" + expression.replace(/"/g, "\\\"") + "\"");
                object[propertyName] = result;
                return true;
            } catch(e) {
                return false;
            }
        }
    },

    _populatePropertyNames: function(object, resultSet)
    {
        for (var o = object; o; o = o.__proto__) {
            try {
                var names = Object.getOwnPropertyNames(o);
                for (var i = 0; i < names.length; ++i)
                    resultSet[names[i]] = true;
            } catch (e) {
            }
        }
    },

    _getPropertyNames: function(object, resultSet)
    {
        var propertyNameSet = {};
        this._populatePropertyNames(object, propertyNameSet);
        return Object.keys(propertyNameSet);
    },

    getCompletions: function(expression, includeInspectorCommandLineAPI, callFrameId)
    {
        var props = {};
        try {
            var expressionResult;
            // Evaluate on call frame if call frame id is available.
            if (typeof callFrameId === "number") {
                var callFrame = this._callFrameForId(callFrameId);
                if (!callFrame)
                    return props;
                if (expression)
                    expressionResult = this._evaluateOn(callFrame.evaluate, callFrame, expression, true);
                else {
                    // Evaluate into properties in scope of the selected call frame.
                    var scopeChain = callFrame.scopeChain;
                    for (var i = 0; i < scopeChain.length; ++i)
                        this._populatePropertyNames(scopeChain[i], props);
                }
            } else {
                if (!expression)
                    expression = "this";
                expressionResult = this._evaluateOn(inspectedWindow.eval, inspectedWindow, expression, false);
            }
            if (typeof expressionResult === "object")
                this._populatePropertyNames(expressionResult, props);
    
            if (includeInspectorCommandLineAPI) {
                for (var prop in this._commandLineAPI)
                    props[prop] = true;
            }
        } catch(e) {
        }
        return props;
    },

    evaluate: function(expression, objectGroup)
    {
        return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false);
    },

    _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame)
    {
        try {
            return this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame), objectGroup);
        } catch (e) {
            return InjectedScript.RemoteObject.fromException(e);
        }
    },

    _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame)
    {
        // Only install command line api object for the time of evaluation.
        // Surround the expression in with statements to inject our command line API so that
        // the window object properties still take more precedent than our API functions.
        inspectedWindow.console._commandLineAPI = this._commandLineAPI;
    
        // We don't want local variables to be shadowed by global ones when evaluating on CallFrame.
        if (!isEvalOnCallFrame)
            expression = "with (window) {\n" + expression + "\n} ";
        expression = "with (window ? window.console._commandLineAPI : {}) {\n" + expression + "\n}";
        var value = evalFunction.call(object, expression);
    
        delete inspectedWindow.console._commandLineAPI;
    
        // When evaluating on call frame error is not thrown, but returned as a value.
        if (this._type(value) === "error")
            throw value.toString();
    
        return value;
    },

    getNodeId: function(node)
    {
        return InjectedScriptHost.pushNodePathToFrontend(node, false, false);
    },

    callFrames: function()
    {
        var callFrame = InjectedScriptHost.currentCallFrame();
        if (!callFrame)
            return false;
    
        injectedScript.releaseWrapperObjectGroup("backtrace");
        var result = [];
        var depth = 0;
        do {
            result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
            callFrame = callFrame.caller;
        } while (callFrame);
        return result;
    },

    evaluateInCallFrame: function(callFrameId, code, objectGroup)
    {
        var callFrame = this._callFrameForId(callFrameId);
        if (!callFrame)
            return false;
        return this._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup, true);
    },

    _callFrameForId: function(id)
    {
        var callFrame = InjectedScriptHost.currentCallFrame();
        while (--id >= 0 && callFrame)
            callFrame = callFrame.caller;
        return callFrame;
    },

    _nodeForId: function(nodeId)
    {
        if (!nodeId)
            return null;
        return InjectedScriptHost.nodeForId(nodeId);
    },

    _objectForId: function(parsedObjectId)
    {
        return this._idToWrappedObject[parsedObjectId.id];
    },

    resolveNode: function(nodeId)
    {
        var node = this._nodeForId(nodeId);
        if (!node)
            return false;
        // FIXME: receive the object group from client.
        return this._wrapObject(node, "prototype");
    },

    getNodeProperties: function(nodeId, properties)
    {
        var node = this._nodeForId(nodeId);
        if (!node)
            return false;
        var result = {};
        for (var i = 0; i < properties.length; ++i)
            result[properties[i]] = node[properties[i]];
        return result;
    },

    pushNodeToFrontend: function(objectId)
    {
        var parsedObjectId = this._parseObjectId(objectId);
        var object = this._objectForId(parsedObjectId);
        if (!object || this._type(object) !== "node")
            return false;
        return InjectedScriptHost.pushNodePathToFrontend(object, false, false);
    },

    evaluateOnSelf: function(funcBody, args)
    {
        var func = window.eval("(" + funcBody + ")");
        return func.apply(this, args || []);
    },

    _isDefined: function(object)
    {
        return object || this._isHTMLAllCollection(object);
    },

    _isHTMLAllCollection: function(object)
    {
        // document.all is reported as undefined, but we still want to process it.
        return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection;
    },

    _type: function(obj)
    {
        if (obj === null)
            return "null";

        var type = typeof obj;
        if (type !== "object" && type !== "function") {
            // FIXME(33716): typeof document.all is always 'undefined'.
            if (this._isHTMLAllCollection(obj))
                return "array";
            return type;
        }

        // If owning frame has navigated to somewhere else window properties will be undefined.
        // In this case just return result of the typeof.
        if (!inspectedWindow.document)
            return type;

        if (obj instanceof inspectedWindow.Node)
            return (obj.nodeType === undefined ? type : "node");
        if (obj instanceof inspectedWindow.String)
            return "string";
        if (obj instanceof inspectedWindow.Array)
            return "array";
        if (obj instanceof inspectedWindow.Boolean)
            return "boolean";
        if (obj instanceof inspectedWindow.Number)
            return "number";
        if (obj instanceof inspectedWindow.Date)
            return "date";
        if (obj instanceof inspectedWindow.RegExp)
            return "regexp";
        // FireBug's array detection.
        if (isFinite(obj.length) && typeof obj.splice === "function")
            return "array";
        if (isFinite(obj.length) && typeof obj.callee === "function") // arguments.
            return "array";
        if (obj instanceof inspectedWindow.NodeList)
            return "array";
        if (obj instanceof inspectedWindow.HTMLCollection)
            return "array";
        if (obj instanceof inspectedWindow.Error)
            return "error";
        return type;
    },

    _describe: function(obj, abbreviated)
    {
        var type = this._type(obj);

        switch (type) {
        case "object":
        case "node":
            return this._className(obj);
        case "array":
            var className = this._className(obj);
            if (typeof obj.length === "number")
                className += "[" + obj.length + "]";
            return className;
        case "string":
            if (!abbreviated)
                return obj;
            if (obj.length > 100)
                return "\"" + obj.substring(0, 100) + "\u2026\"";
            return "\"" + obj + "\"";
        case "function":
            var objectText = this._toString(obj);
            if (abbreviated)
                objectText = /.*/.exec(objectText)[0].replace(/ +$/g, "");
            return objectText;
        default:
            return this._toString(obj);
        }
    },

    _toString: function(obj)
    {
        // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
        return "" + obj;
    },

    _className: function(obj)
    {
        // We can't use the same code for fetching class names of the dom bindings prototype chain.
        // Both of the methods below result in "Object" names on the foreign engine bindings.
        // I gave up and am using a check below to distinguish between the egine bingings.

        if (jsEngine == "JSC") {
            var str = inspectedWindow.Object ? inspectedWindow.Object.prototype.toString.call(obj) : this._toString(obj);
            return str.replace(/^\[object (.*)\]$/i, "$1");
        } else {
            // V8
            if (isFinite(obj.length) && typeof obj.callee === "function") {
                // Arguments.constructor === Object in V8
                return "Arguments";
            }
            return obj.constructor && obj.constructor.name || "Object";
        }
    },

    _logEvent: function(event)
    {
        console.log(event.type, event);
    },

    _normalizeEventTypes: function(types)
    {
        if (typeof types === "undefined")
            types = [ "mouse", "key", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll" ];
        else if (typeof types === "string")
            types = [ types ];

        var result = [];
        for (var i = 0; i < types.length; i++) {
            if (types[i] === "mouse")
                result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout");
            else if (types[i] === "key")
                result.splice(0, 0, "keydown", "keyup", "keypress");
            else
                result.push(types[i]);
        }
        return result;
    },

    _inspectedNode: function(num)
    {
        var nodeId = InjectedScriptHost.inspectedNode(num);
        return this._nodeForId(nodeId);
    },

    _bindToScript: function(func)
    {
        var args = Array.prototype.slice.call(arguments, 1);
        function bound()
        {
            return func.apply(injectedScript, args.concat(Array.prototype.slice.call(arguments)));
        }
        bound.toString = function() {
            return "bound: " + func;
        };
        return bound;
    }
}

var injectedScript = new InjectedScript();

InjectedScript.RemoteObject = function(objectId, type, description, hasChildren)
{
    this.objectId = objectId;
    this.type = type;
    this.description = description;
    this.hasChildren = hasChildren;
}

InjectedScript.RemoteObject.fromException = function(e)
{
    return new InjectedScript.RemoteObject(null, "error", e.toString());
}

InjectedScript.RemoteObject.fromObject = function(object, objectId, abbreviate)
{
    var type = injectedScript._type(object);
    var rawType = typeof object;
    var hasChildren = (rawType === "object" && object !== null && (Object.getOwnPropertyNames(object).length || !!object.__proto__)) || rawType === "function";
    var description = "";
    try {
        var description = injectedScript._describe(object, abbreviate);
        return new InjectedScript.RemoteObject(objectId, type, description, hasChildren);
    } catch (e) {
        return InjectedScript.RemoteObject.fromException(e);
    }
}

InjectedScript.CallFrameProxy = function(id, callFrame)
{
    this.id = id;
    this.type = callFrame.type;
    this.functionName = (this.type === "function" ? callFrame.functionName : "");
    this.sourceID = callFrame.sourceID;
    this.line = callFrame.line;
    this.scopeChain = this._wrapScopeChain(callFrame);
    this.worldId = injectedScriptId;
}

InjectedScript.CallFrameProxy.prototype = {
    _wrapScopeChain: function(callFrame)
    {
        const GLOBAL_SCOPE = 0;
        const LOCAL_SCOPE = 1;
        const WITH_SCOPE = 2;
        const CLOSURE_SCOPE = 3;
        const CATCH_SCOPE = 4;
    
        var scopeChain = callFrame.scopeChain;
        var scopeChainProxy = [];
        var foundLocalScope = false;
        for (var i = 0; i < scopeChain.length; i++) {
            var scopeType = callFrame.scopeType(i);
            var scopeObject = scopeChain[i];
            var scopeObjectProxy = injectedScript._wrapObject(scopeObject, "backtrace", true);

            switch(scopeType) {
                case LOCAL_SCOPE: {
                    foundLocalScope = true;
                    scopeObjectProxy.isLocal = true;
                    scopeObjectProxy.thisObject = injectedScript._wrapObject(callFrame.thisObject, "backtrace", true);
                    break;
                }
                case CLOSURE_SCOPE: {
                    scopeObjectProxy.isClosure = true;
                    break;
                }
                case WITH_SCOPE:
                case CATCH_SCOPE: {
                    if (foundLocalScope && scopeObject instanceof inspectedWindow.Element)
                        scopeObjectProxy.isElement = true;
                    else if (foundLocalScope && scopeObject instanceof inspectedWindow.Document)
                        scopeObjectProxy.isDocument = true;
                    else
                        scopeObjectProxy.isWithBlock = true;
                    break;
                }
            }
            scopeChainProxy.push(scopeObjectProxy);
        }
        return scopeChainProxy;
    }
}

function CommandLineAPI()
{
    for (var i = 0; i < 5; ++i)
        this.__defineGetter__("$" + i, injectedScript._bindToScript(injectedScript._inspectedNode, i));
}

CommandLineAPI.prototype = {
    // Only add API functions here, private stuff should go to
    // InjectedScript so that it is not suggested by the completion.
    $: function()
    {
        return document.getElementById.apply(document, arguments)
    },

    $$: function()
    {
        return document.querySelectorAll.apply(document, arguments)
    },

    $x: function(xpath, context)
    {
        var nodes = [];
        try {
            var doc = context || document;
            var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null);
            var node;
            while (node = results.iterateNext())
                nodes.push(node);
        } catch (e) {
        }
        return nodes;
    },

    dir: function()
    {
        return console.dir.apply(console, arguments)
    },

    dirxml: function()
    {
        return console.dirxml.apply(console, arguments)
    },

    keys: function(object)
    {
        return Object.keys(object);
    },

    values: function(object)
    {
        var result = [];
        for (var key in object)
            result.push(object[key]);
        return result;
    },

    profile: function()
    {
        return console.profile.apply(console, arguments)
    },

    profileEnd: function()
    {
        return console.profileEnd.apply(console, arguments)
    },

    monitorEvents: function(object, types)
    {
        if (!object || !object.addEventListener || !object.removeEventListener)
            return;
        types = injectedScript._normalizeEventTypes(types);
        for (var i = 0; i < types.length; ++i) {
            object.removeEventListener(types[i], injectedScript._logEvent, false);
            object.addEventListener(types[i], injectedScript._logEvent, false);
        }
    },

    unmonitorEvents: function(object, types)
    {
        if (!object || !object.addEventListener || !object.removeEventListener)
            return;
        types = injectedScript._normalizeEventTypes(types);
        for (var i = 0; i < types.length; ++i)
            object.removeEventListener(types[i], injectedScript._logEvent, false);
    },

    inspect: function(object)
    {
        if (arguments.length === 0)
            return;

        inspectedWindow.console.log(object);
        if (injectedScript._type(object) === "node")
            InjectedScriptHost.pushNodePathToFrontend(object, false, true);
        else {
            switch (injectedScript._describe(object)) {
                case "Database":
                    InjectedScriptHost.selectDatabase(object);
                    break;
                case "Storage":
                    InjectedScriptHost.selectDOMStorage(object);
                    break;
            }
        }
    },

    copy: function(object)
    {
        if (injectedScript._type(object) === "node") {
            var nodeId = InjectedScriptHost.pushNodePathToFrontend(object, false, false);
            InjectedScriptHost.copyNode(nodeId);
        } else
            InjectedScriptHost.copyText(object);
    },

    clear: function()
    {
        InjectedScriptHost.clearConsoleMessages();
    }
}

injectedScript._commandLineAPI = new CommandLineAPI();
return injectedScript;
});

/* InjectedScriptAccess.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2009 Joseph Pecoraro
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

function InjectedScriptAccess(worldId) {
    this._worldId = worldId;
}

InjectedScriptAccess.get = function(worldId)
{
    if (typeof worldId === "number")
        return new InjectedScriptAccess(worldId);

    console.assert(false, "Access to injected script with no id");
}

InjectedScriptAccess.getForNode = function(node)
{
    // FIXME: do something.
    return InjectedScriptAccess.get(-node.id);
}

InjectedScriptAccess.getForObjectId = function(objectId)
{
    // FIXME: move to native layer.
    var tokens = objectId.split(":");
    return InjectedScriptAccess.get(parseInt(tokens[0]));
}

InjectedScriptAccess.getDefault = function()
{
    return InjectedScriptAccess.get(0);
}

InjectedScriptAccess.prototype = {};

InjectedScriptAccess._installHandler = function(methodName, async)
{
    InjectedScriptAccess.prototype[methodName] = function()
    {
        var allArgs = Array.prototype.slice.call(arguments);
        var callback = allArgs[allArgs.length - 1];
        var argsString = JSON.stringify(Array.prototype.slice.call(allArgs, 0, allArgs.length - 1));
        
        function myCallback(result, isException)
        {
            if (!isException)
                callback(result);
            else
                WebInspector.console.addMessage(WebInspector.ConsoleMessage.createTextMessage("Error dispatching: " + methodName));
        }
        InspectorBackend.dispatchOnInjectedScript(this._worldId, methodName, argsString, myCallback);
    };
}

// InjectedScriptAccess message forwarding puts some constraints on the way methods are implemented and called:
// - Make sure corresponding methods in InjectedScript return non-null and non-undefined values,
// - Make sure last parameter of all the InjectedSriptAccess.* calls is a callback function.
// We keep these sorted.
InjectedScriptAccess._installHandler("evaluate");
InjectedScriptAccess._installHandler("evaluateInCallFrame");
InjectedScriptAccess._installHandler("evaluateOnSelf");
InjectedScriptAccess._installHandler("getCompletions");
InjectedScriptAccess._installHandler("getProperties");
InjectedScriptAccess._installHandler("getPrototypes");
InjectedScriptAccess._installHandler("pushNodeToFrontend");
InjectedScriptAccess._installHandler("resolveNode");
InjectedScriptAccess._installHandler("getNodeProperties");
InjectedScriptAccess._installHandler("setPropertyValue");

/* TimelineAgent.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelineAgent = function() {
    // Not implemented.
}

// Must be kept in sync with TimelineItem.h
WebInspector.TimelineAgent.RecordType = {
    EventDispatch : 0,
    Layout : 1,
    RecalculateStyles : 2,
    Paint : 3,
    ParseHTML : 4,
    TimerInstall : 5,
    TimerRemove : 6,
    TimerFire : 7,
    XHRReadyStateChange : 8,
    XHRLoad : 9,
    EvaluateScript : 10,
    MarkTimeline : 11,
    ResourceSendRequest : 12,
    ResourceReceiveResponse : 13,
    ResourceFinish : 14,
    FunctionCall : 15,
    ResourceReceiveData: 16,
    GCEvent : 17,
    MarkDOMContentEventType : 18,
    MarkLoadEventType : 19,
    ScheduleResourceRequest : 20
};

WebInspector.addRecordToTimeline = function(record) {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.addRecordToTimeline(record);
}

WebInspector.timelineProfilerWasStarted = function() {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.timelineWasStarted();
}

WebInspector.timelineProfilerWasStopped = function() {
    if (WebInspector.panels.timeline)
        WebInspector.panels.timeline.timelineWasStopped();
}

/* TimelinePanel.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelinePanel = function()
{
    WebInspector.Panel.call(this, "timeline");

    this.element.appendChild(this._createTopPane());
    this.element.tabIndex = 0;

    this._sidebarBackgroundElement = document.createElement("div");
    this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background";
    this.element.appendChild(this._sidebarBackgroundElement);

    this._containerElement = document.createElement("div");
    this._containerElement.id = "timeline-container";
    this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
    this.element.appendChild(this._containerElement);

    this.createSidebar(this._containerElement, this._containerElement);
    var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
    itemsTreeElement.expanded = true;
    this.sidebarTree.appendChild(itemsTreeElement);

    this._sidebarListElement = document.createElement("div");
    this.sidebarElement.appendChild(this._sidebarListElement);

    this._containerContentElement = document.createElement("div");
    this._containerContentElement.id = "resources-container-content";
    this._containerElement.appendChild(this._containerContentElement);

    this._timelineGrid = new WebInspector.TimelineGrid();
    this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
    this._itemsGraphsElement.id = "timeline-graphs";
    this._containerContentElement.appendChild(this._timelineGrid.element);

    this._topGapElement = document.createElement("div");
    this._topGapElement.className = "timeline-gap";
    this._itemsGraphsElement.appendChild(this._topGapElement);

    this._graphRowsElement = document.createElement("div");
    this._itemsGraphsElement.appendChild(this._graphRowsElement);

    this._bottomGapElement = document.createElement("div");
    this._bottomGapElement.className = "timeline-gap";
    this._itemsGraphsElement.appendChild(this._bottomGapElement);

    this._expandElements = document.createElement("div");
    this._expandElements.id = "orphan-expand-elements";
    this._itemsGraphsElement.appendChild(this._expandElements);

    this._rootRecord = this._createRootRecord();
    this._sendRequestRecords = {};
    this._scheduledResourceRequests = {};
    this._timerRecords = {};

    this._calculator = new WebInspector.TimelineCalculator();
    this._calculator._showShortEvents = false;
    var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold, WebInspector.UIString);
    this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle);
    this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle);
    this._createStatusbarButtons();

    this._boundariesAreValid = true;
    this._scrollTop = 0;

    this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);

    // Disable short events filter by default.
    this.toggleFilterButton.toggled = true;
    this._calculator._showShortEvents = this.toggleFilterButton.toggled;
    this._markTimelineRecords = [];
    this._expandOffset = 15;
}

// Define row height, should be in sync with styles for timeline graphs.
WebInspector.TimelinePanel.rowHeight = 18;
WebInspector.TimelinePanel.shortRecordThreshold = 0.015;

WebInspector.TimelinePanel.prototype = {
    _createTopPane: function() {
        var topPaneElement = document.createElement("div");
        topPaneElement.id = "timeline-overview-panel";

        this._topPaneSidebarElement = document.createElement("div");
        this._topPaneSidebarElement.id = "timeline-overview-sidebar";

        var overviewTreeElement = document.createElement("ol");
        overviewTreeElement.className = "sidebar-tree";
        this._topPaneSidebarElement.appendChild(overviewTreeElement);
        topPaneElement.appendChild(this._topPaneSidebarElement);

        var topPaneSidebarTree = new TreeOutline(overviewTreeElement);
        var timelinesOverviewItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Timelines"));
        topPaneSidebarTree.appendChild(timelinesOverviewItem);
        timelinesOverviewItem.onselect = this._timelinesOverviewItemSelected.bind(this);
        timelinesOverviewItem.select(true);

        var memoryOverviewItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Memory"));
        topPaneSidebarTree.appendChild(memoryOverviewItem);
        memoryOverviewItem.onselect = this._memoryOverviewItemSelected.bind(this);

        this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories);
        this._overviewPane.addEventListener("window changed", this._windowChanged, this);
        this._overviewPane.addEventListener("filter changed", this._refresh, this);
        topPaneElement.appendChild(this._overviewPane.element);

        var separatorElement = document.createElement("div");
        separatorElement.id = "timeline-overview-separator";
        topPaneElement.appendChild(separatorElement);
        return topPaneElement;
    },

    get toolbarItemLabel()
    {
        return WebInspector.UIString("Timeline");
    },

    get statusBarItems()
    {
        return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.clearButton.element, this._overviewPane.statusBarFilters];
    },

    get categories()
    {
        if (!this._categories) {
            this._categories = {
                loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
                scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
                rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
            };
        }
        return this._categories;
    },

    get defaultFocusedElement()
    {
        return this.element;
    },

    get _recordStyles()
    {
        if (!this._recordStylesArray) {
            var recordTypes = WebInspector.TimelineAgent.RecordType;
            var recordStyles = {};
            recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
            recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
            recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
            recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
            recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
            recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
            recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
            recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
            recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
            recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
            recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
            recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
            recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
            recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
            recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
            recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting };
            recordStyles[recordTypes.ResourceReceiveData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading };
            recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting };
            recordStyles[recordTypes.MarkDOMContentEventType] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting };
            recordStyles[recordTypes.MarkLoadEventType] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting };
            recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: this.categories.loading };
            this._recordStylesArray = recordStyles;
        }
        return this._recordStylesArray;
    },

    _createStatusbarButtons: function()
    {
        this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
        this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);

        this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
        this.clearButton.addEventListener("click", this._clearPanel.bind(this), false);

        this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item");
        this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false);

        this.recordsCounter = document.createElement("span");
        this.recordsCounter.className = "timeline-records-counter";
    },

    _updateRecordsCounter: function()
    {
        this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount);
    },

    _updateEventDividers: function()
    {
        this._timelineGrid.removeEventDividers();
        var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset;
        var dividers = [];
        for (var i = 0; i < this._markTimelineRecords.length; ++i) {
            var record = this._markTimelineRecords[i];
            var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth);
            var dividerPosition = Math.round(positions.left);
            if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
                continue;
            var divider = this._createEventDivider(record);
            divider.style.left = (dividerPosition + this._expandOffset) + "px";
            dividers[dividerPosition] = divider;
        }
        this._timelineGrid.addEventDividers(dividers);
        this._overviewPane.updateEventDividers(this._markTimelineRecords, this._createEventDivider.bind(this));
    },

    _createEventDivider: function(record)
    {
        var eventDivider = document.createElement("div");
        eventDivider.className = "resources-event-divider";
        var recordTypes = WebInspector.TimelineAgent.RecordType;

        var eventDividerPadding = document.createElement("div");
        eventDividerPadding.className = "resources-event-divider-padding";
        eventDividerPadding.title = record.title;

        if (record.type === recordTypes.MarkDOMContentEventType)
            eventDivider.className += " resources-blue-divider";
        else if (record.type === recordTypes.MarkLoadEventType)
            eventDivider.className += " resources-red-divider";
        else if (record.type === recordTypes.MarkTimeline) {
            eventDivider.className += " resources-orange-divider";
            eventDividerPadding.title = record.data.message;
        }
        eventDividerPadding.appendChild(eventDivider);
        return eventDividerPadding;
    },

    _timelinesOverviewItemSelected: function(event) {
        this._overviewPane.showTimelines();
    },

    _memoryOverviewItemSelected: function(event) {
        this._overviewPane.showMemoryGraph(this._rootRecord.children);
    },

    _toggleTimelineButtonClicked: function()
    {
        if (this.toggleTimelineButton.toggled)
            InspectorBackend.stopTimelineProfiler();
        else {
            this._clearPanel();
            InspectorBackend.startTimelineProfiler();
        }
    },

    _toggleFilterButtonClicked: function()
    {
        this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
        this._calculator._showShortEvents = this.toggleFilterButton.toggled;
        this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
        this._scheduleRefresh(true);
    },

    timelineWasStarted: function()
    {
        this.toggleTimelineButton.toggled = true;
    },

    timelineWasStopped: function()
    {
        this.toggleTimelineButton.toggled = false;
    },

    addRecordToTimeline: function(record)
    {
        if (record.type == WebInspector.TimelineAgent.RecordType.ResourceSendRequest && record.data.isMainResource) {
            if (this._mainResourceIdentifier != record.data.identifier) {
                // We are loading new main resource -> clear the panel. Check above is necessary since
                // there may be several resource loads with main resource marker upon redirects, redirects are reported with
                // the original identifier.
                this._mainResourceIdentifier = record.data.identifier;
                this._clearPanel();
            }
        }
        this._innerAddRecordToTimeline(record, this._rootRecord);
        this._scheduleRefresh();
    },

    _findParentRecord: function(record)
    {
        var recordTypes = WebInspector.TimelineAgent.RecordType;
        var parentRecord;
        if (record.type === recordTypes.ResourceReceiveResponse ||
            record.type === recordTypes.ResourceFinish ||
            record.type === recordTypes.ResourceReceiveData)
            parentRecord = this._sendRequestRecords[record.data.identifier];
        else if (record.type === recordTypes.TimerFire)
            parentRecord = this._timerRecords[record.data.timerId];
        else if (record.type === recordTypes.ResourceSendRequest)
            parentRecord = this._scheduledResourceRequests[record.data.url];
        return parentRecord;
    },

    _innerAddRecordToTimeline: function(record, parentRecord)
    {
        var connectedToOldRecord = false;
        var recordTypes = WebInspector.TimelineAgent.RecordType;
        if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType)
            parentRecord = null; // No bar entry for load events.
        else if (parentRecord === this._rootRecord) {
            var newParentRecord = this._findParentRecord(record);
            if (newParentRecord) {
                parentRecord = newParentRecord;
                connectedToOldRecord = true;
            }
        }

        if (record.type == recordTypes.TimerFire && record.children && record.children.length) {
            var childRecord = record.children[0];
            if (childRecord.type === recordTypes.FunctionCall) {
                record.data.scriptName = childRecord.data.scriptName;
                record.data.scriptLine = childRecord.data.scriptLine;
                record.children.shift();
                record.children = childRecord.children.concat(record.children);
            }
        }

        var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this);

        if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType) {
            this._markTimelineRecords.push(formattedRecord);
            return;
        }

        ++this._rootRecord._allRecordsCount;
        formattedRecord.collapsed = (parentRecord === this._rootRecord);

        var childrenCount = record.children ? record.children.length : 0;
        for (var i = 0; i < childrenCount; ++i)
            this._innerAddRecordToTimeline(record.children[i], formattedRecord);

        formattedRecord._calculateAggregatedStats(this.categories);

        if (connectedToOldRecord) {
            var record = formattedRecord;
            do {
                var parent = record.parent;
                parent._cpuTime += formattedRecord._cpuTime;
                if (parent._lastChildEndTime < record._lastChildEndTime)
                    parent._lastChildEndTime = record._lastChildEndTime;
                for (var category in formattedRecord._aggregatedStats)
                    parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category];
                record = parent;
            } while (record.parent);
        } else
            if (parentRecord !== this._rootRecord)
                parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime;

        // Keep bar entry for mark timeline since nesting might be interesting to the user.
        if (record.type === recordTypes.MarkTimeline)
            this._markTimelineRecords.push(formattedRecord);
    },

    setSidebarWidth: function(width)
    {
        WebInspector.Panel.prototype.setSidebarWidth.call(this, width);
        this._sidebarBackgroundElement.style.width = width + "px";
        this._topPaneSidebarElement.style.width = width + "px";
    },

    updateMainViewWidth: function(width)
    {
        this._containerContentElement.style.left = width + "px";
        this._scheduleRefresh();
        this._overviewPane.updateMainViewWidth(width);
    },

    resize: function()
    {
        this._closeRecordDetails();
        this._scheduleRefresh();
    },

    _createRootRecord: function()
    {
        var rootRecord = {};
        rootRecord.children = [];
        rootRecord._visibleRecordsCount = 0;
        rootRecord._allRecordsCount = 0;
        rootRecord._aggregatedStats = {};
        return rootRecord;
    },

    _clearPanel: function()
    {
        this._markTimelineRecords = [];
        this._sendRequestRecords = {};
        this._scheduledResourceRequests = {};
        this._timerRecords = {};
        this._rootRecord = this._createRootRecord();
        this._boundariesAreValid = false;
        this._overviewPane.reset();
        this._adjustScrollPosition(0);
        this._refresh();
        this._closeRecordDetails();
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        if (typeof this._scrollTop === "number")
            this._containerElement.scrollTop = this._scrollTop;
        this._refresh();
        WebInspector.drawer.currentPanelCounters = this.recordsCounter;
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);
        this._closeRecordDetails();
        WebInspector.drawer.currentPanelCounters = null;
    },

    _onScroll: function(event)
    {
        this._closeRecordDetails();
        var scrollTop = this._containerElement.scrollTop;
        var dividersTop = Math.max(0, scrollTop);
        this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
        this._scheduleRefresh(true);
    },

    _windowChanged: function()
    {
        this._closeRecordDetails();
        this._scheduleRefresh();
    },

    _scheduleRefresh: function(preserveBoundaries)
    {
        this._closeRecordDetails();
        this._boundariesAreValid &= preserveBoundaries;

        if (!this.visible)
            return;

        if (preserveBoundaries)
            this._refresh();
        else
            if (!this._refreshTimeout)
                this._refreshTimeout = setTimeout(this._refresh.bind(this), 100);
    },

    _refresh: function()
    {
        if (this._refreshTimeout) {
            clearTimeout(this._refreshTimeout);
            delete this._refreshTimeout;
        }

        this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents);
        this._refreshRecords(!this._boundariesAreValid);
        this._updateRecordsCounter();
        if(!this._boundariesAreValid)
            this._updateEventDividers();
        this._boundariesAreValid = true;
    },

    _updateBoundaries: function()
    {
        this._calculator.reset();
        this._calculator.windowLeft = this._overviewPane.windowLeft;
        this._calculator.windowRight = this._overviewPane.windowRight;

        for (var i = 0; i < this._rootRecord.children.length; ++i)
            this._calculator.updateBoundaries(this._rootRecord.children[i]);

        this._calculator.calculateWindow();
    },

    _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed)
    {
        if (!this._calculator._showShortEvents && !record.isLong())
            return;
        var percentages = this._calculator.computeBarGraphPercentages(record);
        if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) {
            ++this._rootRecord._visibleRecordsCount;
            ++record.parent._invisibleChildrenCount;
            if (!parentIsCollapsed)
                recordsWindow.push(record);
        }

        var index = recordsWindow.length;
        record._invisibleChildrenCount = 0;
        for (var i = 0; i < record.children.length; ++i)
            this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed);
        record._visibleChildrenCount = recordsWindow.length - index;
    },

    _filterRecords: function()
    {
        var recordsInWindow = [];
        this._rootRecord._visibleRecordsCount = 0;
        for (var i = 0; i < this._rootRecord.children.length; ++i)
            this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow);
        return recordsInWindow;
    },

    _refreshRecords: function(updateBoundaries)
    {
        if (updateBoundaries)
            this._updateBoundaries();

        var recordsInWindow = this._filterRecords();

        // Calculate the visible area.
        this._scrollTop = this._containerElement.scrollTop;
        var visibleTop = this._scrollTop;
        var visibleBottom = visibleTop + this._containerElement.clientHeight;

        const rowHeight = WebInspector.TimelinePanel.rowHeight;

        // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
        var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1));
        var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));

        // Resize gaps first.
        const top = (startIndex * rowHeight) + "px";
        this._topGapElement.style.height = top;
        this.sidebarElement.style.top = top;
        this.sidebarResizeElement.style.top = top;
        this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";

        // Update visible rows.
        var listRowElement = this._sidebarListElement.firstChild;
        var width = this._graphRowsElement.offsetWidth;
        this._itemsGraphsElement.removeChild(this._graphRowsElement);
        var graphRowElement = this._graphRowsElement.firstChild;
        var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
        this._itemsGraphsElement.removeChild(this._expandElements);
        this._expandElements.removeChildren();

        for (var i = 0; i < endIndex; ++i) {
            var record = recordsInWindow[i];
            var isEven = !(i % 2);

            if (i < startIndex) {
                var lastChildIndex = i + record._visibleChildrenCount;
                if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
                    var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
                    expandElement._update(record, i, this._calculator.computeBarGraphWindowPosition(record, width - this._expandOffset));
                }
            } else {
                if (!listRowElement) {
                    listRowElement = new WebInspector.TimelineRecordListRow().element;
                    this._sidebarListElement.appendChild(listRowElement);
                }
                if (!graphRowElement) {
                    graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback, rowHeight).element;
                    this._graphRowsElement.appendChild(graphRowElement);
                }

                listRowElement.row.update(record, isEven, this._calculator, visibleTop);
                graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i);

                listRowElement = listRowElement.nextSibling;
                graphRowElement = graphRowElement.nextSibling;
            }
        }

        // Remove extra rows.
        while (listRowElement) {
            var nextElement = listRowElement.nextSibling;
            listRowElement.row.dispose();
            listRowElement = nextElement;
        }
        while (graphRowElement) {
            var nextElement = graphRowElement.nextSibling;
            graphRowElement.row.dispose();
            graphRowElement = nextElement;
        }

        this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
        this._itemsGraphsElement.appendChild(this._expandElements);
        this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px";
        // Reserve some room for expand / collapse controls to the left for records that start at 0ms.
        var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0;
        if (updateBoundaries)
            this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft);
        this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight);
    },

    _adjustScrollPosition: function(totalHeight)
    {
        // Prevent the container from being scrolled off the end.
        if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1)
            this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
    },

    _getPopoverAnchor: function(element)
    {
        return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item");
    },

    _showPopover: function(anchor)
    {
        var record = anchor.row._record;
        var popover = new WebInspector.Popover(record._generatePopupContent(this._calculator, this.categories));
        popover.show(anchor);
        return popover;
    },

    _closeRecordDetails: function()
    {
        this._popoverHelper.hidePopup();
    }
}

WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;

WebInspector.TimelineCategory = function(name, title, color)
{
    this.name = name;
    this.title = title;
    this.color = color;
}

WebInspector.TimelineCalculator = function()
{
    this.reset();
    this.windowLeft = 0.0;
    this.windowRight = 1.0;
}

WebInspector.TimelineCalculator.prototype = {
    computeBarGraphPercentages: function(record)
    {
        var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
        var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100;
        var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
        var cpuWidth = record._cpuTime / this.boundarySpan * 100;
        return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
    },

    computeBarGraphWindowPosition: function(record, clientWidth)
    {
        const minWidth = 5;
        const borderWidth = 4;
        var workingArea = clientWidth - minWidth - borderWidth;
        var percentages = this.computeBarGraphPercentages(record);
        var left = percentages.start / 100 * workingArea;
        var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth;
        var widthWithChildren =  (percentages.endWithChildren - percentages.start) / 100 * workingArea;
        var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth;
        if (percentages.endWithChildren > percentages.end)
            widthWithChildren += borderWidth + minWidth;
        return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
    },

    calculateWindow: function()
    {
        this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
        this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
        this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
    },

    reset: function()
    {
        this._absoluteMinimumBoundary = -1;
        this._absoluteMaximumBoundary = -1;
    },

    updateBoundaries: function(record)
    {
        var lowerBound = record.startTime;
        if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary)
            this._absoluteMinimumBoundary = lowerBound;

        const minimumTimeFrame = 0.1;
        const minimumDeltaForZeroSizeEvents = 0.01;
        var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame);
        if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary)
            this._absoluteMaximumBoundary = upperBound;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, WebInspector.UIString);
    }
}


WebInspector.TimelineRecordListRow = function()
{
    this.element = document.createElement("div");
    this.element.row = this;
    this.element.style.cursor = "pointer";
    var iconElement = document.createElement("span");
    iconElement.className = "timeline-tree-icon";
    this.element.appendChild(iconElement);

    this._typeElement = document.createElement("span");
    this._typeElement.className = "type";
    this.element.appendChild(this._typeElement);

    var separatorElement = document.createElement("span");
    separatorElement.className = "separator";
    separatorElement.textContent = " ";

    this._dataElement = document.createElement("span");
    this._dataElement.className = "data dimmed";

    this.element.appendChild(separatorElement);
    this.element.appendChild(this._dataElement);
}

WebInspector.TimelineRecordListRow.prototype = {
    update: function(record, isEven, calculator, offset)
    {
        this._record = record;
        this._calculator = calculator;
        this._offset = offset;

        this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
        this._typeElement.textContent = record.title;

        if (this._dataElement.firstChild)
            this._dataElement.removeChildren();
        if (record.details) {
            var detailsContainer = document.createElement("span");
            if (typeof record.details === "object") {
                detailsContainer.appendChild(document.createTextNode("("));
                detailsContainer.appendChild(record.details);
                detailsContainer.appendChild(document.createTextNode(")"));
            } else
                detailsContainer.textContent = "(" + record.details + ")";
            this._dataElement.appendChild(detailsContainer);
        }
    },

    dispose: function()
    {
        this.element.parentElement.removeChild(this.element);
    }
}

WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
{
    this.element = document.createElement("div");
    this.element.row = this;

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "timeline-graph-bar-area";
    this.element.appendChild(this._barAreaElement);

    this._barWithChildrenElement = document.createElement("div");
    this._barWithChildrenElement.className = "timeline-graph-bar with-children";
    this._barWithChildrenElement.row = this;
    this._barAreaElement.appendChild(this._barWithChildrenElement);

    this._barCpuElement = document.createElement("div");
    this._barCpuElement.className = "timeline-graph-bar cpu"
    this._barCpuElement.row = this;
    this._barAreaElement.appendChild(this._barCpuElement);

    this._barElement = document.createElement("div");
    this._barElement.className = "timeline-graph-bar";
    this._barElement.row = this;
    this._barAreaElement.appendChild(this._barElement);

    this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
    this._expandElement._element.addEventListener("click", this._onClick.bind(this));

    this._scheduleRefresh = scheduleRefresh;
}

WebInspector.TimelineRecordGraphRow.prototype = {
    update: function(record, isEven, calculator, clientWidth, expandOffset, index)
    {
        this._record = record;
        this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
        var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset);
        this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px";
        this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
        this._barElement.style.left = barPosition.left + expandOffset + "px";
        this._barElement.style.width =  barPosition.width + "px";
        this._barCpuElement.style.left = barPosition.left + expandOffset + "px";
        this._barCpuElement.style.width = barPosition.cpuWidth + "px";
        this._expandElement._update(record, index, barPosition);
    },

    _onClick: function(event)
    {
        this._record.collapsed = !this._record.collapsed;
        this._scheduleRefresh();
    },

    dispose: function()
    {
        this.element.parentElement.removeChild(this.element);
        this._expandElement._dispose();
    }
}

WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, panel)
{
    var recordTypes = WebInspector.TimelineAgent.RecordType;
    var style = panel._recordStyles[record.type];

    this.parent = parentRecord;
    if (parentRecord)
        parentRecord.children.push(this);
    this.category = style.category;
    this.title = style.title;
    this.startTime = record.startTime / 1000;
    this.data = record.data;
    this.type = record.type;
    this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime;
    this._selfTime = this.endTime - this.startTime;
    this._lastChildEndTime = this.endTime;
    this.originalRecordForTests = record;
    if (record.stackTrace && record.stackTrace.length)
        this.stackTrace = record.stackTrace;
    this.totalHeapSize = record.totalHeapSize;
    this.usedHeapSize = record.usedHeapSize;

    // Make resource receive record last since request was sent; make finish record last since response received.
    if (record.type === recordTypes.ResourceSendRequest) {
        panel._sendRequestRecords[record.data.identifier] = this;
    } else if (record.type === recordTypes.ScheduleResourceRequest) {
        panel._scheduledResourceRequests[record.data.url] = this;
    } else if (record.type === recordTypes.ResourceReceiveResponse) {
        var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
        if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
            record.data.url = sendRequestRecord.data.url;
            // Now that we have resource in the collection, recalculate details in order to display short url.
            sendRequestRecord.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords);
            if (sendRequestRecord.parent !== panel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
                sendRequestRecord.parent.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords);
        }
    } else if (record.type === recordTypes.ResourceReceiveData) {
        var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
        if (sendRequestRecord) // False for main resource.
            record.data.url = sendRequestRecord.data.url;
    } else if (record.type === recordTypes.ResourceFinish) {
        var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
        if (sendRequestRecord) // False for main resource.
            record.data.url = sendRequestRecord.data.url;
    } else if (record.type === recordTypes.TimerInstall) {
        this.timeout = record.data.timeout;
        this.singleShot = record.data.singleShot;
        panel._timerRecords[record.data.timerId] = this;
    } else if (record.type === recordTypes.TimerFire) {
        var timerInstalledRecord = panel._timerRecords[record.data.timerId];
        if (timerInstalledRecord) {
            this.callSiteStackTrace = timerInstalledRecord.stackTrace;
            this.timeout = timerInstalledRecord.timeout;
            this.singleShot = timerInstalledRecord.singleShot;
        }
    }
    this.details = this._getRecordDetails(record, panel._sendRequestRecords);
}

WebInspector.TimelinePanel.FormattedRecord.prototype = {
    isLong: function()
    {
        return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold;
    },

    get children()
    {
        if (!this._children)
            this._children = [];
        return this._children;
    },

    _generateAggregatedInfo: function()
    {
        var cell = document.createElement("span");
        cell.className = "timeline-aggregated-info";
        for (var index in this._aggregatedStats) {
            var label = document.createElement("div");
            label.className = "timeline-aggregated-category timeline-" + index;
            cell.appendChild(label);
            var text = document.createElement("span");
            text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001, WebInspector.UIString);
            cell.appendChild(text);
        }
        return cell;
    },

    _generatePopupContent: function(calculator, categories)
    {
        var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title);

        if (this._children && this._children.length) {
            contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime + 0.0001, WebInspector.UIString));
            contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"), this._generateAggregatedInfo());
        }
        var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, WebInspector.UIString),
            calculator.formatValue(this.startTime - calculator.minimumBoundary));
        contentHelper._appendTextRow(WebInspector.UIString("Duration"), text);

        const recordTypes = WebInspector.TimelineAgent.RecordType;

        switch (this.type) {
            case recordTypes.GCEvent:
                contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data.usedHeapSizeDelta, WebInspector.UIString));
                break;
            case recordTypes.TimerInstall:
            case recordTypes.TimerFire:
            case recordTypes.TimerRemove:
                contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data.timerId);
                if (typeof this.timeout === "number") {
                    contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000, WebInspector.UIString));
                    contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
                }
                break;
            case recordTypes.FunctionCall:
                contentHelper._appendLinkRow(WebInspector.UIString("Location"), this.data.scriptName, this.data.scriptLine);
                break;
            case recordTypes.ScheduleResourceRequest:
            case recordTypes.ResourceSendRequest:
            case recordTypes.ResourceReceiveResponse:
            case recordTypes.ResourceReceiveData:
            case recordTypes.ResourceFinish:
                contentHelper._appendLinkRow(WebInspector.UIString("Resource"), this.data.url);
                if (this.data.requestMethod)
                    contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data.requestMethod);
                if (typeof this.data.statusCode === "number")
                    contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data.statusCode);
                if (this.data.mimeType)
                    contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data.mimeType);
                if (typeof this.data.expectedContentLength === "number" && this.data.expectedContentLength !== -1)
                    contentHelper._appendTextRow(WebInspector.UIString("Expected Content Length"), this.data.expectedContentLength);
                break;
            case recordTypes.EvaluateScript:
                if (this.data && this.data.url)
                    contentHelper._appendLinkRow(WebInspector.UIString("Script"), this.data.url, this.data.lineNumber);
                break;
            case recordTypes.Paint:
                contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("%d × %d", this.data.x, this.data.y));
                contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", this.data.width, this.data.height));
            case recordTypes.RecalculateStyles: // We don't want to see default details.
                break;
            default:
                if (this.details)
                    contentHelper._appendTextRow(WebInspector.UIString("Details"), this.details);
                break;
        }

        if (this.data.scriptName && this.type !== recordTypes.FunctionCall)
            contentHelper._appendLinkRow(WebInspector.UIString("Function Call"), this.data.scriptName, this.data.scriptLine);

        if (this.usedHeapSize)
            contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize, WebInspector.UIString), Number.bytesToString(this.totalHeapSize, WebInspector.UIString)));

        if (this.callSiteStackTrace && this.callSiteStackTrace.length)
            contentHelper._appendStackTrace(WebInspector.UIString("Call Site stack"), this.callSiteStackTrace);

        if (this.stackTrace)
            contentHelper._appendStackTrace(WebInspector.UIString("Call Stack"), this.stackTrace);

        return contentHelper._contentTable;
    },

    _getRecordDetails: function(record, sendRequestRecords)
    {
        switch (record.type) {
            case WebInspector.TimelineAgent.RecordType.GCEvent:
                return WebInspector.UIString("%s collected", Number.bytesToString(record.data.usedHeapSizeDelta, WebInspector.UIString));
            case WebInspector.TimelineAgent.RecordType.TimerFire:
                return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : record.data.timerId;
            case WebInspector.TimelineAgent.RecordType.FunctionCall:
                return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : null;
            case WebInspector.TimelineAgent.RecordType.EventDispatch:
                return record.data ? record.data.type : null;
            case WebInspector.TimelineAgent.RecordType.Paint:
                return record.data.width + "\u2009\u00d7\u2009" + record.data.height;
            case WebInspector.TimelineAgent.RecordType.TimerInstall:
            case WebInspector.TimelineAgent.RecordType.TimerRemove:
                return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].scriptName, "scripts", this.stackTrace[0].lineNumber, "", "") : record.data.timerId;
            case WebInspector.TimelineAgent.RecordType.ParseHTML:
            case WebInspector.TimelineAgent.RecordType.RecalculateStyles:
                return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].scriptName, "scripts", this.stackTrace[0].lineNumber, "", "") : null;
            case WebInspector.TimelineAgent.RecordType.EvaluateScript:
                return record.data.url ? WebInspector.linkifyResourceAsNode(record.data.url, "scripts", record.data.lineNumber, "", "") : null;
            case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
            case WebInspector.TimelineAgent.RecordType.XHRLoad:
            case WebInspector.TimelineAgent.RecordType.ScheduleResourceRequest:
            case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
            case WebInspector.TimelineAgent.RecordType.ResourceReceiveData:
            case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
            case WebInspector.TimelineAgent.RecordType.ResourceFinish:
                return WebInspector.displayNameForURL(record.data.url);
            case WebInspector.TimelineAgent.RecordType.MarkTimeline:
                return record.data.message;
            default:
                return null;
        }
    },

    _calculateAggregatedStats: function(categories)
    {
        this._aggregatedStats = {};
        for (var category in categories)
            this._aggregatedStats[category] = 0;
        this._cpuTime = this._selfTime;

        if (this._children) {
            for (var index = this._children.length; index; --index) {
                var child = this._children[index - 1];
                this._aggregatedStats[child.category.name] += child._selfTime;
                for (var category in categories)
                    this._aggregatedStats[category] += child._aggregatedStats[category];
            }
            for (var category in this._aggregatedStats)
                this._cpuTime += this._aggregatedStats[category];
        }
    }
}

WebInspector.TimelinePanel.PopupContentHelper = function(title)
{
    this._contentTable = document.createElement("table");;
    var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
    titleCell.colSpan = 2;
    var titleRow = document.createElement("tr");
    titleRow.appendChild(titleCell);
    this._contentTable.appendChild(titleRow);
}

WebInspector.TimelinePanel.PopupContentHelper.prototype = {
    _createCell: function(content, styleName)
    {
        var text = document.createElement("label");
        text.appendChild(document.createTextNode(content));
        var cell = document.createElement("td");
        cell.className = "timeline-details";
        if (styleName)
            cell.className += " " + styleName;
        cell.textContent = content;
        return cell;
    },

    _appendTextRow: function(title, content)
    {
        var row = document.createElement("tr");
        row.appendChild(this._createCell(title, "timeline-details-row-title"));
        row.appendChild(this._createCell(content, "timeline-details-row-data"));
        this._contentTable.appendChild(row);
    },

    _appendElementRow: function(title, content, titleStyle)
    {
        var row = document.createElement("tr");
        var titleCell = this._createCell(title, "timeline-details-row-title");
        if (titleStyle)
            titleCell.addStyleClass(titleStyle);
        row.appendChild(titleCell);
        var cell = document.createElement("td");
        cell.className = "timeline-details";
        cell.appendChild(content);
        row.appendChild(cell);
        this._contentTable.appendChild(row);
    },

    _appendLinkRow: function(title, scriptName, scriptLine)
    {
        var link = WebInspector.linkifyResourceAsNode(scriptName, "scripts", scriptLine, "timeline-details");
        this._appendElementRow(title, link);
    },

    _appendStackTrace: function(title, stackTrace)
    {
        this._appendTextRow("", "");
        var framesTable = document.createElement("table");
        for (var i = 0; i < stackTrace.length; ++i) {
            var stackFrame = stackTrace[i];
            var row = document.createElement("tr");
            row.className = "timeline-details";
            row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name"));
            row.appendChild(this._createCell(" @ "));
            var linkCell = document.createElement("td");
            linkCell.appendChild(WebInspector.linkifyResourceAsNode(stackFrame.scriptName, "scripts", stackFrame.lineNumber, "timeline-details"));
            row.appendChild(linkCell);
            framesTable.appendChild(row);
        }
        this._appendElementRow(title, framesTable, "timeline-stacktrace-title");
    }
}

WebInspector.TimelineExpandableElement = function(container)
{
    this._element = document.createElement("div");
    this._element.className = "timeline-expandable";

    var leftBorder = document.createElement("div");
    leftBorder.className = "timeline-expandable-left";
    this._element.appendChild(leftBorder);

    container.appendChild(this._element);
}

WebInspector.TimelineExpandableElement.prototype = {
    _update: function(record, index, barPosition)
    {
        const rowHeight = WebInspector.TimelinePanel.rowHeight;
        if (record._visibleChildrenCount || record._invisibleChildrenCount) {
            this._element.style.top = index * rowHeight + "px";
            this._element.style.left = barPosition.left + "px";
            this._element.style.width = Math.max(12, barPosition.width + 25) + "px";
            if (!record.collapsed) {
                this._element.style.height = (record._visibleChildrenCount + 1) * rowHeight + "px";
                this._element.addStyleClass("timeline-expandable-expanded");
                this._element.removeStyleClass("timeline-expandable-collapsed");
            } else {
                this._element.style.height = rowHeight + "px";
                this._element.addStyleClass("timeline-expandable-collapsed");
                this._element.removeStyleClass("timeline-expandable-expanded");
            }
            this._element.removeStyleClass("hidden");
        } else
            this._element.addStyleClass("hidden");
    },

    _dispose: function()
    {
        this._element.parentElement.removeChild(this._element);
    }
}

/* TimelineOverviewPane.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TimelineOverviewPane = function(categories)
{
    this._categories = categories;

    this.statusBarFilters = document.createElement("div");
    this.statusBarFilters.className = "status-bar-items";
    for (var categoryName in this._categories) {
        var category = this._categories[categoryName];
        this.statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category, this._onCheckboxClicked.bind(this, category)));
    }

    this._overviewGrid = new WebInspector.TimelineGrid();
    this._overviewGrid.element.id = "timeline-overview-grid";
    this._overviewGrid.itemsGraphsElement.id = "timeline-overview-timelines";
    this._overviewGrid.element.addEventListener("mousedown", this._dragWindow.bind(this), true);

    this._heapGraph = new WebInspector.HeapGraph();
    this._heapGraph.element.id = "timeline-overview-memory";
    this._overviewGrid.element.insertBefore(this._heapGraph.element, this._overviewGrid.itemsGraphsElement);

    this.element = this._overviewGrid.element;

    this._categoryGraphs = {};
    var i = 0;
    for (var category in this._categories) {
        var categoryGraph = new WebInspector.TimelineCategoryGraph(this._categories[category], i++ % 2);
        this._categoryGraphs[category] = categoryGraph;
        this._overviewGrid.itemsGraphsElement.appendChild(categoryGraph.graphElement);
    }
    this._overviewGrid.setScrollAndDividerTop(0, 0);

    this._overviewWindowElement = document.createElement("div");
    this._overviewWindowElement.id = "timeline-overview-window";
    this._overviewGrid.element.appendChild(this._overviewWindowElement);

    this._overviewWindowBordersElement = document.createElement("div");
    this._overviewWindowBordersElement.className = "timeline-overview-window-rulers";
    this._overviewGrid.element.appendChild(this._overviewWindowBordersElement);

    var overviewDividersBackground = document.createElement("div");
    overviewDividersBackground.className = "timeline-overview-dividers-background";
    this._overviewGrid.element.appendChild(overviewDividersBackground);

    this._leftResizeElement = document.createElement("div");
    this._leftResizeElement.className = "timeline-window-resizer";
    this._leftResizeElement.style.left = 0;
    this._overviewGrid.element.appendChild(this._leftResizeElement);

    this._rightResizeElement = document.createElement("div");
    this._rightResizeElement.className = "timeline-window-resizer timeline-window-resizer-right";
    this._rightResizeElement.style.right = 0;
    this._overviewGrid.element.appendChild(this._rightResizeElement);

    this._overviewCalculator = new WebInspector.TimelineOverviewCalculator();

    this.windowLeft = 0.0;
    this.windowRight = 1.0;
}

WebInspector.TimelineOverviewPane.minSelectableSize = 12;

WebInspector.TimelineOverviewPane.prototype = {
    showTimelines: function(event) {
        this._heapGraph.hide();
        this._overviewGrid.itemsGraphsElement.removeStyleClass("hidden");
    },

    showMemoryGraph: function(records) {
        this._heapGraph.show();
        this._heapGraph.update(records);
        this._overviewGrid.itemsGraphsElement.addStyleClass("hidden");
    },

    _onCheckboxClicked: function (category, event) {
        if (event.target.checked)
            category.hidden = false;
        else
            category.hidden = true;
        this._categoryGraphs[category.name].dimmed = !event.target.checked;
        this.dispatchEventToListeners("filter changed");
    },

    _forAllRecords: function(recordsArray, callback)
    {
        if (!recordsArray)
            return;
        for (var i = 0; i < recordsArray.length; ++i) {
            callback(recordsArray[i]);
            this._forAllRecords(recordsArray[i].children, callback);
        }
    },

    update: function(records, showShortEvents)
    {
        this._showShortEvents = showShortEvents;
        // Clear summary bars.
        var timelines = {};
        for (var category in this._categories) {
            timelines[category] = [];
            this._categoryGraphs[category].clearChunks();
        }

        // Create sparse arrays with 101 cells each to fill with chunks for a given category.
        this._overviewCalculator.reset();
        this._forAllRecords(records, this._overviewCalculator.updateBoundaries.bind(this._overviewCalculator));

        function markTimeline(record)
        {
            if (!(this._showShortEvents || record.isLong()))
                return;
            var percentages = this._overviewCalculator.computeBarGraphPercentages(record);

            var end = Math.round(percentages.end);
            var categoryName = record.category.name;
            for (var j = Math.round(percentages.start); j <= end; ++j)
                timelines[categoryName][j] = true;
        }
        this._forAllRecords(records, markTimeline.bind(this));

        // Convert sparse arrays to continuous segments, render graphs for each.
        for (var category in this._categories) {
            var timeline = timelines[category];
            window.timelineSaved = timeline;
            var chunkStart = -1;
            for (var j = 0; j < 101; ++j) {
                if (timeline[j]) {
                    if (chunkStart === -1)
                        chunkStart = j;
                } else {
                    if (chunkStart !== -1) {
                        this._categoryGraphs[category].addChunk(chunkStart, j);
                        chunkStart = -1;
                    }
                }
            }
            if (chunkStart !== -1) {
                this._categoryGraphs[category].addChunk(chunkStart, 100);
                chunkStart = -1;
            }
        }

        this._heapGraph.setSize(this._overviewGrid.element.offsetWidth, 60);
        if (this._heapGraph.visible)
            this._heapGraph.update(records);

        this._overviewGrid.updateDividers(true, this._overviewCalculator);
    },

    updateEventDividers: function(records, dividerConstructor)
    {
        this._overviewGrid.removeEventDividers();
        var dividers = [];
        for (var i = 0; i < records.length; ++i) {
            var record = records[i];
            var positions = this._overviewCalculator.computeBarGraphPercentages(record);
            var dividerPosition = Math.round(positions.start * 10);
            if (dividers[dividerPosition])
                continue;
            var divider = dividerConstructor(record);
            divider.style.left = positions.start + "%";
            dividers[dividerPosition] = divider;
        }
        this._overviewGrid.addEventDividers(dividers);
    },

    updateMainViewWidth: function(width, records)
    {
        this._overviewGrid.element.style.left = width + "px";
        this.statusBarFilters.style.left = Math.max(155, width) + "px";
    },

    reset: function()
    {
        this.windowLeft = 0.0;
        this.windowRight = 1.0;
        this._overviewWindowElement.style.left = "0%";
        this._overviewWindowElement.style.width = "100%";
        this._overviewWindowBordersElement.style.left = "0%";
        this._overviewWindowBordersElement.style.right = "0%";
        this._leftResizeElement.style.left = "0%";
        this._rightResizeElement.style.left = "100%";
        this._overviewCalculator.reset();
        this._overviewGrid.updateDividers(true, this._overviewCalculator);
    },

    _resizeWindow: function(resizeElement, event)
    {
        WebInspector.elementDragStart(resizeElement, this._windowResizeDragging.bind(this, resizeElement), this._endWindowDragging.bind(this), event, "col-resize");
    },

    _windowResizeDragging: function(resizeElement, event)
    {
        if (resizeElement === this._leftResizeElement)
            this._resizeWindowLeft(event.pageX - this._overviewGrid.element.offsetLeft);
        else
            this._resizeWindowRight(event.pageX - this._overviewGrid.element.offsetLeft);
        event.preventDefault();
    },

    _dragWindow: function(event)
    {
        var node = event.target;
        while (node) {
            if (node === this._overviewGrid._dividersLabelBarElement) {
                WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX,
                    this._leftResizeElement.offsetLeft, this._rightResizeElement.offsetLeft), this._endWindowDragging.bind(this), event, "ew-resize");
                break;
            } else if (node === this._overviewGrid.element) {
                var position = event.pageX - this._overviewGrid.element.offsetLeft;
                this._overviewWindowSelector = new WebInspector.TimelinePanel.WindowSelector(this._overviewGrid.element, position, event);
                WebInspector.elementDragStart(null, this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), event, "col-resize");
                break;
            } else if (node === this._leftResizeElement || node === this._rightResizeElement) {
                this._resizeWindow(node, event);
                break;
            }
            node = node.parentNode;
        }
    },

    _windowSelectorDragging: function(event)
    {
        this._overviewWindowSelector._updatePosition(event.pageX - this._overviewGrid.element.offsetLeft);
        event.preventDefault();
    },

    _endWindowSelectorDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
        var window = this._overviewWindowSelector._close(event.pageX - this._overviewGrid.element.offsetLeft);
        delete this._overviewWindowSelector;
        if (window.end - window.start < WebInspector.TimelineOverviewPane.minSelectableSize)
            if (this._overviewGrid.itemsGraphsElement.offsetWidth - window.end > WebInspector.TimelineOverviewPane.minSelectableSize)
                window.end = window.start + WebInspector.TimelineOverviewPane.minSelectableSize;
            else
                window.start = window.end - WebInspector.TimelineOverviewPane.minSelectableSize;
        this._setWindowPosition(window.start, window.end);
    },

    _windowDragging: function(startX, windowLeft, windowRight, event)
    {
        var delta = event.pageX - startX;
        var start = windowLeft + delta;
        var end = windowRight + delta;
        var windowSize = windowRight - windowLeft;

        if (start < 0) {
            start = 0;
            end = windowSize;
        }

        if (end > this._overviewGrid.element.clientWidth) {
            end = this._overviewGrid.element.clientWidth;
            start = end - windowSize;
        }
        this._setWindowPosition(start, end);

        event.preventDefault();
    },

    _resizeWindowLeft: function(start)
    {
        // Glue to edge.
        if (start < 10)
            start = 0;
        else if (start > this._rightResizeElement.offsetLeft -  4)
            start = this._rightResizeElement.offsetLeft - 4;
        this._setWindowPosition(start, null);
    },

    _resizeWindowRight: function(end)
    {
        // Glue to edge.
        if (end > this._overviewGrid.element.clientWidth - 10)
            end = this._overviewGrid.element.clientWidth;
        else if (end < this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.minSelectableSize)
            end = this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.minSelectableSize;
        this._setWindowPosition(null, end);
    },

    _setWindowPosition: function(start, end)
    {
        const rulerAdjustment = 1 / this._overviewGrid.element.clientWidth;
        if (typeof start === "number") {
            this.windowLeft = start / this._overviewGrid.element.clientWidth;
            this._leftResizeElement.style.left = this.windowLeft * 100 + "%";
            this._overviewWindowElement.style.left = this.windowLeft * 100 + "%";
            this._overviewWindowBordersElement.style.left = (this.windowLeft - rulerAdjustment) * 100 + "%";
        }
        if (typeof end === "number") {
            this.windowRight = end / this._overviewGrid.element.clientWidth;
            this._rightResizeElement.style.left = this.windowRight * 100 + "%";
        }
        this._overviewWindowElement.style.width = (this.windowRight - this.windowLeft) * 100 + "%";
        this._overviewWindowBordersElement.style.right = (1 - this.windowRight + 2 * rulerAdjustment) * 100 + "%";
        this.dispatchEventToListeners("window changed");
    },

    _endWindowDragging: function(event)
    {
        WebInspector.elementDragEnd(event);
    },

    _createTimelineCategoryStatusBarCheckbox: function(category, onCheckboxClicked)
    {
        var labelContainer = document.createElement("div");
        labelContainer.addStyleClass("timeline-category-statusbar-item");
        labelContainer.addStyleClass("timeline-category-" + category.name);
        labelContainer.addStyleClass("status-bar-item");

        var label = document.createElement("label");
        var checkElement = document.createElement("input");
        checkElement.type = "checkbox";
        checkElement.className = "timeline-category-checkbox";
        checkElement.checked = true;
        checkElement.addEventListener("click", onCheckboxClicked);
        label.appendChild(checkElement);

        var typeElement = document.createElement("span");
        typeElement.className = "type";
        typeElement.textContent = category.title;
        label.appendChild(typeElement);

        labelContainer.appendChild(label);
        return labelContainer;
    }

}

WebInspector.TimelineOverviewPane.prototype.__proto__ = WebInspector.Object.prototype;


WebInspector.TimelineOverviewCalculator = function()
{
}

WebInspector.TimelineOverviewCalculator.prototype = {
    computeBarGraphPercentages: function(record)
    {
        var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
        var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100;
        return {start: start, end: end};
    },

    reset: function()
    {
        delete this.minimumBoundary;
        delete this.maximumBoundary;
    },

    updateBoundaries: function(record)
    {
        if (typeof this.minimumBoundary === "undefined" || record.startTime < this.minimumBoundary) {
            this.minimumBoundary = record.startTime;
            return true;
        }
        if (typeof this.maximumBoundary === "undefined" || record.endTime > this.maximumBoundary) {
            this.maximumBoundary = record.endTime;
            return true;
        }
        return false;
    },

    get boundarySpan()
    {
        return this.maximumBoundary - this.minimumBoundary;
    },

    formatValue: function(value)
    {
        return Number.secondsToString(value, WebInspector.UIString);
    }
}


WebInspector.TimelineCategoryGraph = function(category, isEven)
{
    this._category = category;

    this._graphElement = document.createElement("div");
    this._graphElement.className = "timeline-graph-side timeline-overview-graph-side" + (isEven ? " even" : "");

    this._barAreaElement = document.createElement("div");
    this._barAreaElement.className = "timeline-graph-bar-area timeline-category-" + category.name;
    this._graphElement.appendChild(this._barAreaElement);
}

WebInspector.TimelineCategoryGraph.prototype = {
    get graphElement()
    {
        return this._graphElement;
    },

    addChunk: function(start, end)
    {
        var chunk = document.createElement("div");
        chunk.className = "timeline-graph-bar";
        this._barAreaElement.appendChild(chunk);
        chunk.style.setProperty("left", start + "%");
        chunk.style.setProperty("width", (end - start) + "%");
    },

    clearChunks: function()
    {
        this._barAreaElement.removeChildren();
    },

    set dimmed(dimmed)
    {
        if (dimmed)
            this._barAreaElement.removeStyleClass("timeline-category-" + this._category.name);
        else
            this._barAreaElement.addStyleClass("timeline-category-" + this._category.name);
    }
}

WebInspector.TimelinePanel.WindowSelector = function(parent, position, event)
{
    this._startPosition = position;
    this._width = parent.offsetWidth;
    this._windowSelector = document.createElement("div");
    this._windowSelector.className = "timeline-window-selector";
    this._windowSelector.style.left = this._startPosition + "px";
    this._windowSelector.style.right = this._width - this._startPosition +  + "px";
    parent.appendChild(this._windowSelector);
}

WebInspector.TimelinePanel.WindowSelector.prototype = {
    _createSelectorElement: function(parent, left, width, height)
    {
        var selectorElement = document.createElement("div");
        selectorElement.className = "timeline-window-selector";
        selectorElement.style.left = left + "px";
        selectorElement.style.width = width + "px";
        selectorElement.style.top = "0px";
        selectorElement.style.height = height + "px";
        parent.appendChild(selectorElement);
        return selectorElement;
    },

    _close: function(position)
    {
        position = Math.max(0, Math.min(position, this._width));
        this._windowSelector.parentNode.removeChild(this._windowSelector);
        return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition};
    },

    _updatePosition: function(position)
    {
        position = Math.max(0, Math.min(position, this._width));
        if (position < this._startPosition) {
            this._windowSelector.style.left = position + "px";
            this._windowSelector.style.right = this._width - this._startPosition + "px";
        } else {
            this._windowSelector.style.left = this._startPosition + "px";
            this._windowSelector.style.right = this._width - position + "px";
        }
    }
}

WebInspector.HeapGraph = function() {
    this._canvas = document.createElement("canvas");

    this._maxHeapSizeLabel = document.createElement("div");
    this._maxHeapSizeLabel.addStyleClass("memory-graph-label");

    this._element = document.createElement("div");
    this._element.addStyleClass("hidden");
    this._element.appendChild(this._canvas);
    this._element.appendChild(this._maxHeapSizeLabel);
}

WebInspector.HeapGraph.prototype = {
    get element() {
    //    return this._canvas;
        return this._element;
    },

    get visible() {
        return !this.element.hasStyleClass("hidden");
    },

    show: function() {
        this.element.removeStyleClass("hidden");
    },

    hide: function() {
        this.element.addStyleClass("hidden");
    },

    setSize: function(w, h) {
        this._canvas.width = w;
        this._canvas.height = h - 5;
    },

    update: function(records)
    {
        if (!records.length)
            return;

        var maxTotalHeapSize = 0;
        var minTime;
        var maxTime;
        this._forAllRecords(records, function(r) {
            if (r.totalHeapSize && r.totalHeapSize > maxTotalHeapSize)
                maxTotalHeapSize = r.totalHeapSize;

            if (typeof minTime === "undefined" || r.startTime < minTime)
                minTime = r.startTime;
            if (typeof maxTime === "undefined" || r.endTime > maxTime)
                maxTime = r.endTime;
        });

        var width = this._canvas.width;
        var height = this._canvas.height;
        var xFactor = width / (maxTime - minTime);
        var yFactor = height / maxTotalHeapSize;

        var histogram = new Array(width);
        this._forAllRecords(records, function(r) {
            if (!r.usedHeapSize)
                return;
             var x = Math.round((r.endTime - minTime) * xFactor);
             var y = Math.round(r.usedHeapSize * yFactor);
             histogram[x] = Math.max(histogram[x] || 0, y);
        });

        var ctx = this._canvas.getContext("2d");
        this._clear(ctx);

        // +1 so that the border always fit into the canvas area.
        height = height + 1;

        ctx.beginPath();
        var initialY = 0;
        for (var k = 0; k < histogram.length; k++) {
            if (histogram[k]) {
                initialY = histogram[k];
                break;
            }
        }
        ctx.moveTo(0, height - initialY);

        for (var x = 0; x < histogram.length; x++) {
             if (!histogram[x])
                 continue;
             ctx.lineTo(x, height - histogram[x]);
        }

        ctx.lineWidth = 0.5;
        ctx.strokeStyle = "rgba(20,0,0,0.8)";
        ctx.stroke();

        ctx.fillStyle = "rgba(214,225,254, 0.8);";
        ctx.lineTo(width, 60);
        ctx.lineTo(0, 60);
        ctx.lineTo(0, height - initialY);
        ctx.fill();
        ctx.closePath();

        this._maxHeapSizeLabel.textContent = Number.bytesToString(maxTotalHeapSize);
    },

    _clear: function(ctx) {
        ctx.fillStyle = "rgba(255,255,255,0.8)";
        ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
    },

    _forAllRecords: WebInspector.TimelineOverviewPane.prototype._forAllRecords
}

/* TestController.js */

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.TestController = function(callId)
{
    this._callId = callId;
    this._waitUntilDone = false;
    this.results = [];
}

WebInspector.TestController.prototype = {
    waitUntilDone: function()
    {
        this._waitUntilDone = true;
    },

    notifyDone: function(result)
    {
        if (typeof result === "undefined" && this.results.length)
            result = this.results;
        var message = typeof result === "undefined" ? "\"<undefined>\"" : JSON.stringify(result);
        InspectorBackend.didEvaluateForTestInFrontend(this._callId, message);
    },

    runAfterPendingDispatches: function(callback)
    {
        if (WebInspector.pendingDispatches === 0) {
            callback();
            return;
        }
        setTimeout(this.runAfterPendingDispatches.bind(this), 0, callback);
    }
}

WebInspector.evaluateForTestInFrontend = function(callId, script)
{
    var controller = new WebInspector.TestController(callId);
    function invokeMethod()
    {
        try {
            var result;
            if (window[script] && typeof window[script] === "function")
                result = window[script].call(WebInspector, controller);
            else
                result = window.eval(script);

            if (!controller._waitUntilDone)
                controller.notifyDone(result);
        } catch (e) {
            controller.notifyDone(e.toString());
        }
    }
    controller.runAfterPendingDispatches(invokeMethod);
}

/* HelpScreen.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.HelpScreen = function(title)
{
    this._addStyleSheetIfNeeded("helpScreen.css");

    this._element = document.createElement("div");
    this._element.className = "help-window-outer";
    this._element.addEventListener("keydown", this._onKeyDown.bind(this), false);

    var mainWindow = this._element.createChild("div", "help-window-main");
    var captionWindow = mainWindow.createChild("div", "help-window-caption");
    var closeButton = captionWindow.createChild("button", "help-close-button");
    this.contentElement = mainWindow.createChild("div", "help-content");
    this.contentElement.tabIndex = 0;
    this.contentElement.addEventListener("blur", this._onBlur.bind(this), false);
    captionWindow.createChild("h1", "help-window-title").innerText = title;

    closeButton.innerText = "\u2716"; // Code stands for HEAVY MULTIPLICATION X.
    closeButton.addEventListener("click", this._hide.bind(this), false);
    this._closeKeys = [
        WebInspector.KeyboardShortcut.Keys.Enter.code,
        WebInspector.KeyboardShortcut.Keys.Esc.code,
        WebInspector.KeyboardShortcut.Keys.Space.code,
    ];
    document.body.appendChild(this._element);
}

WebInspector.HelpScreen.prototype = {
    show: function()
    {
        if (this._isShown)
            return;

        this._element.style.visibility = "visible";
        this._isShown = true;
        this._previousFocusElement = WebInspector.currentFocusElement;
        WebInspector.currentFocusElement = this.contentElement;
    },

    _hide: function()
    {
        this._isShown = false;
        this._element.style.visibility = "hidden";
        WebInspector.currentFocusElement = this._previousFocusElement;
    },

    _onKeyDown: function(event)
    {
        if (this._isShown && this._closeKeys.indexOf(event.keyCode) >= 0) {
            this._hide();
            event.stopPropagation();
        }
    },

    _onBlur: function()
    {
         // Pretend we're modal, grab focus back if we're still shown.
        if (this._isShown)
            WebInspector.currentFocusElement = this.contentElement;
    },

    _addStyleSheetIfNeeded: function(href)
    {
        if (WebInspector.HelpScreen._styleSheetAdded)
            return;
       
        WebInspector.HelpScreen._styleSheetAdded = true;
        var link = document.head.createChild("link");
        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = href;
    }
};

/* GoToLineDialog.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.GoToLineDialog = function(view)
{
    this._element = document.createElement("div");
    this._element.className = "go-to-line-dialog";
    this._element.addEventListener("keydown", this._onKeyDown.bind(this), false);
    this._closeKeys = [
        WebInspector.KeyboardShortcut.Keys.Enter.code,
        WebInspector.KeyboardShortcut.Keys.Esc.code,
    ];

    var dialogWindow = this._element;

    dialogWindow.createChild("label").innerText = WebInspector.UIString("Go to line: ");

    this._input = dialogWindow.createChild("input");
    this._input.setAttribute("type", "text");
    this._input.setAttribute("size", 6);
    var linesCount = view.sourceFrame.textModel.linesCount;
    if (linesCount)
        this._input.setAttribute("title", WebInspector.UIString("1 - %d", linesCount));
    var blurHandler = this._onBlur.bind(this);
    this._input.addEventListener("blur", blurHandler, false);
    

    var go = dialogWindow.createChild("button");
    go.innerText = WebInspector.UIString("Go");
    go.addEventListener("click", this._onClick.bind(this), false);
    go.addEventListener("mousedown", function(e) {
        // Ok button click will close the dialog, removing onBlur listener
        // to let click event be handled.
        this._input.removeEventListener("blur", blurHandler, false);
    }.bind(this), false);

    this._view = view;
    view.element.appendChild(this._element);

    this._previousFocusElement = WebInspector.currentFocusElement;
    WebInspector.currentFocusElement = this._input;
    this._input.select();
}

WebInspector.GoToLineDialog.show = function(sourceView)
{
    if (this._instance)
        return;
    this._instance = new WebInspector.GoToLineDialog(sourceView);
}

WebInspector.GoToLineDialog.prototype = {
    _hide: function()
    {
        if (this._isHiding)
            return;
        this._isHiding = true;

        WebInspector.currentFocusElement = this._previousFocusElement;
        WebInspector.GoToLineDialog._instance = null;
        this._element.parentElement.removeChild(this._element);
    },

    _onBlur: function(event)
    {
        this._hide();
    },

    _onKeyDown: function(event)
    {
        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
            event.preventDefault();
            return;
        }

        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Enter.code)
            this._highlightSelectedLine();

        if (this._closeKeys.indexOf(event.keyCode) >= 0) {
            this._hide();
            event.stopPropagation();
        }
    },

    _onClick: function(event)
    {
        this._highlightSelectedLine();
        this._hide();
    },

    _highlightSelectedLine: function()
    {
        var value = this._input.value;
        var lineNumber = parseInt(value, 10);
        if (!isNaN(lineNumber) && lineNumber > 0) {
            lineNumber = Math.min(lineNumber, this._view.sourceFrame.textModel.linesCount);
            this._view.highlightLine(lineNumber);
        }
    }
};

/* ShortcutsHelp.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.ShortcutsHelp = function()
{
    this._sections = {};
}

WebInspector.ShortcutsHelp.prototype = {
    section: function(name)
    {
        var section = this._sections[name];
        if (!section)
            this._sections[name] = section = new WebInspector.ShortcutsSection(name);
        return section;
    },

    show: function()
    {
        if (!this._helpScreen) {
            this._helpScreen = new WebInspector.HelpScreen(WebInspector.UIString("Keyboard Shortcuts"));
            this._buildTable(this._helpScreen.contentElement, 2);
        }
        this._helpScreen.show();
    },

    _buildTable: function(parent, nColumns)
    {
        var height = 0;
        var orderedSections = [];
        for (var section in this._sections) {
            height += this._sections[section]._height;
            orderedSections.push(this._sections[section])
        }
        function compareSections(a, b)
        {
            return a.order - b.order;
        }
        orderedSections = orderedSections.sort(compareSections);

        const wrapAfter = height / nColumns;
        var table = document.createElement("table");
        table.className = "help-table";
        var row = table.createChild("tr");

        // This manual layout ugliness should be gone once WebKit implements
        // pagination hints for CSS columns (break-inside etc).
        for (var section = 0; section < orderedSections.length;) {
            var td = row.createChild("td");
            td.style.width = (100 / nColumns) + "%";
            var column = td.createChild("table");
            for (var columnHeight = 0;
                columnHeight < wrapAfter && section < orderedSections.length;
                columnHeight += orderedSections[section]._height, section++) {
                orderedSections[section].renderSection(column);
            }
        }
        parent.appendChild(table);
    }
};

WebInspector.shortcutsHelp = new WebInspector.ShortcutsHelp();

WebInspector.ShortcutsSection = function(name)
{
    this.name = name;
    this._lines = [];
    this.order = ++WebInspector.ShortcutsSection._sequenceNumber;
};

WebInspector.ShortcutsSection._sequenceNumber = 0;

WebInspector.ShortcutsSection.prototype = {
    addKey: function(key, description)
    {
        this.addLine(this._renderKey(key), description);
    },

    addRelatedKeys: function(keys, description)
    {
        this.addLine(this._renderSequence(keys,"/"), description);
    },

    addAlternateKeys: function(keys, description)
    {
        this.addLine(this._renderSequence(keys,WebInspector.UIString("or")), description);
    },

    addLine: function(htmlKey, description)
    {
        this._lines.push({ key: htmlKey, text: description })
    },

    renderSection: function(parent)
    {
        this._renderHeader(parent);

        for (var line = 0; line < this._lines.length; ++line) {
            var tr = parent.createChild("tr");
            tr.createChild("td", "help-key-cell").innerHTML = this._lines[line].key + " : ";
            tr.createChild("td").innerText = this._lines[line].text;
        }
    },

    _renderHeader: function(parent)
    {
        var trHead = parent.createChild("tr");
 
        trHead.createChild("th");
        trHead.createChild("th").innerText = this.name;
    },

    _renderSequence: function(sequence, delimiter)
    {
        var delimiterHtml = '<span class="help-key-delimiter">' + delimiter.escapeHTML() + '</span>'
        return sequence.map(this._renderKey).join(delimiterHtml);
    },

    _renderKey: function(key)
    {
        function renderLabel(label)
        {
            return '<span class="help-key monospace">' + label.escapeHTML() + '</span>';
        }
        return key.split(" + ").map(renderLabel).join('<span class="help-combine-keys">+</span>');
    },

    get _height()
    {
        return this._lines.length + 2; // add some space for header
    }
};

/* HAREntry.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// See http://groups.google.com/group/http-archive-specification/web/har-1-2-spec
// for HAR specification.

WebInspector.HAREntry = function(resource)
{
    this._resource = resource;
}

WebInspector.HAREntry.prototype = {
    build: function()
    {
        return {
            pageref: this._resource.documentURL,
            startedDateTime: new Date(this._resource.startTime * 1000),
            time: WebInspector.HAREntry._toMilliseconds(this._resource.duration),
            request: this._buildRequest(),
            response: this._buildResponse(),
            // cache: {...}, -- Not supproted yet.
            timings: this._buildTimings()
        };
    },

    _buildRequest: function()
    {
        var res = {
            method: this._resource.requestMethod,
            url: this._resource.url,
            // httpVersion: "HTTP/1.1" -- Not available.
            // cookies: [] -- Not available.
            headers: this._buildHeaders(this._resource.requestHeaders),
            headersSize: -1, // Not available.
            bodySize: -1 // Not available.
        };
        if (this._resource.queryParameters)
            res.queryString = this._buildParameters(this._resource.queryParameters);
        if (this._resource.requestFormData)
            res.postData = this._buildPostData();
        return res;
    },

    _buildResponse: function()
    {
        return {
            status: this._resource.statusCode,
            statusText: this._resource.statusText,
            // "httpVersion": "HTTP/1.1" -- Not available.
            // "cookies": [],  -- Not available.
            headers: this._buildHeaders(this._resource.responseHeaders),
            content: this._buildContent(),
            redirectURL: this._resource.responseHeaderValue("Location") || "",
            headersSize: -1, // Not available.
            bodySize: this._resource.resourceSize
        };
    },

    _buildContent: function()
    {
        return {
            size: this._resource.resourceSize,
            // compression: 0, -- Not available.
            mimeType: this._resource.mimeType,
            // text: -- Not available.
        };
    },

    _buildTimings: function()
    {
        var waitForConnection = this._interval("connectStart", "connectEnd");
        var blocked;
        var connect;
        var dns = this._interval("dnsStart", "dnsEnd");
        var send = this._interval("sendStart", "sendEnd");
        var ssl = this._interval("sslStart", "sslEnd");

        if (ssl !== -1 && send !== -1)
            send -= ssl;

        if (this._resource.connectionReused) {
            connect = -1;
            blocked = waitForConnection;
        } else {
            blocked = 0;
            connect = waitForConnection;
            if (dns !== -1)
                connect -= dns;
        }

        return {
            blocked: blocked,
            dns: dns,
            connect: connect,
            send: send,
            wait: this._interval("sendEnd", "receiveHeadersEnd"),
            receive: WebInspector.HAREntry._toMilliseconds(this._resource.receiveDuration),
            ssl: ssl
        };
    },

    _buildHeaders: function(headers)
    {
        var result = [];
        for (var name in headers)
            result.push({ name: name, value: headers[name] });
        return result;
    },

    _buildPostData: function()
    {
        var res = {
            mimeType: this._resource.requestHeaderValue("Content-Type"),
            text: this._resource.requestFormData
        };
        if (this._resource.formParameters)
           res.params = this._buildParameters(this._resource.formParameters);
        return res;
    },

    _buildParameters: function(parameters)
    {
        return parameters.slice();
    },

    _interval: function(start, end)
    {
        var timing = this._resource.timing;
        if (!timing)
            return -1;
        var startTime = timing[start];
        return typeof startTime !== "number" || startTime === -1 ? -1 : Math.round(timing[end] - startTime);
    }
};

WebInspector.HAREntry._toMilliseconds = function(time)
{
    return time === -1 ? -1 : Math.round(time * 1000);
}

WebInspector.HARLog = function()
{
}

WebInspector.HARLog.prototype = {
    build: function()
    {
        var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent);
        
        return {
            version: "1.2",
            creator: {
                name: "WebInspector",
                version: webKitVersion ? webKitVersion[1] : "n/a"
            },
            pages: this._buildPages(),
            entries: Object.properties(WebInspector.resources).map(this._convertResource)
        }
    },

    _buildPages: function()
    {
        return [
            {
                startedDateTime: new Date(WebInspector.mainResource.startTime * 1000),
                id: WebInspector.mainResource.documentURL,
                title: "",
                pageTimings: this.buildMainResourceTimings()
            }
        ];
    },

    buildMainResourceTimings: function()
    {
        var resourcesPanel = WebInspector.panels.resources;
        var startTime = WebInspector.mainResource.startTime;
        return {
             onContentLoad: this._pageEventTime(resourcesPanel.mainResourceDOMContentTime),
             onLoad: this._pageEventTime(resourcesPanel.mainResourceLoadTime),
        }
    },

    _convertResource: function(id)
    {
        return (new WebInspector.HAREntry(WebInspector.resources[id])).build();
    },

    _pageEventTime: function(time)
    {
        var startTime = WebInspector.mainResource.startTime;
        if (time === -1 || startTime === -1)
            return -1;
        return WebInspector.HAREntry._toMilliseconds(time - startTime);
    }
};

/* DevTools.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * FIXME: change field naming style to use trailing underscore.
 * @fileoverview Tools is a main class that wires all components of the
 * DevTools frontend together. It is also responsible for overriding existing
 * WebInspector functionality while it is getting upstreamed into WebCore.
 */

var context = {};  // Used by WebCore's inspector routines.

(function () {
    Preferences.ignoreWhitespace = false;
    Preferences.samplingCPUProfiler = true;
    Preferences.heapProfilerPresent = true;
    Preferences.debuggerAlwaysEnabled = true;
    Preferences.profilerAlwaysEnabled = true;
    Preferences.canEditScriptSource = true;
    Preferences.onlineDetectionEnabled = false;
    Preferences.nativeInstrumentationEnabled = true;
})();

var devtools = devtools || {};

devtools.domContentLoaded = function()
{
    WebInspector.setAttachedWindow(WebInspector.queryParamsObject.docked === "true");
    if (WebInspector.queryParamsObject.toolbar_color && WebInspector.queryParamsObject.text_color)
        WebInspector.setToolbarColors(WebInspector.queryParamsObject.toolbar_color, WebInspector.queryParamsObject.text_color);
}
document.addEventListener("DOMContentLoaded", devtools.domContentLoaded, false);


// FIXME: This needs to be upstreamed.
(function InterceptProfilesPanelEvents()
{
    var oldShow = WebInspector.ProfilesPanel.prototype.show;
    WebInspector.ProfilesPanel.prototype.show = function()
    {
        this.enableToggleButton.visible = false;
        oldShow.call(this);
        // Show is called on every show event of a panel, so
        // we only need to intercept it once.
        WebInspector.ProfilesPanel.prototype.show = oldShow;
    };
})();


/*
 * @override
 * TODO(mnaganov): Restore l10n when it will be agreed that it is needed.
 */
WebInspector.UIString = function(string)
{
    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
};


/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */
(function()
{
    var originalDragStart = WebInspector.elementDragStart;
    WebInspector.elementDragStart = function(element)
    {
        if (element) {
            var glassPane = document.createElement("div");
            glassPane.style.cssText = "position:absolute;width:100%;height:100%;opacity:0;z-index:1";
            glassPane.id = "glass-pane-for-drag";
            element.parentElement.appendChild(glassPane);
        }

        originalDragStart.apply(this, arguments);
    };

    var originalDragEnd = WebInspector.elementDragEnd;
    WebInspector.elementDragEnd = function()
    {
        originalDragEnd.apply(this, arguments);

        var glassPane = document.getElementById("glass-pane-for-drag");
        if (glassPane)
            glassPane.parentElement.removeChild(glassPane);
    };
})();



///////////////////////////////////////////
// Chromium layout test harness support. //
///////////////////////////////////////////

WebInspector.runAfterPendingDispatchesQueue = [];

WebInspector.TestController.prototype.runAfterPendingDispatches = function(callback)
{
    WebInspector.runAfterPendingDispatchesQueue.push(callback);
};

WebInspector.queuesAreEmpty = function()
{
    var copy = this.runAfterPendingDispatchesQueue.slice();
    this.runAfterPendingDispatchesQueue = [];
    for (var i = 0; i < copy.length; ++i)
        copy[i].call(this);
};


/////////////////////////////
// Chromium theme support. //
/////////////////////////////

WebInspector.setToolbarColors = function(backgroundColor, color)
{
    if (!WebInspector._themeStyleElement) {
        WebInspector._themeStyleElement = document.createElement("style");
        document.head.appendChild(WebInspector._themeStyleElement);
    }
    WebInspector._themeStyleElement.textContent =
        "#toolbar {\
             background-image: none !important;\
             background-color: " + backgroundColor + " !important;\
         }\
         \
         .toolbar-label {\
             color: " + color + " !important;\
             text-shadow: none;\
         }";
}

WebInspector.resetToolbarColors = function()
{
    if (WebInspector._themeStyleElement)
        WebInspector._themeStyleElement.textContent = "";

}

////////////////////////////////////////////////////////
// Platform-specific WebInspector extensions support. //
////////////////////////////////////////////////////////

WebInspector.platformExtensionAPI = function(tabId)
{
    function getTabId()
    {
        return tabId;
    }
    webInspector.inspectedWindow.__proto__.__defineGetter__("tabId", getTabId);
}

WebInspector.buildPlatformExtensionAPI = function()
{
    return "(" + WebInspector.platformExtensionAPI + ")(" + WebInspector._inspectedTabId + ");";
}

WebInspector.setInspectedTabId = function(tabId)
{
    WebInspector._inspectedTabId = tabId;
}

/* Tests.js */

/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains small testing framework along with the
 * test suite for the frontend. These tests are a part of the continues build
 * and are executed by the devtools_sanity_unittest.cc as a part of the
 * Interactive UI Test suite.
 * FIXME: change field naming style to use trailing underscore.
 */

if (window.domAutomationController) {

var ___interactiveUiTestsMode = true;

/**
 * Test suite for interactive UI tests.
 * @constructor
 */
TestSuite = function()
{
    this.controlTaken_ = false;
    this.timerId_ = -1;
};


/**
 * Reports test failure.
 * @param {string} message Failure description.
 */
TestSuite.prototype.fail = function(message)
{
    if (this.controlTaken_)
        this.reportFailure_(message);
    else
        throw message;
};


/**
 * Equals assertion tests that expected === actual.
 * @param {Object} expected Expected object.
 * @param {Object} actual Actual object.
 * @param {string} opt_message User message to print if the test fails.
 */
TestSuite.prototype.assertEquals = function(expected, actual, opt_message)
{
    if (expected !== actual) {
        var message = "Expected: '" + expected + "', but was '" + actual + "'";
        if (opt_message)
            message = opt_message + "(" + message + ")";
        this.fail(message);
    }
};


/**
 * True assertion tests that value == true.
 * @param {Object} value Actual object.
 * @param {string} opt_message User message to print if the test fails.
 */
TestSuite.prototype.assertTrue = function(value, opt_message)
{
    this.assertEquals(true, !!value, opt_message);
};


/**
 * Contains assertion tests that string contains substring.
 * @param {string} string Outer.
 * @param {string} substring Inner.
 */
TestSuite.prototype.assertContains = function(string, substring)
{
    if (string.indexOf(substring) === -1)
        this.fail("Expected to: '" + string + "' to contain '" + substring + "'");
};


/**
 * Takes control over execution.
 */
TestSuite.prototype.takeControl = function()
{
    this.controlTaken_ = true;
    // Set up guard timer.
    var self = this;
    this.timerId_ = setTimeout(function() {
        self.reportFailure_("Timeout exceeded: 20 sec");
    }, 20000);
};


/**
 * Releases control over execution.
 */
TestSuite.prototype.releaseControl = function()
{
    if (this.timerId_ !== -1) {
        clearTimeout(this.timerId_);
        this.timerId_ = -1;
    }
    this.reportOk_();
};


/**
 * Async tests use this one to report that they are completed.
 */
TestSuite.prototype.reportOk_ = function()
{
    window.domAutomationController.send("[OK]");
};


/**
 * Async tests use this one to report failures.
 */
TestSuite.prototype.reportFailure_ = function(error)
{
    if (this.timerId_ !== -1) {
        clearTimeout(this.timerId_);
        this.timerId_ = -1;
    }
    window.domAutomationController.send("[FAILED] " + error);
};


/**
 * Runs all global functions starting with "test" as unit tests.
 */
TestSuite.prototype.runTest = function(testName)
{
    try {
        this[testName]();
        if (!this.controlTaken_)
            this.reportOk_();
    } catch (e) {
        this.reportFailure_(e);
    }
};


/**
 * @param {string} panelName Name of the panel to show.
 */
TestSuite.prototype.showPanel = function(panelName)
{
    // Open Scripts panel.
    var toolbar = document.getElementById("toolbar");
    var button = toolbar.getElementsByClassName(panelName)[0];
    button.click();
    this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel);
};


/**
 * Overrides the method with specified name until it's called first time.
 * @param {Object} receiver An object whose method to override.
 * @param {string} methodName Name of the method to override.
 * @param {Function} override A function that should be called right after the
 *     overriden method returns.
 * @param {boolean} opt_sticky Whether restore original method after first run
 *     or not.
 */
TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky)
{
    var orig = receiver[methodName];
    if (typeof orig !== "function")
        this.fail("Cannot find method to override: " + methodName);
    var test = this;
    receiver[methodName] = function(var_args) {
        try {
            var result = orig.apply(this, arguments);
        } finally {
            if (!opt_sticky)
                receiver[methodName] = orig;
        }
        // In case of exception the override won't be called.
        try {
            override.apply(this, arguments);
        } catch (e) {
            test.fail("Exception in overriden method '" + methodName + "': " + e);
        }
        return result;
    };
};


// UI Tests


/**
 * Tests that resources tab is enabled when corresponding item is selected.
 */
TestSuite.prototype.testEnableResourcesTab = function()
{
    this.showPanel("resources");

    var test = this;
    this.addSniffer(WebInspector, "updateResource",
        function(payload) {
            test.assertEquals("simple_page.html", payload.lastPathComponent);
            WebInspector.panels.resources.refresh();
            WebInspector.panels.resources.revealAndSelectItem(WebInspector.resources[payload.id]);

            test.releaseControl();
        });

    // Following call should lead to reload that we capture in the
    // addResource override.
    WebInspector.panels.resources._enableResourceTracking();

    // We now have some time to report results to controller.
    this.takeControl();
};


/**
 * Tests that correct content length is reported for resources.
 */
TestSuite.prototype.testResourceContentLength = function()
{
    this.showPanel("resources");
    var test = this;

    var png = false;
    var html = false;
    this.addSniffer(WebInspector, "updateResource",
        function(payload) {
            if (!payload.didLengthChange)
                return;
            var resource = WebInspector.resources[payload.id];
            if (!resource || !resource.url)
                return;
            if (resource.url.search("image.html") !== -1) {
              var expectedLength = 87;
              test.assertTrue(
                  resource.resourceSize <= expectedLength,
                  "image.html content length is greater thatn expected.");
              if (expectedLength === resource.resourceSize)
                  html = true;
            } else if (resource.url.search("image.png") !== -1) {
              var expectedLength = 257796;
              test.assertTrue(
                  resource.resourceSize <= expectedLength,
                  "image.png content length is greater than expected.");
              if (expectedLength === resource.resourceSize)
                  png = true;
            }
            if (html && png) {
              // Wait 1 second before releasing control to check that the content
              // lengths are not updated anymore.
              setTimeout(function() {
                  test.releaseControl();
              }, 1000);
            }
        }, true);

    // Make sure resource tracking is on.
    WebInspector.panels.resources._enableResourceTracking();
    // Reload inspected page to update all resources.
    test.evaluateInConsole_(
        "window.location.reload(true);",
         function(resultText) {
             test.assertEquals("undefined", resultText, "Unexpected result of reload().");
         });

    // We now have some time to report results to controller.
    this.takeControl();
};


/**
 * Tests resource headers.
 */
TestSuite.prototype.testResourceHeaders = function()
{
    this.showPanel("resources");

    var test = this;

    var responseOk = false;
    var timingOk = false;

    this.addSniffer(WebInspector, "updateResource",
        function(payload) {
            var resource = this.resources[payload.id];
            if (!resource || resource.mainResource) {
                // We are only interested in secondary resources in this test.
                return;
            }

            var requestHeaders = JSON.stringify(resource.requestHeaders);
            test.assertContains(requestHeaders, "Accept");

            if (payload.didResponseChange) {
                var responseHeaders = JSON.stringify(resource.responseHeaders);
                test.assertContains(responseHeaders, "Content-type");
                test.assertContains(responseHeaders, "Content-Length");
                test.assertTrue(typeof resource.responseReceivedTime !== "undefined");
                responseOk = true;
            }

            if (payload.didTimingChange) {
                test.assertTrue(typeof resource.startTime !== "undefined");
                timingOk = true;
            }

            if (payload.didCompletionChange) {
                test.assertTrue(responseOk);
                test.assertTrue(timingOk);
                test.assertTrue(typeof resource.endTime !== "undefined");
                test.releaseControl();
            }
        }, true);

    WebInspector.panels.resources._enableResourceTracking();
    this.takeControl();
};


/**
 * Tests the mime type of a cached (HTTP 304) resource.
 */
TestSuite.prototype.testCachedResourceMimeType = function()
{
    this.showPanel("resources");

    var test = this;
    var hasReloaded = false;

    this.addSniffer(WebInspector, "updateResource",
        function(payload) {
            var resource = this.resources[payload.id];
            if (!resource || resource.mainResource) {
                // We are only interested in secondary resources in this test.
                return;
            }

            if (payload.didResponseChange) {
                // Test server uses a default mime type for JavaScript files.
                test.assertEquals("text/html", payload.mimeType);
                if (!hasReloaded) {
                    hasReloaded = true;
                    // Reload inspected page to update all resources.
                    test.evaluateInConsole_("window.location.reload(true);", function() {});
                } else
                    test.releaseControl();
            }

        }, true);

    WebInspector.panels.resources._enableResourceTracking();
    this.takeControl();
};


/**
 * Tests that profiler works.
 */
TestSuite.prototype.testProfilerTab = function()
{
    this.showPanel("profiles");

    var panel = WebInspector.panels.profiles;
    var test = this;

    function findDisplayedNode() {
        var node = panel.visibleView.profileDataGridTree.children[0];
        if (!node) {
            // Profile hadn't been queried yet, re-schedule.
            window.setTimeout(findDisplayedNode, 100);
            return;
        }

        // Iterate over displayed functions and search for a function
        // that is called "fib" or "eternal_fib". If found, this will mean
        // that we actually have profiled page's code.
        while (node) {
            if (node.functionName.indexOf("fib") !== -1)
                test.releaseControl();
            node = node.traverseNextNode(true, null, true);
        }

        test.fail();
    }

    function findVisibleView() {
        if (!panel.visibleView) {
            setTimeout(findVisibleView, 0);
            return;
        }
        setTimeout(findDisplayedNode, 0);
    }

    findVisibleView();
    this.takeControl();
};


/**
 * Tests that heap profiler works.
 */
TestSuite.prototype.testHeapProfiler = function()
{
    this.showPanel("profiles");

    var panel = WebInspector.panels.profiles;
    var test = this;

    function findDisplayedNode() {
        var node = panel.visibleView.dataGrid.children[0];
        if (!node) {
            // Profile hadn't been queried yet, re-schedule.
            window.setTimeout(findDisplayedNode, 100);
            return;
        }

        // Iterate over displayed functions and find node called "A"
        // If found, this will mean that we actually have taken heap snapshot.
        while (node) {
            if (node.constructorName.indexOf("A") !== -1) {
                test.releaseControl();
                return;
            }
            node = node.traverseNextNode(false, null, true);
        }

        test.fail();
    }

    function findVisibleView() {
        if (!panel.visibleView) {
            setTimeout(findVisibleView, 0);
            return;
        }
        setTimeout(findDisplayedNode, 0);
    }

    WebInspector.HeapSnapshotProfileType.prototype.buttonClicked();
    findVisibleView();
    this.takeControl();
};


/**
 * Tests that scripts tab can be open and populated with inspected scripts.
 */
TestSuite.prototype.testShowScriptsTab = function()
{
    this.showPanel("scripts");
    var test = this;
    // There should be at least main page script.
    this._waitUntilScriptsAreParsed(["debugger_test_page.html"],
        function() {
            test.releaseControl();
        });
    // Wait until all scripts are added to the debugger.
    this.takeControl();
};


/**
 * Tests that scripts tab is populated with inspected scripts even if it
 * hadn't been shown by the moment inspected paged refreshed.
 * @see http://crbug.com/26312
 */
TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function()
{
    var test = this;
    this.assertEquals(WebInspector.panels.elements, WebInspector.currentPanel, "Elements panel should be current one.");

    this.addSniffer(WebInspector.panels.scripts, "reset", waitUntilScriptIsParsed);

    // Reload inspected page. It will reset the debugger agent.
    test.evaluateInConsole_(
        "window.location.reload(true);",
        function(resultText) {});

    function waitUntilScriptIsParsed() {
        test.showPanel("scripts");
        test._waitUntilScriptsAreParsed(["debugger_test_page.html"],
            function() {
                test.releaseControl();
            });
    }

    // Wait until all scripts are added to the debugger.
    this.takeControl();
};


/**
 * Tests that scripts list contains content scripts.
 */
TestSuite.prototype.testContentScriptIsPresent = function()
{
    this.showPanel("scripts");
    var test = this;

    test._waitUntilScriptsAreParsed(
        ["page_with_content_script.html", "simple_content_script.js"],
        function() {
          test.releaseControl();
        });

    // Wait until all scripts are added to the debugger.
    this.takeControl();
};


/**
 * Tests that scripts are not duplicaed on Scripts tab switch.
 */
TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function()
{
    var test = this;

    // There should be two scripts: one for the main page and another
    // one which is source of console API(see
    // InjectedScript._ensureCommandLineAPIInstalled).
    var expectedScriptsCount = 2;
    var parsedScripts = [];

    this.showPanel("scripts");


    function switchToElementsTab() {
        test.showPanel("elements");
        setTimeout(switchToScriptsTab, 0);
    }

    function switchToScriptsTab() {
        test.showPanel("scripts");
        setTimeout(checkScriptsPanel, 0);
    }

    function checkScriptsPanel() {
        test.assertTrue(!!WebInspector.panels.scripts.visibleView, "No visible script view.");
        test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing.");
        checkNoDuplicates();
        test.releaseControl();
    }

    function checkNoDuplicates() {
        var scriptSelect = document.getElementById("scripts-files");
        var options = scriptSelect.options;
        for (var i = 0; i < options.length; i++) {
            var scriptName = options[i].text;
            for (var j = i + 1; j < options.length; j++)
                test.assertTrue(scriptName !== options[j].text, "Found script duplicates: " + test.optionsToString_(options));
        }
    }

    test._waitUntilScriptsAreParsed(
        ["debugger_test_page.html"],
        function() {
            checkNoDuplicates();
            setTimeout(switchToElementsTab, 0);
        });


    // Wait until all scripts are added to the debugger.
    this.takeControl();
};


// Tests that debugger works correctly if pause event occurs when DevTools
// frontend is being loaded.
TestSuite.prototype.testPauseWhenLoadingDevTools = function()
{
    this.showPanel("scripts");
    var test = this;

    var expectations = {
            functionsOnStack: ["callDebugger"],
            lineNumber: 8,
            lineText: "  debugger;"
        };


    // Script execution can already be paused.
    if (WebInspector.currentPanel.paused) {
        var callFrame = WebInspector.currentPanel.sidebarPanes.callstack.selectedCallFrame;
        this.assertEquals(expectations.functionsOnStack[0], callFrame.functionName);
        var callbackInvoked = false;
        this._checkSourceFrameWhenLoaded(expectations, function() {
                callbackInvoked = true;
                if (test.controlTaken_)
                    test.releaseControl();
            });
        if (!callbackInvoked) {
            test.takeControl();
        }
        return;
    }

    this._waitForScriptPause(
        {
            functionsOnStack: ["callDebugger"],
            lineNumber: 8,
            lineText: "  debugger;"
        },
        function() {
            test.releaseControl();
        });
    this.takeControl();
};


// Tests that pressing "Pause" will pause script execution if the script
// is already running.
TestSuite.prototype.testPauseWhenScriptIsRunning = function()
{
    this.showPanel("scripts");
    var test = this;

    test.evaluateInConsole_(
        'setTimeout("handleClick()" , 0)',
        function(resultText) {
          test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText);
          testScriptPauseAfterDelay();
        });

    // Wait for some time to make sure that inspected page is running the
    // infinite loop.
    function testScriptPauseAfterDelay() {
        setTimeout(testScriptPause, 300);
    }

    function testScriptPause() {
        // The script should be in infinite loop. Click "Pause" button to
        // pause it and wait for the result.
        WebInspector.panels.scripts.pauseButton.click();

        test._waitForScriptPause(
            {
                functionsOnStack: ["handleClick", ""],
                lineNumber: 5,
                lineText: "  while(true) {"
            },
            function() {
                test.releaseControl();
            });
    }

    this.takeControl();
};


/**
 * Serializes options collection to string.
 * @param {HTMLOptionsCollection} options
 * @return {string}
 */
TestSuite.prototype.optionsToString_ = function(options)
{
    var names = [];
    for (var i = 0; i < options.length; i++)
        names.push('"' + options[i].text + '"');
    return names.join(",");
};


/**
 * Ensures that main HTML resource is selected in Scripts panel and that its
 * source frame is setup. Invokes the callback when the condition is satisfied.
 * @param {HTMLOptionsCollection} options
 * @param {function(WebInspector.SourceView,string)} callback
 */
TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback)
{
    var test = this;

    var scriptSelect = document.getElementById("scripts-files");
    var options = scriptSelect.options;

    test.assertTrue(options.length, "Scripts list is empty");

    // Select page's script if it's not current option.
    var scriptResource;
    if (options[scriptSelect.selectedIndex].text === scriptName)
        scriptResource = options[scriptSelect.selectedIndex].representedObject;
    else {
        var pageScriptIndex = -1;
        for (var i = 0; i < options.length; i++) {
            if (options[i].text === scriptName) {
                pageScriptIndex = i;
                break;
            }
        }
        test.assertTrue(-1 !== pageScriptIndex, "Script with url " + scriptName + " not found among " + test.optionsToString_(options));
        scriptResource = options[pageScriptIndex].representedObject;

        // Current panel is "Scripts".
        WebInspector.currentPanel._showScriptOrResource(scriptResource);
        test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex, "Unexpected selected option index.");
    }

    test.assertTrue(scriptResource instanceof WebInspector.Resource,
                    "Unexpected resource class.");
    test.assertTrue(!!scriptResource.url, "Resource URL is null.");
    test.assertTrue(scriptResource.url.search(scriptName + "$") !== -1, "Main HTML resource should be selected.");

    var scriptsPanel = WebInspector.panels.scripts;

    var view = scriptsPanel.visibleView;
    test.assertTrue(view instanceof WebInspector.SourceView);

    if (!view.sourceFrame._loaded) {
        test.addSniffer(view, "_sourceFrameSetupFinished", function(event) {
            callback(view, scriptResource.url);
        });
    } else
        callback(view, scriptResource.url);
};


/*
 * Evaluates the code in the console as if user typed it manually and invokes
 * the callback when the result message is received and added to the console.
 * @param {string} code
 * @param {function(string)} callback
 */
TestSuite.prototype.evaluateInConsole_ = function(code, callback)
{
    WebInspector.showConsole();
    WebInspector.console.prompt.text = code;
    WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter"));

    this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage",
        function(commandResult) {
            callback(commandResult.toMessageElement().textContent);
        });
};


/**
 * Tests that console auto completion works when script execution is paused.
 */
TestSuite.prototype.testCompletionOnPause = function()
{
    this.showPanel("scripts");
    var test = this;
    this._executeCodeWhenScriptsAreParsed("handleClick()", ["completion_on_pause.html"]);

    this._waitForScriptPause(
        {
            functionsOnStack: ["innerFunction", "handleClick", ""],
            lineNumber: 9,
            lineText: "    debugger;"
        },
        showConsole);

    function showConsole() {
        if (WebInspector.currentFocusElement === WebInspector.console.promptElement)
            testLocalsCompletion();
        else {
            test.addSniffer(WebInspector.console, "afterShow", testLocalsCompletion);
            WebInspector.showConsole();
        }
    }

    function testLocalsCompletion() {
        checkCompletions("th", ["parameter1", "closureLocal", "p", "createClosureLocal"], testThisCompletion);
    }

    function testThisCompletion() {
        checkCompletions("this.", ["field1", "field2", "m"], testFieldCompletion);
    }

    function testFieldCompletion() {
        checkCompletions("this.field1.", ["id", "name"], function() { test.releaseControl(); });
    }

    function checkCompletions(expression, expectedProperties, callback) {
        test.addSniffer(WebInspector.console, "_reportCompletions",
            function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
                test.assertTrue(!isException, "Exception while collecting completions");
                for (var i = 0; i < expectedProperties.length; i++) {
                    var name = expectedProperties[i];
                    test.assertTrue(result[name], "Name " + name + " not found among the completions: " + JSON.stringify(result));
                }
                setTimeout(callback, 0);
            });
      WebInspector.console.prompt.text = expression;
      WebInspector.console.prompt.autoCompleteSoon();
    }

    this.takeControl();
};


/**
 * Checks current execution line against expectations.
 * @param {WebInspector.SourceFrame} sourceFrame
 * @param {number} lineNumber Expected line number
 * @param {string} lineContent Expected line text
 */
TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber, lineContent)
{
    this.assertEquals(lineNumber, sourceFrame.executionLine, "Unexpected execution line number.");
    this.assertEquals(lineContent, sourceFrame._textModel.line(lineNumber - 1), "Unexpected execution line text.");
}


/**
 * Checks that all expected scripts are present in the scripts list
 * in the Scripts panel.
 * @param {Array.<string>} expected Regular expressions describing
 *     expected script names.
 * @return {boolean} Whether all the scripts are in "scripts-files" select
 *     box
 */
TestSuite.prototype._scriptsAreParsed = function(expected)
{
    var scriptSelect = document.getElementById("scripts-files");
    var options = scriptSelect.options;

    // Check that at least all the expected scripts are present.
    var missing = expected.slice(0);
    for (var i = 0 ; i < options.length; i++) {
        for (var j = 0; j < missing.length; j++) {
            if (options[i].text.search(missing[j]) !== -1) {
                missing.splice(j, 1);
                break;
            }
        }
    }
    return missing.length === 0;
};


/**
 * Waits for script pause, checks expectations, and invokes the callback.
 * @param {Object} expectations  Dictionary of expectations
 * @param {function():void} callback
 */
TestSuite.prototype._waitForScriptPause = function(expectations, callback)
{
    var test = this;
    // Wait until script is paused.
    test.addSniffer(
        WebInspector,
        "pausedScript",
        function(details) {
            var callFrames = details.callFrames;
            var functionsOnStack = [];
            for (var i = 0; i < callFrames.length; i++)
                functionsOnStack.push(callFrames[i].functionName);

            test.assertEquals(expectations.functionsOnStack.join(","), functionsOnStack.join(","), "Unexpected stack.");

            // Check that execution line where the script is paused is
            // expected one.
            test._checkSourceFrameWhenLoaded(expectations, callback);
        });
};


/**
 * Waits for current source frame to load, checks expectations, and invokes
 * the callback.
 * @param {Object} expectations  Dictionary of expectations
 * @param {function():void} callback
 */
TestSuite.prototype._checkSourceFrameWhenLoaded = function(expectations, callback)
{
    var test = this;

    var frame = WebInspector.currentPanel.visibleView.sourceFrame;
    if (frame._loaded)
        checkExecLine();
    else {
        setTimeout(function() {
            test._checkSourceFrameWhenLoaded(expectations, callback);
        }, 100);
    }
    function checkExecLine() {
        test._checkExecutionLine(frame, expectations.lineNumber, expectations.lineText);
        callback();
    }
};


/**
 * Waits until all the scripts are parsed and asynchronously executes the code
 * in the inspected page.
 */
TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts)
{
    var test = this;

    function executeFunctionInInspectedPage() {
        // Since breakpoints are ignored in evals' calculate() function is
        // execute after zero-timeout so that the breakpoint is hit.
        test.evaluateInConsole_(
            'setTimeout("' + code + '" , 0)',
            function(resultText) {
                test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code);
            });
    }

    test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage);
};


/**
 * Waits until all the scripts are parsed and invokes the callback.
 */
TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback)
{
    var test = this;

    function waitForAllScripts() {
        if (test._scriptsAreParsed(expectedScripts))
            callback();
        else
            test.addSniffer(WebInspector, "parsedScriptSource", waitForAllScripts);
    }

    waitForAllScripts();
};


/**
 * Executes the 'code' with InjectedScriptAccess.getProperties overriden
 * so that all callbacks passed to InjectedScriptAccess.getProperties are
 * extended with the "hook".
 * @param {Function} hook The hook function.
 * @param {Function} code A code snippet to be executed.
 */
TestSuite.prototype._hookGetPropertiesCallback = function(hook, code)
{
    var accessor = InjectedScriptAccess.prototype;
    var orig = accessor.getProperties;
    accessor.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate, callback) {
        orig.call(this, objectProxy, ignoreHasOwnProperty, abbreviate,
            function() {
              callback.apply(this, arguments);
              hook();
            });
    };
    try {
        code();
    } finally {
        accessor.getProperties = orig;
    }
};


/**
 * Tests "Pause" button will pause debugger when a snippet is evaluated.
 */
TestSuite.prototype.testPauseInEval = function()
{
    this.showPanel("scripts");

    var test = this;

    var pauseButton = document.getElementById("scripts-pause");
    pauseButton.click();

    devtools.tools.evaluateJavaScript("fib(10)");

    this.addSniffer(WebInspector, "pausedScript",
        function() {
            test.releaseControl();
        });

    test.takeControl();
};


/**
 * Key event with given key identifier.
 */
TestSuite.createKeyEvent = function(keyIdentifier)
{
    var evt = document.createEvent("KeyboardEvent");
    evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, "");
    return evt;
};


/**
 * Test runner for the test suite.
 */
var uiTests = {};


/**
 * Run each test from the test suit on a fresh instance of the suite.
 */
uiTests.runAllTests = function()
{
    // For debugging purposes.
    for (var name in TestSuite.prototype) {
        if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function")
            uiTests.runTest(name);
    }
};


/**
 * Run specified test on a fresh instance of the test suite.
 * @param {string} name Name of a test method from TestSuite class.
 */
uiTests.runTest = function(name)
{
    if (uiTests._populatedInterface)
        new TestSuite().runTest(name);
    else
        uiTests._pendingTestName = name;
};

(function() {

function runTests()
{
    uiTests._populatedInterface = true;
    var name = uiTests._pendingTestName;
    delete uiTests._pendingTestName;
    if (name)
        new TestSuite().runTest(name);
}

var oldShowElementsPanel = WebInspector.showElementsPanel;
WebInspector.showElementsPanel = function()
{
    oldShowElementsPanel.call(this);
    runTests();
}

var oldShowPanel = WebInspector.showPanel;
WebInspector.showPanel = function(name)
{
    oldShowPanel.call(this, name);
    runTests();
}

})();

}

