blob: fe415527aceb55e59919e7f8da1645e637713b8d [file] [log] [blame]
/* 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.proxyType = function(objectProxy)
{
if (objectProxy === null)
return "null";
var type = typeof objectProxy;
if (type !== "object" && type !== "function")
return type;
return objectProxy.type;
}
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);
return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
}
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;
}
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;
var newClassName = " " + this.className + " ";
this.className = newClassName.replace(" " + className + " ", " ");
}
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";
}
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()
{
this.innerHTML = "";
}
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.__defineGetter__("totalOffsetLeft", function()
{
var total = 0;
for (var element = this; element; element = element.offsetParent)
total += element.offsetLeft;
return total;
});
Element.prototype.__defineGetter__("totalOffsetTop", function()
{
var total = 0;
for (var element = this; element; element = element.offsetParent)
total += element.offsetTop;
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;
}
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;");
}
String.prototype.collapseWhitespace = function()
{
return this.replace(/[\s\xA0]+/g, " ");
}
String.prototype.trimLeadingWhitespace = function()
{
return this.replace(/^[\s\xA0]+/g, "");
}
String.prototype.trimTrailingWhitespace = function()
{
return this.replace(/[\s\xA0]+$/g, "");
}
String.prototype.trimWhitespace = function()
{
return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, "");
}
String.prototype.trimURL = function(baseURLDomain)
{
var result = this.replace(/^https?:\/\//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.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("%.3fMB", 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);
}
}
function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
{
// indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound.
return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1);
}
function indexOfObjectInListSortedByFunction(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;
}
}
// By returning 1 less than the negative lower search bound, we can reuse this function
// for both indexOf and insertionIndexFor, with some simple arithmetic.
return (-first - 1);
}
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" && Object.proxyType(substitution) === "number")
substitution = substitution.description;
substitution = parseInt(substitution);
return !isNaN(substitution) ? substitution : 0;
},
f: function(substitution, token)
{
if (typeof substitution == "object" && Object.proxyType(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" && Object.proxyType(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 isFnKey(event) {
return event.keyCode >= 112 && event.keyCode <= 123;
}
/* 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;
}
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);
child.deselect();
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 know, 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.handleKeyEvent = function(event)
{
if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
return false;
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();
}
}
}
if (nextSelectedElement) {
nextSelectedElement.reveal();
nextSelectedElement.select();
}
if (handled) {
event.preventDefault();
event.stopPropagation();
}
return handled;
}
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.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;
if (this._listItemNode)
this._listItemNode.innerHTML = x;
},
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();
}
}
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._listItemNode.innerHTML = this._title;
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.treeElementSelected, 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.treeElementSelected = function(event)
{
var element = event.currentTarget;
if (!element || !element.treeElement || !element.treeElement.selectable)
return;
if (element.treeElement.isEventWithinDisclosureTriangle(event))
return;
element.treeElement.select();
}
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();
}
}
TreeElement.treeElementDoubleClicked = function(event)
{
var element = event.currentTarget;
if (!element || !element.treeElement)
return;
if (element.treeElement.ondblclick)
element.treeElement.ondblclick(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.select = function(supressOnSelect)
{
if (!this.treeOutline || !this.selectable || this.selected)
return;
if (this.treeOutline.selectedTreeElement)
this.treeOutline.selectedTreeElement.deselect();
this.selected = true;
this.treeOutline.selectedTreeElement = this;
if (this._listItemNode)
this._listItemNode.addStyleClass("selected");
if (this.onselect && !supressOnSelect)
this.onselect(this);
}
TreeElement.prototype.deselect = function(supressOnDeselect)
{
if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
return;
this.selected = false;
this.treeOutline.selectedTreeElement = null;
if (this._listItemNode)
this._listItemNode.removeStyleClass("selected");
if (this.ondeselect && !supressOnDeselect)
this.ondeselect(this);
}
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.
*/
var Preferences = {
showUserAgentStyles: true,
maxInlineTextChildLength: 80,
minConsoleHeight: 75,
minSidebarWidth: 100,
minElementsSidebarWidth: 200,
minScriptsSidebarWidth: 200,
showInheritedComputedStyleProperties: false,
styleRulesExpandedState: {},
showMissingLocalizedStrings: false,
heapProfilerPresent: false,
samplingCPUProfiler: false,
showColorNicknames: true,
colorFormat: "hex",
eventListenersFilter: "all",
resourcesLargeRows: true
}
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";
}
preloadImages();
var WebInspector = {
resources: {},
resourceURLMap: {},
cookieDomains: {},
missingLocalizedStrings: {},
pendingDispatches: 0,
get platform()
{
if (!("_platform" in this))
this._platform = InspectorFrontendHost.platform();
return this._platform;
},
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);
}
},
_createPanels: function()
{
var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
if (hiddenPanels.indexOf("elements") === -1)
this.panels.elements = new WebInspector.ElementsPanel();
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 (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
this.panels.storage = new WebInspector.StoragePanel();
if (hiddenPanels.indexOf("console") === -1)
this.panels.console = new WebInspector.ConsolePanel();
},
_loadPreferences: function()
{
var colorFormat = InspectorFrontendHost.setting("color-format");
if (colorFormat)
Preferences.colorFormat = colorFormat;
var eventListenersFilter = InspectorFrontendHost.setting("event-listeners-filter");
if (eventListenersFilter)
Preferences.eventListenersFilter = eventListenersFilter;
var resourcesLargeRows = InspectorFrontendHost.setting("resources-large-rows");
if (typeof resourcesLargeRows !== "undefined")
Preferences.resourcesLargeRows = resourcesLargeRows;
},
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) {
InspectorFrontendHost.attach();
body.removeStyleClass("detached");
body.addStyleClass("attached");
dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
} else {
InspectorFrontendHost.detach();
body.removeStyleClass("attached");
body.addStyleClass("detached");
dockToggleButton.title = WebInspector.UIString("Dock to main window.");
}
},
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);
}
},
get hoveredDOMNode()
{
return this._hoveredDOMNode;
},
set hoveredDOMNode(x)
{
if (this._hoveredDOMNode === x)
return;
this._hoveredDOMNode = x;
if (this._hoveredDOMNode)
this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
else
this._updateHoverHighlight();
},
_updateHoverHighlightSoon: function(delay)
{
if ("_updateHoverHighlightTimeout" in this)
clearTimeout(this._updateHoverHighlightTimeout);
this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
},
_updateHoverHighlight: function()
{
if ("_updateHoverHighlightTimeout" in this) {
clearTimeout(this._updateHoverHighlightTimeout);
delete this._updateHoverHighlightTimeout;
}
if (this._hoveredDOMNode) {
InspectorBackend.highlightDOMNode(this._hoveredDOMNode.id);
this.showingDOMNodeHighlight = true;
} else {
InspectorBackend.hideDOMNodeHighlight();
this.showingDOMNodeHighlight = false;
}
}
}
WebInspector.loaded = function()
{
var platform = WebInspector.platform;
document.body.addStyleClass("platform-" + platform);
var port = WebInspector.port;
document.body.addStyleClass("port-" + port);
this._loadPreferences();
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)"),
other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
};
this.panels = {};
this._createPanels();
var toolbarElement = document.getElementById("toolbar");
var previousToolbarItem = toolbarElement.children[0];
this.panelOrder = [];
for (var panelName in this.panels) {
var panel = this.panels[panelName];
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);
previousToolbarItem = panelToolbarItem;
}
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("unload", this.windowUnload.bind(this), true);
window.addEventListener("resize", this.windowResize.bind(this), true);
document.addEventListener("focus", this.focusChanged.bind(this), true);
document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
document.addEventListener("copy", this.documentCopy.bind(this), true);
document.addEventListener("contextmenu", this.contextMenu.bind(this), true);
var mainPanelsElement = document.getElementById("main-panels");
mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);
// Focus the mainPanelsElement in a timeout so it happens after the initial focus,
// so it doesn't get reset to the first toolbar button. This initial focus happens
// on Mac when the window is made key and the WebHTMLView becomes the first responder.
setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);
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
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);
InspectorFrontendHost.loaded();
}
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() {
var methodName = arguments[0];
var parameters = Array.prototype.slice.call(arguments, 1);
// We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
// This is important to LayoutTests.
function delayDispatch()
{
WebInspector[methodName].apply(WebInspector, parameters);
WebInspector.pendingDispatches--;
}
WebInspector.pendingDispatches++;
setTimeout(delayDispatch, 0);
}
WebInspector.windowUnload = function(event)
{
InspectorFrontendHost.windowUnloading();
}
WebInspector.windowResize = function(event)
{
if (this.currentPanel && this.currentPanel.resize)
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)
{
InspectorFrontendHost.closeWindow();
}
WebInspector.documentClick = function(event)
{
var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
if (!anchor)
return;
// Prevent the link from navigating, since we don't do any navigation by following links normally.
event.preventDefault();
function followLink()
{
// FIXME: support webkit-html-external-link links here.
if (anchor.href in WebInspector.resourceURLMap) {
if (anchor.hasStyleClass("webkit-html-external-link")) {
anchor.removeStyleClass("webkit-html-external-link");
anchor.addStyleClass("webkit-html-resource-link");
}
WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
} else {
var profileString = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
if (profileString)
WebInspector.showProfileForURL(anchor.href);
}
}
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.documentKeyDown = function(event)
{
if (!this.currentFocusElement)
return;
if (this.currentFocusElement.handleKeyEvent)
this.currentFocusElement.handleKeyEvent(event);
else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
WebInspector[this.currentFocusElement.id + "KeyDown"](event);
if (!event.handled) {
var isMac = WebInspector.isMac();
switch (event.keyIdentifier) {
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) {
var searchField = document.getElementById("search");
searchField.focus();
searchField.select();
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;
}
}
}
WebInspector.documentKeyUp = function(event)
{
if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
return;
this.currentFocusElement.handleKeyUpEvent(event);
}
WebInspector.documentCanCopy = function(event)
{
if (!this.currentFocusElement)
return;
// Calling preventDefault() will say "we support copying, so enable the Copy menu".
if (this.currentFocusElement.handleCopyEvent)
event.preventDefault();
else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
event.preventDefault();
}
WebInspector.documentCopy = function(event)
{
if (!this.currentFocusElement)
return;
if (this.currentFocusElement.handleCopyEvent)
this.currentFocusElement.handleCopyEvent(event);
else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
WebInspector[this.currentFocusElement.id + "Copy"](event);
}
WebInspector.contextMenu = function(event)
{
if (event.handled || event.target.hasStyleClass("popup-glasspane"))
event.preventDefault();
}
WebInspector.mainKeyDown = function(event)
{
if (this.currentPanel && this.currentPanel.handleKeyEvent)
this.currentPanel.handleKeyEvent(event);
}
WebInspector.mainKeyUp = function(event)
{
if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
this.currentPanel.handleKeyUpEvent(event);
}
WebInspector.mainCopy = function(event)
{
if (this.currentPanel && this.currentPanel.handleCopyEvent)
this.currentPanel.handleCopyEvent(event);
}
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.toggleAttach = function()
{
this.attached = !this.attached;
this.drawer.resize();
}
WebInspector.toolbarDragStart = function(event)
{
if ((!WebInspector.attached && WebInspector.platform !== "mac-leopard") || 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.showConsole = function()
{
this.drawer.showView(this.console);
}
WebInspector.showChanges = function()
{
this.drawer.showView(this.changes);
}
WebInspector.showElementsPanel = function()
{
this.currentPanel = this.panels.elements;
}
WebInspector.showResourcesPanel = function()
{
this.currentPanel = this.panels.resources;
}
WebInspector.showScriptsPanel = function()
{
this.currentPanel = this.panels.scripts;
}
WebInspector.showTimelinePanel = function()
{
this.currentPanel = this.panels.timeline;
}
WebInspector.showProfilesPanel = function()
{
this.currentPanel = this.panels.profiles;
}
WebInspector.showStoragePanel = function()
{
this.currentPanel = this.panels.storage;
}
WebInspector.showConsolePanel = function()
{
this.currentPanel = this.panels.console;
}
WebInspector.addResource = function(identifier, payload)
{
var resource = new WebInspector.Resource(
payload.requestHeaders,
payload.requestURL,
payload.host,
payload.path,
payload.lastPathComponent,
identifier,
payload.isMainResource,
payload.cached,
payload.requestMethod,
payload.requestFormData);
this.resources[identifier] = resource;
this.resourceURLMap[resource.url] = resource;
if (resource.mainResource)
this.mainResource = resource;
if (this.panels.resources)
this.panels.resources.addResource(resource);
var match = payload.documentURL.match(/^(http[s]?|file):\/\/([\/]*[^\/]+)/i);
if (match)
this.addCookieDomain(match[1].toLowerCase() === "file" ? "" : match[2]);
}
WebInspector.clearConsoleMessages = function()
{
WebInspector.console.clearMessages(false);
}
WebInspector.selectDatabase = function(o)
{
WebInspector.showStoragePanel();
WebInspector.panels.storage.selectDatabase(o);
}
WebInspector.selectDOMStorage = function(o)
{
WebInspector.showStoragePanel();
WebInspector.panels.storage.selectDOMStorage(o);
}
WebInspector.updateResource = function(identifier, payload)
{
var resource = this.resources[identifier];
if (!resource)
return;
if (payload.didRequestChange) {
resource.url = payload.url;
resource.domain = payload.domain;
resource.path = payload.path;
resource.lastPathComponent = payload.lastPathComponent;
resource.requestHeaders = payload.requestHeaders;
resource.mainResource = payload.mainResource;
resource.requestMethod = payload.requestMethod;
resource.requestFormData = payload.requestFormData;
}
if (payload.didResponseChange) {
resource.mimeType = payload.mimeType;
resource.suggestedFilename = payload.suggestedFilename;
resource.expectedContentLength = payload.expectedContentLength;
resource.statusCode = payload.statusCode;
resource.suggestedFilename = payload.suggestedFilename;
resource.responseHeaders = payload.responseHeaders;
}
if (payload.didTypeChange) {
resource.type = payload.type;
}
if (payload.didLengthChange) {
resource.contentLength = payload.contentLength;
}
if (payload.didCompletionChange) {
resource.failed = payload.failed;
resource.finished = payload.finished;
}
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.
if (this.panels.resources)
this.panels.resources.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.
if (this.panels.resources)
this.panels.resources.mainResourceDOMContentTime = payload.domContentEventTime;
}
}
}
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.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)
{
if (!this.panels.storage)
return;
this.panels.storage.updateDOMStorage(storageId);
}
WebInspector.resourceTrackingWasEnabled = function()
{
this.panels.resources.resourceTrackingWasEnabled();
}
WebInspector.resourceTrackingWasDisabled = function()
{
this.panels.resources.resourceTrackingWasDisabled();
}
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)
{
this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
}
WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
{
this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
}
WebInspector.pausedScript = function(callFrames)
{
this.panels.scripts.debuggerPaused(callFrames);
}
WebInspector.resumedScript = function()
{
this.panels.scripts.debuggerResumed();
}
WebInspector.populateInterface = function()
{
for (var panelName in this.panels) {
var panel = this.panels[panelName];
if ("populateInterface" in panel)
panel.populateInterface();
}
}
WebInspector.reset = function()
{
for (var panelName in this.panels) {
var panel = this.panels[panelName];
if ("reset" in panel)
panel.reset();
}
for (var category in this.resourceCategories)
this.resourceCategories[category].removeAllResources();
this.resources = {};
this.resourceURLMap = {};
this.cookieDomains = {};
this.hoveredDOMNode = null;
delete this.mainResource;
this.console.clearMessages();
}
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);
}
WebInspector.addConsoleMessage = function(payload)
{
var consoleMessage = new WebInspector.ConsoleMessage(
payload.source,
payload.type,
payload.level,
payload.line,
payload.url,
payload.groupLevel,
payload.repeatCount);
consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1));
this.console.addMessage(consoleMessage);
}
WebInspector.updateConsoleMessageRepeatCount = function(count)
{
this.console.updateMessageRepeatCount(count);
}
WebInspector.log = function(message)
{
// remember 'this' for setInterval() callback
var self = this;
// return indication if we can actually log a message
function isLogAvailable()
{
return WebInspector.ConsoleMessage && WebInspector.ObjectProxy && 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.ObjectProxy(null, [], 0, message, false);
// post the message
var msg = new WebInspector.ConsoleMessage(
WebInspector.ConsoleMessage.MessageSource.Other,
WebInspector.ConsoleMessage.MessageType.Log,
WebInspector.ConsoleMessage.MessageLevel.Debug,
-1,
null,
null,
repeatCount,
message);
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);
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)
{
var node = WebInspector.domAgent.nodeForId(nodeId);
if (!node)
// FIXME: Should we deselect if null is passed in?
return;
this.currentPanel = this.panels.elements;
this.panels.elements.focusedDOMNode = node;
}
WebInspector.displayNameForURL = function(url)
{
if (!url)
return "";
var resource = this.resourceURLMap[url];
if (resource)
return resource.displayName;
return url.trimURL(WebInspector.mainResource ? 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.showResourceForURL = function(url, line, preferredPanel)
{
var resource = this.resourceForURL(url);
if (!resource)
return false;
if (preferredPanel && preferredPanel in WebInspector.panels) {
var panel = this.panels[preferredPanel];
if (!("showResource" in panel))
panel = null;
else if ("canShowResource" in panel && !panel.canShowResource(resource))
panel = null;
}
this.currentPanel = panel || this.panels.resources;
if (!this.currentPanel)
return false;
this.currentPanel.showResource(resource, 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.showProfilesPanel();
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;
a.title = tooltipText || url;
a.target = "_blank";
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.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.KeyCodes.Esc) {
event.preventDefault();
event.handled = true;
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 query = event.target.value;
var forceSearch = event.keyIdentifier === "Enter";
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 (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 && this.currentPanel.jumpToNextSearchResult)
this.currentPanel.jumpToNextSearchResult();
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 this.missingLocalizedStrings)) {
if (!WebInspector.InspectorBackendStub)
console.error("Localized string \"" + string + "\" not found.");
this.missingLocalizedStrings[string] = true;
}
if (Preferences.showMissingLocalizedStrings)
string += " (not localized)";
}
return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
}
WebInspector.isMac = function()
{
if (!("_isMac" in this))
this._isMac = WebInspector.platform.indexOf("mac-") === 0;
return this._isMac;
}
WebInspector.isBeingEdited = function(element)
{
return element.__editing;
}
WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
{
if (element.__editing)
return;
element.__editing = true;
var oldText = getContent(element);
var oldHandleKeyEvent = element.handleKeyEvent;
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;
this.removeStyleClass("editing");
this.tabIndex = oldTabIndex;
this.scrollTop = 0;
this.scrollLeft = 0;
this.handleKeyEvent = oldHandleKeyEvent;
element.removeEventListener("blur", blurEventListener, false);
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);
}
element.handleKeyEvent = function(event) {
if (oldHandleKeyEvent)
oldHandleKeyEvent(event);
if (event.handled)
return;
if (isEnterKey(event)) {
editingCommitted.call(element);
event.preventDefault();
event.stopPropagation();
event.handled = true;
} else if (event.keyCode === 27) { // Escape key
editingCancelled.call(element);
event.preventDefault();
event.handled = true;
} else if (event.keyIdentifier === "U+0009") // Tab key
moveDirection = (event.shiftKey ? "backward" : "forward");
}
element.addEventListener("blur", blurEventListener, false);
WebInspector.currentFocusElement = 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},
}
/* InspectorBackendStub.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.InspectorBackend) {
WebInspector.InspectorBackendStub = function()
{
this._searchingForNode = false;
this._attachedWindowHeight = 0;
this._debuggerEnabled = true;
this._profilerEnabled = true;
this._resourceTrackingEnabled = false;
this._timelineEnabled = false;
this._settings = {};
}
WebInspector.InspectorBackendStub.prototype = {
wrapCallback: function(func)
{
return func;
},
platform: function()
{
return "mac-leopard";
},
port: function()
{
return "unknown";
},
closeWindow: function()
{
this._windowVisible = false;
},
attach: function()
{
},
detach: function()
{
},
storeLastActivePanel: function(panel)
{
},
clearMessages: function()
{
},
searchingForNode: function()
{
return this._searchingForNode;
},
search: function(sourceRow, query)
{
},
toggleNodeSearch: function()
{
this._searchingForNode = !this._searchingForNode;
},
setAttachedWindowHeight: function(height)
{
},
moveByUnrestricted: function(x, y)
{
},
addResourceSourceToFrame: function(identifier, element)
{
},
addSourceToFrame: function(mimeType, source, element)
{
return false;
},
getResourceDocumentNode: function(identifier)
{
return undefined;
},
highlightDOMNode: function(node)
{
},
hideDOMNodeHighlight: function()
{
},
inspectedWindow: function()
{
return window;
},
loaded: function()
{
},
localizedStringsURL: function()
{
return undefined;
},
windowUnloading: function()
{
return false;
},
hiddenPanels: function()
{
return "";
},
debuggerEnabled: function()
{
return this._debuggerEnabled;
},
enableResourceTracking: function()
{
this._resourceTrackingEnabled = true;
WebInspector.resourceTrackingWasEnabled();
},
disableResourceTracking: function()
{
this._resourceTrackingEnabled = false;
WebInspector.resourceTrackingWasDisabled();
},
resourceTrackingEnabled: function()
{
return this._resourceTrackingEnabled;
},
enableDebugger: function()
{
this._debuggerEnabled = true;
},
disableDebugger: function()
{
this._debuggerEnabled = false;
},
addBreakpoint: function(sourceID, line, condition)
{
},
removeBreakpoint: function(sourceID, line)
{
},
updateBreakpoint: function(sourceID, line, condition)
{
},
pauseInDebugger: function()
{
},
pauseOnExceptions: function()
{
return false;
},
setPauseOnExceptions: function(value)
{
},
resumeDebugger: function()
{
},
profilerEnabled: function()
{
return true;
},
enableProfiler: function()
{
this._profilerEnabled = true;
},
disableProfiler: function()
{
this._profilerEnabled = false;
},
startProfiling: function()
{
},
stopProfiling: function()
{
},
getProfileHeaders: function(callId)
{
WebInspector.didGetProfileHeaders(callId, []);
},
getProfile: function(callId, uid)
{
if (WebInspector.__fullProfiles && (uid in WebInspector.__fullProfiles))
{
WebInspector.didGetProfile(callId, WebInspector.__fullProfiles[uid]);
}
},
takeHeapSnapshot: function()
{
},
databaseTableNames: function(database)
{
return [];
},
stepIntoStatementInDebugger: function()
{
},
stepOutOfFunctionInDebugger: function()
{
},
stepOverStatementInDebugger: function()
{
},
setSetting: function(setting, value)
{
this._settings[setting] = value;
},
dispatchOnInjectedScript: function()
{
},
releaseWrapperObjectGroup: function()
{
},
setting: function(setting)
{
return this._settings[setting];
}
}
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;
this._settings = {};
}
WebInspector.InspectorFrontendHostStub.prototype = {
platform: function()
{
return "mac-leopard";
},
port: function()
{
return "unknown";
},
closeWindow: function()
{
this._windowVisible = false;
},
attach: function()
{
},
detach: function()
{
},
search: function(sourceRow, query)
{
},
setAttachedWindowHeight: function(height)
{
},
moveWindowBy: function(x, y)
{
},
addResourceSourceToFrame: function(identifier, element)
{
},
addSourceToFrame: function(mimeType, source, element)
{
return false;
},
loaded: function()
{
},
localizedStringsURL: function()
{
return undefined;
},
hiddenPanels: function()
{
return "";
},
setSetting: function(setting, value)
{
this._settings[setting] = value;
},
setting: function(setting)
{
return this._settings[setting];
}
}
InspectorFrontendHost = new WebInspector.InspectorFrontendHostStub();
}
/* 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];
},
dispatchEventToListeners: function(eventType) {
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, 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;
}
}
/* 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.
};
WebInspector.KeyboardShortcut.KeyCodes = {
Esc: 27,
Space: 32,
PageUp: 33, // also NUM_NORTH_EAST
PageDown: 34, // also NUM_SOUTH_EAST
End: 35, // also NUM_SOUTH_WEST
Home: 36, // also NUM_NORTH_WEST
Left: 37, // also NUM_WEST
Up: 38, // also NUM_NORTH
Right: 39, // also NUM_EAST
Down: 40, // also NUM_SOUTH
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
Semicolon: 186, // ;
Comma: 188, // ,
Period: 190, // .
Slash: 191, // /
Apostrophe: 192, // `
SingleQuote: 222, // '
};
/**
* 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._makeKeyFromCodeAndModifiers = function(keyCode, modifiers)
{
return (keyCode & 255) | (modifiers << 8);
};
/* 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.completions = completions;
this.completionStopCharacters = stopCharacters;
this.history = [];
this.historyOffset = 0;
}
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();
},
handleKeyEvent: 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;
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();
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)
{
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));
},
_completionsReady: function(selection, auto, originalWordPrefixRange, 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;
if (completions.length === 1 || selection.isCollapsed || auto) {
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;
if (foundIndex === null || (foundIndex + 1) >= completions.length)
var completionText = completions[0];
else
var completionText = completions[foundIndex + 1];
}
var wordPrefixLength = originalWordPrefixRange.toString().length;
this._userEnteredRange = fullWordRange;
this._userEnteredText = fullWordRange.toString();
fullWordRange.deleteContents();
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;
},
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();
},
_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();
}
}
/* Popup.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.
*/
/**
* This class provides a popup that can be shown relative to an anchor element
* or at an arbitrary absolute position.
* Points are Objects: {x: xValue, y: yValue}.
* Rectangles are Objects: {x: xValue, y: yValue, width: widthValue, height: heightValue}.
*
* element is an optional unparented visible element (style.display != "none" AND style.visibility != "hidden").
* If the element is absent/undefined, it must have been set with the element(x) setter before the show() method invocation.
*/
WebInspector.Popup = function(element)
{
if (element)
this.element = element;
this._keyHandler = this._keyEventHandler.bind(this);
this._mouseDownHandler = this._mouseDownEventHandler.bind(this);
this._visible = false;
this._autoHide = true;
}
WebInspector.Popup.prototype = {
show: function()
{
if (this.visible)
return;
var ownerDocument = this._contentElement.ownerDocument;
if (!ownerDocument)
return;
this._glasspaneElement = ownerDocument.createElement("div");
this._glasspaneElement.className = "popup-glasspane";
ownerDocument.body.appendChild(this._glasspaneElement);
this._contentElement.positionAt(0, 0);
this._contentElement.removeStyleClass("hidden");
ownerDocument.body.appendChild(this._contentElement);
this.positionElement();
this._visible = true;
ownerDocument.addEventListener("keydown", this._keyHandler, false);
ownerDocument.addEventListener("mousedown", this._mouseDownHandler, false);
},
hide: function()
{
if (this.visible) {
this._visible = false;
this._contentElement.ownerDocument.removeEventListener("keydown", this._keyHandler, false);
this._contentElement.ownerDocument.removeEventListener("mousedown", this._mouseDownHandler, false);
this._glasspaneElement.parentElement.removeChild(this._glasspaneElement);
this._contentElement.parentElement.removeChild(this._contentElement);
}
},
get visible()
{
return this._visible;
},
set element(x)
{
this._checkNotVisible();
this._contentElement = x;
this._contentElement.addStyleClass("hidden");
},
get element()
{
return this._contentElement;
},
positionElement: function()
{
var element = this._contentElement;
var anchorElement = this._anchorElement;
var targetDocument = element.ownerDocument;
var targetDocumentBody = targetDocument.body;
var targetDocumentElement = targetDocument.documentElement;
var clippingBox = {x: 0, y: 0, width: targetDocumentElement.clientWidth, height: targetDocumentElement.clientHeight};
var parentElement = element.offsetParent || element.parentElement;
var anchorPosition = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop};
// FIXME(apavlov@chromium.org): Translate anchorPosition to the element.ownerDocument frame when https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
var anchorBox = {x: anchorPosition.x, y: anchorPosition.y, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight};
var elementBox = {x: element.totalOffsetLeft, y: element.totalOffsetTop, width: element.offsetWidth, height: element.offsetHeight};
var newElementPosition = {x: 0, y: 0};
if (anchorBox.y - elementBox.height >= clippingBox.y)
newElementPosition.y = anchorBox.y - elementBox.height;
else
newElementPosition.y = Math.min(anchorBox.y + anchorBox.height, Math.max(clippingBox.y, clippingBox.y + clippingBox.height - elementBox.height));
if (anchorBox.x + elementBox.height <= clippingBox.x + clippingBox.height)
newElementPosition.x = anchorBox.x;
else
newElementPosition.x = Math.max(clippingBox.x, clippingBox.x + clippingBox.height - elementBox.height);
element.positionAt(newElementPosition.x, newElementPosition.y);
},
set anchor(x)
{
this._checkNotVisible();
this._anchorElement = x;
},
get anchor()
{
return this._anchorElement;
},
set autoHide(x)
{
this._autoHide = x;
},
_checkNotVisible: function()
{
if (this.visible)
throw new Error("The popup must not be visible.");
},
_keyEventHandler: function(event)
{
// Escape hides the popup.
if (event.keyIdentifier == "U+001B") {
this.hide();
event.preventDefault();
event.handled = true;
}
},
_mouseDownEventHandler: function(event)
{
if (this._autoHide && event.originalTarget === this._glasspaneElement)
this.hide();
}
}
/* 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;
},
processCallback: function(callbackId, opt_vararg)
{
var args = Array.prototype.slice.call(arguments, 1);
var callback = this._callbacks[callbackId];
callback.apply(null, args);
delete this._callbacks[callbackId];
}
}
WebInspector.Callback._INSTANCE = new WebInspector.Callback();
WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE);
WebInspector.Callback.processCallback = WebInspector.Callback._INSTANCE.processCallback.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");
}
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);
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);
}
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}}
];
function animationFinished()
{
var mainStatusBar = document.getElementById("main-status-bar");
mainStatusBar.insertBefore(anchoredItems, mainStatusBar.firstChild);
mainStatusBar.style.removeProperty("padding-left");
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;
},
_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";
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.handleKeyEvent = this._promptKeyDown.bind(this);
this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");
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("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, "All");
createDividerElement.call(this);
this.errorElement = createFilterElement.call(this, "Errors");
this.warningElement = createFilterElement.call(this, "Warnings");
this.logElement = createFilterElement.call(this, "Logs");
this.filter(this.allElement, false);
this._shortcuts = {};
var shortcut;
var handler = this.clearMessages.bind(this, true);
shortcut = WebInspector.KeyboardShortcut.makeKey("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
this._shortcuts[shortcut] = handler;
this._shortcuts[shortcut].isMacOnly = true;
shortcut = WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
this._shortcuts[shortcut] = handler;
this._customFormatters = {
"object": this._formatobject,
"array": this._formatarray,
"node": this._formatnode
};
}
WebInspector.ConsoleView.prototype = {
_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.");
},
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) {
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.promptElement.scrollIntoView(false);
},
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 {
msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount);
msgCopy.totalRepeatCount = count;
msgCopy.setMessageBody(msg.args);
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;
}
},
clearMessages: function(clearInspectorController)
{
if (clearInspectorController)
InspectorBackend.clearMessages(false);
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);
if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused)
var callFrameId = WebInspector.panels.scripts.selectedCallFrameId();
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.clearMessages(true);
},
_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();
},
_promptKeyDown: function(event)
{
if (isEnterKey(event)) {
this._enterKeyPressed(event);
return;
}
if (isFnKey(event)) {
if (WebInspector.currentPanel && WebInspector.currentPanel.handleKeyEvent) {
WebInspector.currentPanel.handleKeyEvent(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;
}
}
this.prompt.handleKeyEvent(event);
},
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(result.value, result.isException);
};
InjectedScriptAccess.evaluate(expression, objectGroup, evalCallback);
},
_enterKeyPressed: function(event)
{
if (event.altKey)
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, exception)
{
self.prompt.history.push(str);
self.prompt.historyOffset = 0;
self.prompt.text = "";
self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
}
this.evalInInspectedWindow(str, "console", printResult);
},
_format: function(output, forceObjectFormat)
{
var isProxy = (output != null && typeof output === "object");
var type = (forceObjectFormat ? "object" : Object.proxyType(output));
var formatter = this._customFormatters[type];
if (!formatter || !isProxy) {
formatter = this._formatvalue;
output = output.description || output;
type = "undecorated";
}
var span = document.createElement("span");
span.addStyleClass("console-formatted-" + type);
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);
}
InjectedScriptAccess.pushNodeToFrontend(object, printNode);
},
_formatarray: function(arr, elem)
{
InjectedScriptAccess.getProperties(arr, false, this._printArray.bind(this, elem));
},
_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._format(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("]"));
}
}
WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount)
{
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;
if (arguments.length > 7)
this.setMessageBody(Array.prototype.slice.call(arguments, 7));
}
WebInspector.ConsoleMessage.prototype = {
setMessageBody: function(args)
{
this.args = args;
switch (this.type) {
case WebInspector.ConsoleMessage.MessageType.Trace:
var span = document.createElement("span");
span.addStyleClass("console-formatted-trace");
var stack = Array.prototype.slice.call(args);
var funcNames = stack.map(function(f) {
return f || WebInspector.UIString("(anonymous function)");
});
span.appendChild(document.createTextNode(funcNames.join("\n")));
this.formattedMessage = span;
break;
case WebInspector.ConsoleMessage.MessageType.Object:
this.formattedMessage = this._format(["%O", args[0]]);
break;
default:
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 contintually 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" && typeof parameters[i] !== "function")
parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]);
// Multiple parameters with the first being a format string. Save unused substitutions.
if (parameters.length > 1 && Object.proxyType(parameters[0]) === "string") {
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) {
this._formatIndividualValue(parameters[i], formattedResult);
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 formating, 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);
},
_formatIndividualValue: function(param, formattedResult)
{
if (Object.proxyType(param) === "string") {
if (this.originatingCommand && this.level === WebInspector.ConsoleMessage.MessageLevel.Log) {
var quotedString = "\"" + param.description.replace(/"/g, "\\\"") + "\"";
formattedResult.appendChild(WebInspector.linkifyStringAsFragment(quotedString));
} else
formattedResult.appendChild(WebInspector.linkifyStringAsFragment(param.description));
} else
formattedResult.appendChild(WebInspector.console._format(param));
},
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)
element.addStyleClass("console-group-title");
if (this.elementsTreeOutline) {
element.addStyleClass("outline-disclosure");
element.appendChild(this.elementsTreeOutline.element);
return element;
}
if (this.url && this.url !== "undefined") {
var urlElement = document.createElement("a");
urlElement.className = "console-message-url webkit-html-resource-link";
urlElement.href = this.url;
urlElement.lineNumber = this.line;
if (this.source === WebInspector.ConsoleMessage.MessageSource.JS)
urlElement.preferredPanel = "scripts";
if (this.line > 0)
urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line;
else
urlElement.textContent = WebInspector.displayNameForURL(this.url);
element.appendChild(urlElement);
}
var messageTextElement = document.createElement("span");
messageTextElement.className = "console-message-text";
if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: ")));
messageTextElement.appendChild(this.formattedMessage);
element.appendChild(messageTextElement);
if (this.repeatCount > 1)
this._updateRepeatCount();
return element;
},
_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:
typeString = "Log";
break;
case WebInspector.ConsoleMessage.MessageType.Object:
typeString = "Object";
break;
case WebInspector.ConsoleMessage.MessageType.Trace:
typeString = "Trace";
break;
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;
}
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,
EndGroup: 4,
Assert: 5
}
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";
commandTextElement.textContent = this.command;
element.appendChild(commandTextElement);
return element;
}
}
WebInspector.ConsoleTextMessage = function(text, level)
{
level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text);
}
WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
{
var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
var message = result;
var line = (exception ? result.line : -1);
var url = (exception ? result.sourceURL : null);
this.originatingCommand = originatingCommand;
WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message);
}
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) {
this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
element.addEventListener("click", this._titleClicked.bind(this), true);
} 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()
{
WebInspector.View.call(this);
this.element.addStyleClass("panel");
}
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;
if ("toolbarItemClass" in this)
this._toolbarItem.addStyleClass(this.toolbarItemClass);
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;
},
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 = document.getElementById("main-panels");
this.updateSidebarWidth();
},
hide: function()
{
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");
},
attach: function()
{
if (!this.element.parentNode)
document.getElementById("main-panels").appendChild(this.element);
},
searchCanceled: function(startingNewSearch)
{
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) {
currentView = this.visibleView;
this._currentSearchResultIndex = 0;
showFirstResult = true;
}
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);
if (showLastResult)
currentView.jumpToLastSearchResult();
else
currentView.jumpToPreviousSearchResult();
},
handleKeyEvent: function(event)
{
this.handleSidebarKeyEvent(event);
},
handleSidebarKeyEvent: function(event)
{
if (this.hasSidebar && this.sidebarTree)
this.sidebarTree.handleKeyEvent(event);
},
createSidebar: function(parentElement, resizerParentElement)
{
if (this.hasSidebar)
return;
if (!parentElement)
parentElement = this.element;
if (!resizerParentElement)
resizerParentElement = parentElement;
this.hasSidebar = true;
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);
},
_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);
},
updateSidebarWidth: function(width)
{
if (!this.hasSidebar)
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);
var visibleView = this.visibleView;
if (visibleView && "resize" in visibleView)
visibleView.resize();
},
setSidebarWidth: function(width)
{
this.sidebarElement.style.width = width + "px";
this.sidebarResizeElement.style.left = (width - 3) + "px";
},
updateMainViewWidth: function(width)
{
// Should be implemented by ancestors.
}
}
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.id = "resources-dividers";
this.element.appendChild(this._dividersElement);
this._eventDividersElement = document.createElement("div");
this._eventDividersElement.id = "resources-event-dividers";
this.element.appendChild(this._eventDividersElement);
this._dividersLabelBarElement = document.createElement("div");
this._dividersLabelBarElement.id = "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;
this._eventDividersElement.removeChildren();
// Reuse divider elements and labels.
var divider = this._dividersElement.firstChild;
var dividerLabelBar = this._dividersLabelBarElement.firstChild;
var clientWidth = this._dividersLabelBarElement.clientWidth - 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);
}
if (i === dividerCount)
divider.addStyleClass("last");
else
divider.removeStyleClass("last");
var left = paddingLeft + clientWidth * (i / dividerCount);
var percentLeft = 100 * left / this._dividersLabelBarElement.clientWidth + "%";
divider.style.left = percentLeft;
dividerLabelBar.style.left = percentLeft;
if (!isNaN(slice))
dividerLabelBar._labelElement.textContent = calculator.formatValue(slice * i);
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;
},
addEventDivider: function(divider)
{
this._eventDividersElement.appendChild(divider);
},
setScrollAndDividerTop: function(scrollTop, dividersTop)
{
this._dividersElement.style.top = scrollTop + "px";
this._eventDividersElement.style.top = scrollTop + "px";
this._dividersLabelBarElement.style.top = dividersTop + "px";
}
}
/* AbstractTimelinePanel.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.AbstractTimelinePanel = function()
{
WebInspector.Panel.call(this);
this._items = [];
this._staleItems = [];
}
WebInspector.AbstractTimelinePanel.prototype = {
get categories()
{
// Should be implemented by the concrete subclasses.
return {};
},
populateSidebar: function()
{
// Should be implemented by the concrete subclasses.
},
createItemTreeElement: function(item)
{
// Should be implemented by the concrete subclasses.
},
createItemGraph: function(item)
{
// Should be implemented by the concrete subclasses.
},
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("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;
},
updateGraphDividersIfNeeded: function(force)
{
if (!this.visible) {
this.needsRefresh = true;
return false;
}
return this._timelineGrid.updateDividers(force, this.calculator);
},
_updateDividersLabelBarPosition: function()
{
var scrollTop = this.containerElement.scrollTop;
var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.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();
},
show: function()
{
WebInspector.Panel.prototype.show.call(this);
this._updateDividersLabelBarPosition();
this.refreshIfNeeded();
},
resize: function()
{
this.updateGraphDividersIfNeeded();
},
updateMainViewWidth: function(width)
{
this._containerContentElement.style.left = width + "px";
this.updateGraphDividersIfNeeded();
},
invalidateAllItems: function()
{
this._staleItems = this._items.slice();
},
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;
}
for (var i = 0; i < staleItemsLength; ++i)
this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator);
this._staleItems = [];
this.updateGraphDividersIfNeeded();
},
reset: function()
{
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);
},
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);
}
}
WebInspector.AbstractTimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
WebInspector.AbstractTimelineCalculator = function()
{
}
WebInspector.AbstractTimelineCalculator.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.AbstractTimelineCategory = function(name, title, color)
{
this.name = name;
this.title = title;
this.color = color;
}
WebInspector.AbstractTimelineCategory.prototype = {
toString: function()
{
return this.title;
}
}
/* 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(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached, requestMethod, requestFormData)
{
this.identifier = identifier;
this.startTime = -1;
this.endTime = -1;
this.mainResource = mainResource;
this.requestHeaders = requestHeaders;
this.url = url;
this.domain = domain;
this.path = path;
this.lastPathComponent = lastPathComponent;
this.cached = cached;
this.requestMethod = requestMethod || "";
this.requestFormData = requestFormData || "";
this.category = WebInspector.resourceCategories.other;
}
WebInspector.Resource.StatusText = {
100: "Continue",
101: "Switching Protocols",
102: "Processing (WebDav)",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status (WebDav)",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Switch Proxy",
307: "Temporary",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Requested Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a teapot",
422: "Unprocessable Entity (WebDav)",
423: "Locked (WebDav)",
424: "Failed Dependency (WebDav)",
425: "Unordered Collection",
426: "Upgrade Required",
449: "Retry With",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage (WebDav)",
509: "Bandwidth Limit Exceeded",
510: "Not Extended"
};
// 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,
Other: 7,
isTextType: function(type)
{
return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
},
toString: function(type)
{
switch (type) {
case this.Document:
return WebInspector.UIString("document");
case this.Stylesheet:
return WebInspector.UIString("stylesheet");
case this.Image:
return WebInspector.UIString("image");
case this.Font:
return WebInspector.UIString("font");
case this.Script:
return WebInspector.UIString("script");
case this.XHR:
return WebInspector.UIString("XHR");
case this.Other:
default:
return WebInspector.UIString("other");
}
}
}
WebInspector.Resource.prototype = {
get url()
{
return this._url;
},
set url(x)
{
if (this._url === x)
return;
var oldURL = this._url;
this._url = x;
// 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 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 contentLength()
{
return this._contentLength || 0;
},
set contentLength(x)
{
if (this._contentLength === x)
return;
this._contentLength = x;
if (WebInspector.panels.resources)
WebInspector.panels.resources.refreshResource(this);
},
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._checkTips();
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 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.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;
},
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;
},
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;
},
get tips()
{
if (!("_tips" in this))
this._tips = {};
return this._tips;
},
_addTip: function(tip)
{
if (tip.id in this.tips)
return;
this.tips[tip.id] = tip;
// FIXME: Re-enable this code once we have a scope bar in the Console.
// Otherwise, we flood the Console with too many tips.
/*
var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Tip,
-1, this.url, null, 1, tip.message);
WebInspector.console.addMessage(msg);
*/
},
_checkTips: function()
{
for (var tip in WebInspector.Tips)
this._checkTip(WebInspector.Tips[tip]);
},
_checkTip: function(tip)
{
var addTip = false;
switch (tip.id) {
case WebInspector.Tips.ResourceNotCompressed.id:
addTip = this._shouldCompress();
break;
}
if (addTip)
this._addTip(tip);
},
_shouldCompress: function()
{
return WebInspector.Resource.Type.isTextType(this.type)
&& this.domain
&& !("Content-Encoding" in this.responseHeaders)
&& this.contentLength !== undefined
&& this.contentLength >= 512;
},
_mimeTypeIsConsistentWithType: function()
{
if (typeof this.type === "undefined"
|| this.type === WebInspector.Resource.Type.Other
|| this.type === WebInspector.Resource.Type.XHR)
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.toString(this.type), this.mimeType));
break;
}
if (msg)
WebInspector.console.addMessage(msg);
}
}
WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;
WebInspector.Resource.CompareByStartTime = function(a, b)
{
if (a.startTime < b.startTime)
return -1;
if (a.startTime > b.startTime)
return 1;
return 0;
}
WebInspector.Resource.CompareByResponseReceivedTime = function(a, b)
{
if (a.responseReceivedTime === -1 && b.responseReceivedTime !== -1)
return 1;
if (a.responseReceivedTime !== -1 && b.responseReceivedTime === -1)
return -1;
if (a.responseReceivedTime < b.responseReceivedTime)
return -1;
if (a.responseReceivedTime > b.responseReceivedTime)
return 1;
return 0;
}
WebInspector.Resource.CompareByEndTime = function(a, b)
{
if (a.endTime === -1 && b.endTime !== -1)
return 1;
if (a.endTime !== -1 && b.endTime === -1)
return -1;
if (a.endTime < b.endTime)
return -1;
if (a.endTime > b.endTime)
return 1;
return 0;
}
WebInspector.Resource.CompareByDuration = function(a, b)
{
if (a.duration < b.duration)
return -1;
if (a.duration > b.duration)
return 1;
return 0;
}
WebInspector.Resource.CompareByLatency = function(a, b)
{
if (a.latency < b.latency)
return -1;
if (a.latency > b.latency)
return 1;
return 0;
}
WebInspector.Resource.CompareBySize = function(a, b)
{
if (a.contentLength < b.contentLength)
return -1;
if (a.contentLength > b.contentLength)
return 1;
return 0;
}
WebInspector.Resource.StatusTextForCode = function(code)
{
return code ? code + " " + WebInspector.Resource.StatusText[code] : "";
}
/* 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)
{
WebInspector.AbstractTimelineCategory.call(this, name, title, color);
this.resources = [];
}
WebInspector.ResourceCategory.prototype = {
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 = [];
}
}
WebInspector.ResourceCategory.prototype.__proto__ = WebInspector.AbstractTimelineCategory.prototype;
/* 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.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());
}
var callId = WebInspector.Callback.wrap(sortingCallback);
InspectorBackend.getDatabaseTableNames(callId, this._id);
},
executeSql: function(query, onSuccess, onError)
{
function callback(result)
{
if (!(result instanceof Array)) {
onError(result);
return;
}
onSuccess(result);
}
InjectedScriptAccess.executeSql(this._id, query, callback);
}
}
WebInspector.didGetDatabaseTableNames = WebInspector.Callback.processCallback;
/* 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)
{
var callId = WebInspector.Callback.wrap(callback);
InspectorBackend.getDOMStorageEntries(callId, this._id);
},
setItem: function(key, value, callback)
{
var callId = WebInspector.Callback.wrap(callback);
InspectorBackend.setDOMStorageItem(callId, this._id, key, value);
},
removeItem: function(key, callback)
{
var callId = WebInspector.Callback.wrap(callback);
InspectorBackend.removeDOMStorageItem(callId, this._id, key);
}
}
WebInspector.didGetDOMStorageEntries = WebInspector.Callback.processCallback;
WebInspector.didSetDOMStorageItem = WebInspector.Callback.processCallback;
WebInspector.didRemoveDOMStorageItem = WebInspector.Callback.processCallback;
/* 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.updateWidths();
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[0].width = columns[0].title.length;
columns[1].title = WebInspector.UIString("Value");
columns[1].width = columns[1].title.length;
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;
if (key.length > columns[0].width)
columns[0].width = key.length;
var value = entries[i][1];
data[1] = value;
if (value.length > columns[1].width)
columns[1].width = value.length;
var node = new WebInspector.DataGridNode(data, false);
node.selectable = true;
nodes.push(node);
keys.push(key);
}
var totalColumnWidths = columns[0].width + columns[1].width;
var width = Math.round((columns[0].width * 100) / totalColumnWidths);
const minimumPrecent = 10;
if (width < minimumPrecent)
width = minimumPrecent;
if (width > 100 - minimumPrecent)
width = 100 - minimumPrecent;
columns[0].width = width;
columns[1].width = 100 - width;
columns[0].width += "%";
columns[1].width += "%";
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._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);
// 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");
var 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;
columnGroup.appendChild(col);
var cell = document.createElement("th");
cell.className = columnIdentifier + "-column";
cell.columnIdentifier = columnIdentifier;
var div = document.createElement("div");
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) {
cell.addStyleClass(column.aligned);
this.aligned[columnIdentifier] = column.aligned;
}
headerRow.appendChild(cell);
++columnCount;
}
columnGroup.span = 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 i = 0; i < columnCount; ++i) {
var cell = document.createElement("td");
fillerRow.appendChild(cell);
}
this._dataTableColumnGroup = columnGroup.cloneNode(true);
this._dataTable.appendChild(this._dataTableColumnGroup);
this.dataTableBody.appendChild(fillerRow);
this.columns = columns || {};
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;
},
// 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 left = 0;
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;
}
// 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.rightNeighboringColumnID = i + 1;
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;
resizer.style.left = left + "px";
}
},
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();
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 = [];
},
handleKeyEvent: function(event)
{
if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
return false;
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();
}
return handled;
},
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;
},
_keyDown: function(event)
{
this.handleKeyEvent(event);
},
_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.removeStyleClass("sort-ascending");
this._sortColumnCell.removeStyleClass("sort-descending");
}
if (cell == this._sortColumnCell) {
if (sortOrder == "ascending")
sortOrder = "descending";
else
sortOrder = "ascending";
}
this._sortColumnCell = cell;
cell.addStyleClass("sort-" + sortOrder);
this.dispatchEventToListeners("sorting changed");
},
_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();
},
_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.rightNeighboringColumnID - 1; i++)
leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].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.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].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;
event.preventDefault();
},
_endResizerDragging: function(event)
{
WebInspector.elementDragEnd(event);
this.currentResizer = null;
},
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");
for (var columnIdentifier in this.dataGrid.columns) {
var cell = this.createCell(columnIdentifier);
this._element.appendChild(cell);
}
return this._element;
},
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();
for (var columnIdentifier in this.dataGrid.columns) {
var cell = this.createCell(columnIdentifier);
this._element.appendChild(cell);
}
},
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(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._cookieDomain = cookieDomain;
}
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()
{
this.element.removeChildren();
var self = this;
function callback(cookies, isAdvanced) {
var dataGrid = (isAdvanced ? self.dataGridForCookies(cookies) : self.simpleDataGridForCookies(cookies));
if (dataGrid) {
self._dataGrid = dataGrid;
self.element.appendChild(dataGrid.element);
self._dataGrid.updateWidths();
if (isAdvanced)
self.deleteButton.visible = true;
} else {
var emptyMsgElement = document.createElement("div");
emptyMsgElement.className = "storage-table-empty";
emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies.");
self.element.appendChild(emptyMsgElement);
self._dataGrid = null;
self.deleteButton.visible = false;
}
}
WebInspector.Cookies.getCookiesAsync(callback, this._cookieDomain);
},
dataGridForCookies: function(cookies)
{
if (!cookies.length)
return null;
for (var i = 0; i < cookies.length; ++i)
cookies[i].expires = new Date(cookies[i].expires);
var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} };
columns[0].title = WebInspector.UIString("Name");
columns[0].width = columns[0].title.length;
columns[1].title = WebInspector.UIString("Value");
columns[1].width = columns[1].title.length;
columns[2].title = WebInspector.UIString("Domain");
columns[2].width = columns[2].title.length;
columns[3].title = WebInspector.UIString("Path");
columns[3].width = columns[3].title.length;
columns[4].title = WebInspector.UIString("Expires");
columns[4].width = columns[4].title.length;
columns[5].title = WebInspector.UIString("Size");
columns[5].width = columns[5].title.length;
columns[5].aligned = "right";
columns[6].title = WebInspector.UIString("HTTP");
columns[6].width = columns[6].title.length;
columns[6].aligned = "centered";
columns[7].title = WebInspector.UIString("Secure");
columns[7].width = columns[7].title.length;
columns[7].aligned = "centered";
function updateDataAndColumn(index, value) {
data[index] = value;
if (value.length > columns[index].width)
columns[index].width = value.length;
}
var data;
var nodes = [];
for (var i = 0; i < cookies.length; ++i) {
var cookie = cookies[i];
data = {};
updateDataAndColumn(0, cookie.name);
updateDataAndColumn(1, cookie.value);
updateDataAndColumn(2, cookie.domain);
updateDataAndColumn(3, cookie.path);
updateDataAndColumn(4, (cookie.session ? WebInspector.UIString("Session") : cookie.expires.toGMTString()));
updateDataAndColumn(5, Number.bytesToString(cookie.size, WebInspector.UIString));
updateDataAndColumn(6, (cookie.httpOnly ? "\u2713" : "")); // Checkmark
updateDataAndColumn(7, (cookie.secure ? "\u2713" : "")); // Checkmark
var node = new WebInspector.DataGridNode(data, false);
node.cookie = cookie;
node.selectable = true;
nodes.push(node);
}
var totalColumnWidths = 0;
for (var columnIdentifier in columns)
totalColumnWidths += columns[columnIdentifier].width;
// Enforce the Value column (the 2nd column) to be a max of 33%
// tweaking the raw total width because may massively outshadow the others
var valueColumnWidth = columns[1].width;
if (valueColumnWidth / totalColumnWidths > 0.33) {
totalColumnWidths -= valueColumnWidth;
totalColumnWidths *= 1.33;
columns[1].width = totalColumnWidths * 0.33;
}
// Calculate the percentage width for the columns.
const minimumPrecent = 6;
var recoupPercent = 0;
for (var columnIdentifier in columns) {
var width = columns[columnIdentifier].width;
width = Math.round((width / totalColumnWidths) * 100);
if (width < minimumPrecent) {
recoupPercent += (minimumPrecent - width);
width = minimumPrecent;
}
columns[columnIdentifier].width = width;
}
// Enforce the minimum percentage width. (need to narrow total percentage due to earlier additions)
while (recoupPercent > 0) {
for (var columnIdentifier in columns) {
if (columns[columnIdentifier].width > minimumPrecent) {
--columns[columnIdentifier].width;
--recoupPercent;
if (!recoupPercent)
break;
}
}
}
for (var columnIdentifier in columns)
columns[columnIdentifier].width += "%";
var dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCookieCallback.bind(this));
var length = nodes.length;
for (var i = 0; i < length; ++i)
dataGrid.appendChild(nodes[i]);
if (length > 0)
nodes[0].selected = true;
return dataGrid;
},
simpleDataGridForCookies: function(cookies)
{
if (!cookies.length)
return null;
var columns = {};
columns[0] = {};
columns[1] = {};
columns[0].title = WebInspector.UIString("Name");
columns[0].width = columns[0].title.length;
columns[1].title = WebInspector.UIString("Value");
columns[1].width = columns[1].title.length;
var nodes = [];
for (var i = 0; i < cookies.length; ++i) {
var cookie = cookies[i];
var data = {};
var name = cookie.name;
data[0] = name;
if (name.length > columns[0].width)
columns[0].width = name.length;
var value = cookie.value;
data[1] = value;
if (value.length > columns[1].width)
columns[1].width = value.length;
var node = new WebInspector.DataGridNode(data, false);
node.selectable = true;
nodes.push(node);
}
var totalColumnWidths = columns[0].width + columns[1].width;
var width = Math.round((columns[0].width * 100) / totalColumnWidths);
const minimumPrecent = 20;
if (width < minimumPrecent)
width = minimumPrecent;
if (width > 100 - minimumPrecent)
width = 100 - minimumPrecent;
columns[0].width = width;
columns[1].width = 100 - width;
columns[0].width += "%";
columns[1].width += "%";
var dataGrid = new WebInspector.DataGrid(columns);
var length = nodes.length;
for (var i = 0; i < length; ++i)
dataGrid.appendChild(nodes[i]);
if (length > 0)
nodes[0].selected = true;
return dataGrid;
},
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;
/* 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)
{
this.sourceID = sourceID;
this.sourceURL = sourceURL;
this.source = source;
this.startingLine = startingLine;
this.errorLine = errorLine;
this.errorMessage = errorMessage;
// 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 = WebInspector.UIString("(program): %s", match[1]);
}
}
WebInspector.Script.prototype = {
}
/* Breakpoint.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.Breakpoint = function(url, line, sourceID, condition)
{
this.url = url;
this.line = line;
this.sourceID = sourceID;
this._enabled = true;
this._sourceText = "";
this._condition = condition || "";
}
WebInspector.Breakpoint.prototype = {
get enabled()
{
return this._enabled;
},
set enabled(x)
{
if (this._enabled === x)
return;
this._enabled = x;
if (this._enabled)
this.dispatchEventToListeners("enabled");
else
this.dispatchEventToListeners("disabled");
},
get sourceText()
{
return this._sourceText;
},
set sourceText(text)
{
this._sourceText = text;
this.dispatchEventToListeners("text-changed");
},
get label()
{
var displayName = (this.url ? WebInspector.displayNameForURL(this.url) : WebInspector.UIString("(program)"));
return displayName + ":" + this.line;
},
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;
this.dispatchEventToListeners("condition-changed");
if (this.enabled)
InspectorBackend.updateBreakpoint(this.sourceID, this.line, c);
}
}
WebInspector.Breakpoint.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.addEventListener("click", this.toggleExpanded.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;
}
}
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;
}
WebInspector.ElementsTreeOutline.prototype = {
get rootDOMNode()
{
return this._rootDOMNode;
},
set rootDOMNode(x)
{
if (this._rootDOMNode === x)
return;
this._rootDOMNode = x;
this.update();
},
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();
if (x && !this.suppressSelectHighlight) {
InspectorBackend.highlightDOMNode(x.id);
if ("_restorePreviousHighlightNodeTimeout" in this)
clearTimeout(this._restorePreviousHighlightNodeTimeout);
function restoreHighlightToHoveredNode()
{
var hoveredNode = WebInspector.hoveredDOMNode;
if (hoveredNode)
InspectorBackend.highlightDOMNode(hoveredNode.id);
else
InspectorBackend.hideDOMNodeHighlight();
}
this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000);
}
}
},
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;
},
revealAndSelectNode: function(node)
{
if (!node)
return;
var treeElement = this.findTreeElement(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;
},
handleKeyEvent: function(event)
{
var selectedElement = this.selectedTreeElement;
if (!selectedElement)
return;
// Delete or backspace pressed, delete the node.
if (event.keyCode === 8 || event.keyCode === 46) {
selectedElement.remove();
event.preventDefault();
return;
}
// On Enter or Return start editing the first attribute
// or create a new attribute on the selected element.
if (event.keyIdentifier === "Enter") {
if (this._editing)
return;
selectedElement._startEditing();
// prevent a newline from being immediately inserted
event.preventDefault();
return;
}
TreeOutline.prototype.handleKeyEvent.call(this, event);
},
_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.elementCloseTag) {
element.hovered = true;
this._previousHoveredElement = element;
}
WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null);
},
_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.hoveredDOMNode = null;
}
}
WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
WebInspector.ElementsTreeElement = function(node)
{
var hasChildrenOverride = node.hasChildNodes() && !this._showInlineText(node);
// The title will be updated in onattach.
TreeElement.call(this, "", node, hasChildrenOverride);
if (this.representedObject.nodeType == Node.ELEMENT_NODE)
this._canAddAttributes = true;
}
WebInspector.ElementsTreeElement.prototype = {
get highlighted()
{
return this._highlighted;
},
set highlighted(x)
{
if (this._highlighted === x)
return;
this._highlighted = x;
if (this.listItemElement) {
if (x)
this.listItemElement.addStyleClass("highlighted");
else
this.listItemElement.removeStyleClass("highlighted");
}
},
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");
if (this._canAddAttributes)
this._pendingToggleNewAttribute = setTimeout(this.toggleNewAttributeButton.bind(this, true), 500);
} else {
this.listItemElement.removeStyleClass("hovered");
if (this._pendingToggleNewAttribute) {
clearTimeout(this._pendingToggleNewAttribute);
delete this._pendingToggleNewAttribute;
}
this.toggleNewAttributeButton(false);
}
}
},
createTooltipForImageNode: function(node, callback)
{
function createTooltipThenCallback(properties)
{
if (!properties) {
callback();
return;
}
var tooltipText = null;
if (properties.offsetHeight === properties.naturalHeight && properties.offsetWidth === properties.naturalWidth)
tooltipText = WebInspector.UIString("%d × %d pixels", properties.offsetWidth, properties.offsetHeight);
else
tooltipText = WebInspector.UIString("%d × %d pixels (Natural: %d × %d pixels)", properties.offsetWidth, properties.offsetHeight, properties.naturalWidth, properties.naturalHeight);
callback(tooltipText);
}
var objectProxy = new WebInspector.ObjectProxy(node.id);
WebInspector.ObjectProxy.getPropertiesAsync(objectProxy, ["naturalHeight", "naturalWidth", "offsetHeight", "offsetWidth"], createTooltipThenCallback);
},
toggleNewAttributeButton: function(visible)
{
function removeAddAttributeSpan()
{
if (this._addAttributeElement && this._addAttributeElement.parentNode)
this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
delete this._addAttributeElement;
this.updateSelection();
}
if (!this._addAttributeElement && visible && !this._editing) {
var span = document.createElement("span");
span.className = "add-attribute webkit-html-attribute-name";
span.textContent = " ?=\"\"";
span.addEventListener("dblclick", removeAddAttributeSpan.bind(this), false);
this._addAttributeElement = span;
var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
this._insertInLastAttributePosition(tag, span);
} else if (!visible && this._addAttributeElement)
removeAddAttributeSpan.call(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()
{
this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false);
if (this._highlighted)
this.listItemElement.addStyleClass("highlighted");
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))
return;
this.updateChildren();
},
updateChildren: function(fullRefresh)
{
WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh));
},
_updateChildren: function(fullRefresh)
{
if (fullRefresh) {
var selectedTreeElement = this.treeOutline.selectedTreeElement;
if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
this.select();
this.removeChildren();
}
var treeElement = this;
var treeChildIndex = 0;
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); i < treeElement.children.length; ++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.
var wasSelected = existingTreeElement.selected;
treeElement.removeChild(existingTreeElement);
treeElement.insertChild(existingTreeElement, treeChildIndex);
if (wasSelected)
existingTreeElement.select();
} else {
// No existing element found, insert a new element.
var newElement = new WebInspector.ElementsTreeElement(child);
newElement.selectable = treeOutline.selectEnabled;
treeElement.insertChild(newElement, treeChildIndex);
}
}
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) {
if ("elementCloseTag" in this.children[i])
continue;
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);
var lastChild = this.children[this.children.length - 1];
if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) {
var title = "<span class=\"webkit-html-tag close\">&lt;/" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
var item = new TreeElement(title, null, false);
item.selectable = false;
item.elementCloseTag = true;
this.appendChild(item);
}
},
onexpand: function()
{
this.treeOutline.updateSelection();
},
oncollapse: function()
{
this.treeOutline.updateSelection();
},
onreveal: function()
{
if (this.listItemElement)
this.listItemElement.scrollIntoViewIfNeeded(false);
},
onselect: function()
{
this.treeOutline.focusedDOMNode = this.representedObject;
this.updateSelection();
},
onmousedown: function(event)
{
if (this._editing)
return;
if (this.isEventWithinDisclosureTriangle(event))
return;
if (this.treeOutline.showInElementsPanelEnabled) {
WebInspector.showElementsPanel();
WebInspector.panels.elements.focusedDOMNode = this.representedObject;
}
// Prevent selecting the nearest word on double click.
if (event.detail >= 2)
event.preventDefault();
},
ondblclick: function(treeElement, event)
{
if (this._editing)
return;
if (this._startEditingFromEvent(event, treeElement))
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();
},
_startEditingFromEvent: function(event, treeElement)
{
if (this.treeOutline.focusedDOMNode != this.representedObject)
return;
if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
return false;
var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
if (textNode)
return this._startEditingTextNode(textNode);
var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
if (attribute)
return this._startEditingAttribute(attribute, event.target);
var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
if (newAttribute)
return this._addNewAttribute(treeElement.listItemElement);
return false;
},
_startEditing: function()
{
if (this.treeOutline.focusedDOMNode !== this.representedObject)
return;
var listItem = this._listItemNode;
if (this._canAddAttributes) {
this.toggleNewAttributeButton(false);
var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
if (attribute)
return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);
return this._addNewAttribute(listItem);
}
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(listItemElement)
{
var attr = document.createElement("span");
attr.className = "webkit-html-attribute";
attr.style.marginLeft = "2px"; // overrides the .editing margin rule
attr.style.marginRight = "2px"; // overrides the .editing margin rule
var name = document.createElement("span");
name.className = "webkit-html-attribute-name new-attribute";
name.textContent = " ";
var value = document.createElement("span");
value.className = "webkit-html-attribute-value";
attr.appendChild(name);
attr.appendChild(value);
var tag = 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(attributeElements[i].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 = true;
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 = true;
WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
return 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;
var newAttribute;
if (moveDirection) {
var found = false;
var attributes = this.representedObject.attributes;
for (var i = 0, len = attributes.length; i < len; ++i) {
if (attributes[i].name === attributeName) {
found = true;
if (moveDirection === "backward" && i > 0)
moveToAttribute = attributes[i - 1].name;
else if (moveDirection === "forward" && i < attributes.length - 1)
moveToAttribute = attributes[i + 1].name;
else if (moveDirection === "forward" && i === attributes.length - 1)
newAttribute = true;
}
}
if (!found && moveDirection === "backward" && attributes.length > 0)
moveToAttribute = attributes[attributes.length - 1].name;
else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText))
newAttribute = true;
}
function moveToNextAttributeIfNeeded() {
if (moveToAttribute)
this._triggerEditAttribute(moveToAttribute);
else if (newAttribute)
this._addNewAttribute(this.listItemElement);
}
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);
} 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);
},
_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;
// No need to call _updateTitle here, it will be called after the nodeValue is committed.
},
_editingCancelled: function(element, context)
{
delete this._editing;
// No need to call _updateTitle here, the editing code will revert to the original text.
},
_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 self = this;
function callback(tooltipText)
{
var title = self._nodeTitleInfo(self.representedObject, self.hasChildren, WebInspector.linkifyURL, tooltipText).title;
self.title = "<span class=\"highlight\">" + title + "</span>";
delete self.selectionElement;
self.updateSelection();
self._preventFollowingLinksOnDoubleClick();
};
// TODO: Replace with InjectedScriptAccess.getBasicProperties(obj, [names]).
if (this.representedObject.nodeName.toLowerCase() !== "img")
callback();
else
this.createTooltipForImageNode(this.representedObject, callback);
},
_nodeTitleInfo: function(node, hasChildren, linkify, tooltipText)
{
var info = {title: "", hasChildren: hasChildren};
switch (node.nodeType) {
case Node.DOCUMENT_NODE:
info.title = "Document";
break;
case Node.ELEMENT_NODE:
info.title = "<span class=\"webkit-html-tag\">&lt;" + node.nodeName.toLowerCase().escapeHTML();
if (node.hasAttributes()) {
for (var i = 0; i < node.attributes.length; ++i) {
var attr = node.attributes[i];
info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=&#8203;\"";
var value = attr.value;
if (linkify && (attr.name === "src" || attr.name === "href")) {
var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
info.title += linkify(attr.value, value, "webkit-html-attribute-value", node.nodeName.toLowerCase() == "a", tooltipText);
} else {
var value = value.escapeHTML();
value = value.replace(/([\/;:\)\]\}])/g, "$1&#8203;");
info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
}
info.title += "\"</span>";
}
}
info.title += "&gt;</span>&#8203;";
// 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
var textChild = onlyTextChild.call(node);
var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;
if (showInlineText) {
info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>&#8203;<span class=\"webkit-html-tag\">&lt;/" + node.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
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.JavaScriptSourceSyntaxHighlighter(null, null);
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.CSSSourceSyntaxHighlighter(null, null);
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;
default:
info.title = node.nodeName.toLowerCase().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);
}
var callId = WebInspector.Callback.wrap(removeNodeCallback);
InspectorBackend.removeNode(callId, this.representedObject.id);
}
}
WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;
WebInspector.didRemoveNode = WebInspector.Callback.processCallback;
/* 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;
/* PropertiesSection.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.PropertiesSection = function(title, subtitle)
{
this.element = document.createElement("div");
this.element.className = "section";
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.propertiesElement = document.createElement("ol");
this.propertiesElement.className = "properties";
this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
this.propertiesTreeOutline.section = this;
this.element.appendChild(this.headerElement);
this.element.appendChild(this.propertiesElement);
this.title = title;
this.subtitle = subtitle;
this._expanded = false;
}
WebInspector.PropertiesSection.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.innerHTML = x;
},
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;
}
}
/* ObjectProxy.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.ObjectProxy = function(objectId, path, protoDepth, description, hasChildren)
{
this.objectId = objectId;
this.path = path || [];
this.protoDepth = protoDepth || 0;
this.description = description;
this.hasChildren = hasChildren;
}
WebInspector.ObjectProxy.wrapPrimitiveValue = function(value)
{
var proxy = new WebInspector.ObjectProxy();
proxy.type = typeof value;
proxy.description = value;
return proxy;
}
WebInspector.ObjectProxy.getPropertiesAsync = function(objectProxy, propertiesToQueryFor, callback)
{
function createPropertiesMapThenCallback(propertiesPayload)
{
if (!propertiesPayload) {
callback();
return;
}
var result = [];
for (var i = 0; i < propertiesPayload.length; ++i)
if (propertiesToQueryFor.indexOf(propertiesPayload[i].name) !== -1)
result[propertiesPayload[i].name] = propertiesPayload[i].value.description;
callback(result);
};
InjectedScriptAccess.getProperties(objectProxy, true, createPropertiesMapThenCallback);
}
WebInspector.ObjectPropertyProxy = 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);
};
InjectedScriptAccess.getProperties(this.object, this.ignoreHasOwnProperty, 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)
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);
}
}
}
WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
{
var a = propertyA.name;
var b = propertyB.name;
// 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]));
}
};
InjectedScriptAccess.getProperties(this.property.value, false, callback.bind(this));
},
ondblclick: function(element, 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");
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.trimWhitespace();
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();
}
};
InjectedScriptAccess.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), 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()
{
WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints"));
this.breakpoints = {};
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 = {
addBreakpoint: function(breakpoint)
{
if (this.breakpoints[breakpoint.id])
return;
this.breakpoints[breakpoint.id] = breakpoint;
breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
breakpoint.addEventListener("text-changed", this._breakpointTextChanged, this);
this._appendBreakpointElement(breakpoint);
if (this.emptyElement.parentElement) {
this.bodyElement.removeChild(this.emptyElement);
this.bodyElement.appendChild(this.listElement);
}
if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID)
return;
if (breakpoint.enabled)
InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
},
_appendBreakpointElement: function(breakpoint)
{
function checkboxClicked(event)
{
breakpoint.enabled = !breakpoint.enabled;
// without this, we'd switch to the source of the clicked breakpoint
event.stopPropagation();
}
function breakpointClicked()
{
var script = WebInspector.panels.scripts.scriptOrResourceForID(breakpoint.sourceID);
if (script)
WebInspector.panels.scripts.showScript(script, breakpoint.line);
}
var breakpointElement = document.createElement("li");
breakpoint._breakpointListElement = breakpointElement;
breakpointElement._breakpointObject = breakpoint;
breakpointElement.addEventListener("click", breakpointClicked, false);
var checkboxElement = document.createElement("input");
checkboxElement.className = "checkbox-elem";
checkboxElement.type = "checkbox";
checkboxElement.checked = breakpoint.enabled;
checkboxElement.addEventListener("click", checkboxClicked, false);
breakpointElement.appendChild(checkboxElement);
var labelElement = document.createTextNode(breakpoint.label);
breakpointElement.appendChild(labelElement);
var sourceTextElement = document.createElement("div");
sourceTextElement.textContent = breakpoint.sourceText;
sourceTextElement.className = "source-text";
breakpointElement.appendChild(sourceTextElement);
var currentElement = this.listElement.firstChild;
while (currentElement) {
var currentBreak = currentElement._breakpointObject;
if (currentBreak.url > breakpoint.url) {
this.listElement.insertBefore(breakpointElement, currentElement);
return;
} else if (currentBreak.url == breakpoint.url && currentBreak.line > breakpoint.line) {
this.listElement.insertBefore(breakpointElement, currentElement);
return;
}
currentElement = currentElement.nextSibling;
}
this.listElement.appendChild(breakpointElement);
},
removeBreakpoint: function(breakpoint)
{
if (!this.breakpoints[breakpoint.id])
return;
delete this.breakpoints[breakpoint.id];
breakpoint.removeEventListener("enabled", null, this);
breakpoint.removeEventListener("disabled", null, this);
breakpoint.removeEventListener("text-changed", null, this);
var element = breakpoint._breakpointListElement;
element.parentElement.removeChild(element);
if (!this.listElement.firstChild) {
this.bodyElement.removeChild(this.listElement);
this.bodyElement.appendChild(this.emptyElement);
}
if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID)
return;
InspectorBackend.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
},
_breakpointEnableChanged: function(event)
{
var breakpoint = event.target;
var checkbox = breakpoint._breakpointListElement.firstChild;
checkbox.checked = breakpoint.enabled;
if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID)
return;
if (breakpoint.enabled)
InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
else
InspectorBackend.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
},
_breakpointTextChanged: function(event)
{
var breakpoint = event.target;
var sourceTextElement = breakpoint._breakpointListElement.firstChild.nextSibling.nextSibling;
sourceTextElement.textContent = breakpoint.sourceText;
}
}
WebInspector.BreakpointsSidebarPane.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"));
this._shortcuts = {};
var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Period,
WebInspector.KeyboardShortcut.Modifiers.Ctrl);
this._shortcuts[shortcut] = this._selectNextCallFrameOnStack.bind(this);
var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Comma,
WebInspector.KeyboardShortcut.Modifiers.Ctrl);
this._shortcuts[shortcut] = this._selectPreviousCallFrameOnStack.bind(this);
}
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];
subtitle = WebInspector.displayNameForURL(scriptOrResource.sourceURL || scriptOrResource.url);
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);
}
},
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");
},
handleKeyEvent: function(event)
{
var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
var handler = this._shortcuts[shortcut];
if (handler) {
handler(event);
event.preventDefault();
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;
}
}
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.ObjectPropertyProxy("this", 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(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"));
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.expanded = this.section.loadSavedExpressions().length > 0;
this.onexpand = this.refreshExpressions.bind(this);
}
WebInspector.WatchExpressionsSidebarPane.prototype = {
refreshExpressions: function()
{
this.section.update();
}
}
WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
WebInspector.WatchExpressionsSection = function()
{
this._watchObjectGroupId = "watch-group";
WebInspector.ObjectPropertiesSection.call(this);
this.watchExpressions = this.loadSavedExpressions();
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, exception)
{
if (exception) {
// Exception results are not wrappers, but text messages.
result = WebInspector.ObjectProxy.wrapPrimitiveValue(result);
} else if (result.type === "string") {
// Evaluation result is intentionally not abbreviated. However, we'd like to distinguish between null and "null"
result.description = "\"" + result.description + "\"";
}
var property = new WebInspector.ObjectPropertyProxy(expression, result);
property.watchIndex = watchIndex;
property.isException = exception;
// For newly added, empty expressions, set description to "",
// since otherwise you get DOMWindow
if (property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
property.value.description = "";
// 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();
}
}
}
InspectorBackend.releaseWrapperObjectGroup(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];
},
loadSavedExpressions: function()
{
var json = InspectorFrontendHost.setting("watchExpressions");
if (!json)
return [];
try {
json = JSON.parse(json);
} catch(e) {
return [];
}
return json.expressions || [];
},
saveExpressions: function()
{
var toSave = [];
for (var i = 0; i < this.watchExpressions.length; i++)
if (this.watchExpressions[i])
toSave.push(this.watchExpressions[i]);
var json = JSON.stringify({expressions: toSave});
InspectorFrontendHost.setSetting("watchExpressions", json);
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.isException)
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.trimWhitespace();
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.trimWhitespace();
if (!expression)
expression = null;
this.property.name = expression;
this.treeOutline.section.updateExpression(this, expression);
}
}
WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;
/* 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);
};
InjectedScriptAccess.getComputedStyle(node.id, callback);
var inlineStyleCallback = function(stylePayload) {
if (!stylePayload)
return;
self._inlineStyleId = stylePayload.id;
};
InjectedScriptAccess.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 (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();
};
InjectedScriptAccess.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 = new WebInspector.ObjectProxy(node.id, [], i);
var section = new WebInspector.ObjectPropertiesSection(prototype, prototypes[i], WebInspector.UIString("Prototype"));
self.sections.push(section);
body.appendChild(section.element);
}
};
InjectedScriptAccess.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";
if (Preferences.eventListenersFilter === "all")
option.selected = true;
option.label = WebInspector.UIString("All Nodes");
this.settingsSelectElement.appendChild(option);
option = document.createElement("option");
option.value = "selected";
if (Preferences.eventListenersFilter === "selected")
option.selected = true;
option.label = WebInspector.UIString("Selected Node Only");
this.settingsSelectElement.appendChild(option);
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 = {
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.listener.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];
Preferences.eventListenersFilter = selectedOption.value;
InspectorFrontendHost.setSetting("event-listeners-filter", Preferences.eventListenersFilter);
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 (Preferences.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);
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)
{
this.eventListener = eventListener;
WebInspector.ObjectPropertiesSection.call(this, null, this._getFunctionDisplayName(), this._getNodeDisplayName());
this.editable = false;
this.element.className = "event-bar"; /* Changed from "section" */
this.propertiesElement.className = "event-properties"; /* Changed from "properties" */
}
WebInspector.EventListenerBar.prototype = {
update: function()
{
var properties = [];
for (var propertyName in this.eventListener) {
// Just build properties in place - no need to reach out for injected script.
var value = this.eventListener[propertyName];
if (value instanceof WebInspector.DOMNode)
value = new WebInspector.ObjectProxy(value.id, [], 0, appropriateSelectorForNode(value), true);
else
value = WebInspector.ObjectProxy.wrapPrimitiveValue(value);
properties.push(new WebInspector.ObjectPropertyProxy(propertyName, value));
}
this.updateProperties(properties);
},
_getNodeDisplayName: function()
{
var node = this.eventListener.node;
if (!node)
return "";
if (node.nodeType === Node.DOCUMENT_NODE)
return "document";
return appropriateSelectorForNode(node);
},
_getFunctionDisplayName: function()
{
// Requires that Function.toString() return at least the function's signature.
var match = this.eventListener.listener.toString().match(/function ([^\(]+?)\(/);
return (match ? match[1] : 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"],
};
/* 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()
{
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);
if (Preferences.colorFormat === "hex")
option.selected = true;
option.label = WebInspector.UIString("Hex Colors");
this.settingsSelectElement.appendChild(option);
option = document.createElement("option");
option.value = "rgb";
option.action = this._changeColorFormat.bind(this);
if (Preferences.colorFormat === "rgb")
option.selected = true;
option.label = WebInspector.UIString("RGB Colors");
this.settingsSelectElement.appendChild(option);
option = document.createElement("option");
option.value = "hsl";
option.action = this._changeColorFormat.bind(this);
if (Preferences.colorFormat === "hsl")
option.selected = true;
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);
this.titleElement.appendChild(this.settingsSelectElement);
}
WebInspector.StylesSidebarPane.prototype = {
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;
var body = this.bodyElement;
if (!node) {
body.removeChildren();
this.sections = [];
return;
}
var self = this;
function callback(styles)
{
if (!styles)
return;
node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules);
self._update(refresh, body, node, editedSection, forceUpdate);
}
InjectedScriptAccess.getStyles(node.id, !Preferences.showUserAgentStyles, callback);
},
_update: function(refresh, body, node, editedSection, forceUpdate)
{
if (!refresh) {
body.removeChildren();
this.sections = [];
}
var styleRules = [];
if (refresh) {
for (var i = 0; i < this.sections.length; ++i) {
var section = this.sections[i];
if (section instanceof WebInspector.BlankStylePropertiesSection)
continue;
if (section.computedStyle)
section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node);
var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule };
styleRules.push(styleRule);
}
} else {
var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false });
var nodeName = node.nodeName.toLowerCase();
for (var i = 0; i < node.attributes.length; ++i) {
var attr = node.attributes[i];
if (attr.style) {
var attrStyle = { style: attr.style, editable: false };
attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name);
attrStyle.selectorText = nodeName + "[" + attr.name;
if (attr.value.length)
attrStyle.selectorText += "=" + attr.value;
attrStyle.selectorText += "]";
styleRules.push(attrStyle);
}
}
// Always Show element's Style Attributes
if (node.nodeType === Node.ELEMENT_NODE) {
var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true };
inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style");
styleRules.push(inlineStyle);
}
var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles);
if (matchedStyleRules) {
// Add rules in reverse order to match the cascade order.
for (var i = (matchedStyleRules.length - 1); i >= 0; --i) {
var rule = matchedStyleRules[i];
styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule });
}
}
}
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 usedProperties = {};
var disabledComputedProperties = {};
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)
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)
continue;
var style = styleRules[i].style;
var uniqueProperties = style.uniqueStyleProperties;
for (var j = 0; j < uniqueProperties.length; ++j) {
var name = uniqueProperties[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];
}
}
}
if (refresh) {
// 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 = (styleRule.usedProperties || usedProperties);
section.update((section === editedSection) || styleRule.computedStyle);
}
} else {
// Make a property section for each style rule.
for (var i = 0; i < styleRules.length; ++i) {
var styleRule = styleRules[i];
var subtitle = styleRule.subtitle;
delete styleRule.subtitle;
var computedStyle = styleRule.computedStyle;
delete styleRule.computedStyle;
var ruleUsedProperties = styleRule.usedProperties;
delete styleRule.usedProperties;
var editable = styleRule.editable;
delete styleRule.editable;
var isAttribute = styleRule.isAttribute;
delete styleRule.isAttribute;
// Default editable to true if it was omitted.
if (typeof editable === "undefined")
editable = true;
var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable);
if (computedStyle)
section.disabledComputedProperties = disabledComputedProperties;
section.pane = this;
if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState)
section.expanded = Preferences.styleRulesExpandedState[section.identifier];
else if (computedStyle)
section.collapse(true);
else if (isAttribute && styleRule.style.length === 0)
section.collapse(true);
else
section.expand(true);
body.appendChild(section.element);
this.sections.push(section);
}
}
},
_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 === Preferences.colorFormat) {
selectedIndex = i;
break;
}
}
this.settingsSelectElement.selectedIndex = selectedIndex;
},
_changeColorFormat: function(event)
{
var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
Preferences.colorFormat = selectedOption.value;
InspectorFrontendHost.setSetting("color-format", Preferences.colorFormat);
for (var i = 0; i < this.sections.length; ++i)
this.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[1];
this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
this.sections.splice(2, 0, blankSection);
return blankSection;
},
removeSection: function(section)
{
var index = this.sections.indexOf(section);
if (index === -1)
return;
this.sections.splice(index, 1);
if (section.element.parentNode)
section.element.parentNode.removeChild(section.element);
}
}
WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable)
{
WebInspector.PropertiesSection.call(this, styleRule.selectorText);
this.titleElement.addEventListener("click", this._clickSelector.bind(this), false);
this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false);
this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false);
this.styleRule = styleRule;
this.rule = this.styleRule.rule;
this.computedStyle = computedStyle;
this.editable = (editable && !computedStyle);
// 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 = usedProperties;
if (computedStyle) {
this.element.addStyleClass("computed-style");
if (Preferences.showInheritedComputedStyleProperties)
this.element.addStyleClass("show-inherited");
var showInheritedLabel = document.createElement("label");
var showInheritedInput = document.createElement("input");
showInheritedInput.type = "checkbox";
showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
var computedStyleSection = this;
var showInheritedToggleFunction = function(event) {
Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
if (Preferences.showInheritedComputedStyleProperties)
computedStyleSection.element.addStyleClass("show-inherited");
else
computedStyleSection.element.removeStyleClass("show-inherited");
event.stopPropagation();
};
showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
showInheritedLabel.appendChild(showInheritedInput);
showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited")));
this.subtitleElement.appendChild(showInheritedLabel);
} else {
if (!subtitle) {
if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) {
var url = this.styleRule.parentStyleSheet.href;
subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url));
this.subtitleElement.addStyleClass("file");
} 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
subtitle = WebInspector.UIString("inline stylesheet");
}
this.subtitle = subtitle;
}
this.identifier = styleRule.selectorText;
if (this.subtitle)
this.identifier += ":" + this.subtitleElement.textContent;
}
WebInspector.StylePropertiesSection.prototype = {
get usedProperties()
{
return this._usedProperties || {};
},
set usedProperties(x)
{
this._usedProperties = x;
this.update();
},
expand: function(dontRememberState)
{
WebInspector.PropertiesSection.prototype.expand.call(this);
if (dontRememberState)
return;
if (!Preferences.styleRulesExpandedState)
Preferences.styleRulesExpandedState = {};
Preferences.styleRulesExpandedState[this.identifier] = true;
},
collapse: function(dontRememberState)
{
WebInspector.PropertiesSection.prototype.collapse.call(this);
if (dontRememberState)
return;
if (!Preferences.styleRulesExpandedState)
Preferences.styleRulesExpandedState = {};
Preferences.styleRulesExpandedState[this.identifier] = false;
},
isPropertyInherited: function(property)
{
if (!this.computedStyle || !this._usedProperties || this.noAffect)
return false;
// These properties should always show for Computed Style.
var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties);
},
isPropertyOverloaded: function(property, shorthand)
{
if (this.computedStyle || !this._usedProperties || this.noAffect)
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;
},
isInspectorStylesheet: function()
{
return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet);
},
update: function(full)
{
if (full || this.computedStyle) {
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);
}
}
if (this._afterUpdate) {
this._afterUpdate(this);
delete this._afterUpdate;
}
},
onpopulate: function()
{
var style = this.styleRule.style;
var foundShorthands = {};
var uniqueProperties = style.uniqueStyleProperties;
var disabledProperties = style.__disabledPropertyValues || {};
for (var name in disabledProperties)
uniqueProperties.push(name);
uniqueProperties.sort();
for (var i = 0; i < uniqueProperties.length; ++i) {
var name = uniqueProperties[i];
var disabled = name in disabledProperties;
if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties)
disabled = true;
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;
},
_clickSelector: function(event)
{
event.stopPropagation();
// Don't search "Computed Styles", "Style Attribute", or Mapped Attributes.
if (this.computedStyle || !this.rule || this.rule.isUser)
return;
var searchElement = document.getElementById("search");
searchElement.value = this.styleRule.selectorText;
WebInspector.performSearch({ target: searchElement });
},
_dblclickEmptySpace: function(event)
{
this.expand();
this.addNewBlankProperty().startEditing();
},
_dblclickSelector: function(event)
{
if (!this.editable)
return;
if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
this.expand();
this.addNewBlankProperty().startEditing();
return;
}
if (!this.rule)
return;
this.startEditingSelector();
event.stopPropagation();
},
startEditingSelector: function()
{
var element = this.titleElement;
if (WebInspector.isBeingEdited(element))
return;
WebInspector.startEditing(this.titleElement, 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 callback(result)
{
if (!result) {
// Invalid Syntax for a Selector
moveToNextIfNeeded.call(self);
return;
}
var newRulePayload = result[0];
var doesAffectSelectedNode = result[1];
if (!doesAffectSelectedNode) {
self.noAffect = true;
self.element.addStyleClass("no-affect");
} else {
delete self.noAffect;
self.element.removeStyleClass("no-affect");
}
var newRule = WebInspector.CSSStyleDeclaration.parseRule(newRulePayload);
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);
}
InjectedScriptAccess.applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback);
},
editingSelectorCancelled: function()
{
// Do nothing, this is overridden by BlankStylePropertiesSection.
}
}
WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
WebInspector.BlankStylePropertiesSection = function(defaultSelectorText)
{
WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: 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 callback(result)
{
if (!result) {
// Invalid Syntax for a Selector
self.editingSelectorCancelled();
return;
}
var rule = result[0];
var doesSelectorAffectSelectedNode = result[1];
var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule);
styleRule.rule = rule;
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();
}
InjectedScriptAccess.addStyleSelector(newContent, this.pane.node.id, callback);
},
editingSelectorCancelled: function()
{
this.pane.removeSection(this);
},
makeNormal: function(styleRule)
{
this.element.removeStyleClass("blank-section");
this.styleRule = styleRule;
this.rule = styleRule.rule;
this.computedStyle = false;
this.editable = true;
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 = "name";
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 (Preferences.colorFormat === "rgb")
format = (color.simple ? "rgb" : "rgba");
else if (Preferences.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();
// 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 (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(newPayload)
{
if (!newPayload)
return;
self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
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);
}
InjectedScriptAccess.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(element, event)
{
this.startEditing(event.target);
event.stopPropagation();
},
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 };
// Lie about our children to prevent expanding on double click and to collapse shorthands.
this.hasChildren = false;
if (!selectElement)
selectElement = this.listItemElement;
this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this);
WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
},
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 {
// FIXME: this should cycle through known keywords for the current property name.
return;
}
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();
event.handled = true;
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.
InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText);
}
this.applyStyleText(this.listItemElement.textContent);
},
editingEnded: function(context)
{
this.hasChildren = context.hasChildren;
if (context.expanded)
this.expand();
delete this.listItemElement.handleKeyEvent;
delete this.originalCSSText;
},
editingCancelled: function(element, context)
{
if (this._newProperty)
this.treeOutline.removeChild(this);
else if (this.originalCSSText) {
InjectedScriptAccess.setStyleText(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;
var item = section.addNewBlankProperty();
item.startEditing();
return;
}
if (moveToSelector)
section.startEditingSelector();
}
},
applyStyleText: function(styleText, updateInterface)
{
var section = this.treeOutline.section;
var elementsPanel = WebInspector.panels.elements;
var styleTextLength = styleText.trimWhitespace().length;
if (!styleTextLength && updateInterface) {
if (this._newProperty) {
// The user deleted everything, so remove the tree element and update.
this.parent.removeChild(this);
return;
} else {
delete section._afterUpdate;
}
}
var self = this;
function callback(result)
{
if (!result) {
// 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();
return;
}
var newPayload = result[0];
var changedProperties = result[1];
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 = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
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);
if (!section.rule)
WebInspector.panels.elements.treeOutline.update();
}
InjectedScriptAccess.applyStyleText(this.style.id, styleText.trimWhitespace(), this.name, callback);
}
}
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);
window.addEventListener("resize", this._windowResized.bind(this), true);
}
WebInspector.PanelEnablerView.prototype = {
_enableButtonCicked: function()
{
this.dispatchEventToListeners("enable clicked");
},
_windowResized: 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;
/* 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)
{
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.title = title;
this.disabled = false;
this._toggled = 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 toggled()
{
return this._toggled;
},
set toggled(x)
{
if (this._toggled === x)
return;
if (x)
this.element.addStyleClass("toggled-on");
else
this.element.removeStyleClass("toggled-on");
this._toggled = 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.quadraticCurveTo(x, y + h, x + r, y + h);
ctx.lineTo(x + w - r, y + h);
ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
ctx.lineTo(x + w, y + r);
ctx.quadraticCurveTo(x + w, y, x + w - r, y);
ctx.lineTo(x + r, y);
ctx.quadraticCurveTo(x, y, x, y + r);
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);
this.element.addStyleClass("elements");
this.contentElement = document.createElement("div");
this.contentElement.id = "elements-content";
this.contentElement.className = "outline-disclosure";
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 = document.getElementById("main-panels");
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 (InspectorBackend.searchingForNode()) {
InspectorBackend.toggleNodeSearch();
this.panel.nodeSearchButton.toggled = false;
}
if (this._focusedDOMNode)
InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, function() {});
};
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.styles = new WebInspector.StylesSidebarPane();
this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
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";
this.sidebarElement.appendChild(this.sidebarPanes.styles.element);
this.sidebarElement.appendChild(this.sidebarPanes.metrics.element);
this.sidebarElement.appendChild(this.sidebarPanes.properties.element);
this.sidebarElement.appendChild(this.sidebarPanes.eventListeners.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._nodeSearchButtonClicked.bind(this), false);
this.searchingForNode = false;
this.element.appendChild(this.contentElement);
this.element.appendChild(this.sidebarElement);
this.element.appendChild(this.sidebarResizeElement);
this._changedStyles = {};
this.reset();
}
WebInspector.ElementsPanel.prototype = {
toolbarItemClass: "elements",
get toolbarItemLabel()
{
return WebInspector.UIString("Elements");
},
get statusBarItems()
{
return [this.nodeSearchButton.element, this.crumbsElement];
},
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.hoveredDOMNode = null;
if (InspectorBackend.searchingForNode()) {
InspectorBackend.toggleNodeSearch();
this.nodeSearchButton.toggled = false;
}
},
resize: function()
{
this.treeOutline.updateSelection();
this.updateBreadcrumbSizes();
},
reset: function()
{
if (this.focusedDOMNode) {
this._selectedPathOnReset = [];
var node = this.focusedDOMNode;
while ("index" in node) {
this._selectedPathOnReset.push(node.nodeName);
this._selectedPathOnReset.push(node.index);
node = node.parentNode;
}
this._selectedPathOnReset.reverse();
}
this.rootDOMNode = null;
this.focusedDOMNode = null;
WebInspector.hoveredDOMNode = null;
if (InspectorBackend.searchingForNode()) {
InspectorBackend.toggleNodeSearch();
this.nodeSearchButton.toggled = false;
}
this.recentlyModifiedNodes = [];
delete this.currentQuery;
this.searchCanceled();
},
setDocument: function(inspectedRootDocument)
{
this.reset();
if (!inspectedRootDocument)
return;
inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInserted.bind(this));
inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this));
this.treeOutline.suppressSelectHighlight = true;
this.rootDOMNode = inspectedRootDocument;
this.treeOutline.suppressSelectHighlight = false;
function selectDefaultNode()
{
this.treeOutline.suppressSelectHighlight = true;
var candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
if (candidateFocusNode) {
this.focusedDOMNode = candidateFocusNode;
if (this.treeOutline.selectedTreeElement)
this.treeOutline.selectedTreeElement.expand();
}
}
function selectLastSelectedNode(nodeId)
{
var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : 0;
if (!node) {
selectDefaultNode.call(this);
return;
}
this.treeOutline.suppressSelectHighlight = true;
this.focusedDOMNode = node;
this.treeOutline.suppressSelectHighlight = false;
}
if (this._selectedPathOnReset)
InjectedScriptAccess.nodeByPath(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
else
selectDefaultNode.call(this);
delete this._selectedPathOnReset;
},
searchCanceled: function()
{
if (this._searchResults) {
for (var i = 0; i < this._searchResults.length; ++i) {
var treeElement = this.treeOutline.findTreeElement(this._searchResults[i]);
if (treeElement)
treeElement.highlighted = false;
}
}
WebInspector.updateSearchMatchesCount(0, this);
this._currentSearchResultIndex = 0;
this._searchResults = [];
InjectedScriptAccess.searchCanceled(function() {});
},
performSearch: function(query)
{
// Call searchCanceled since it will reset everything we need before doing a new search.
this.searchCanceled();
const whitespaceTrimmedQuery = query.trimWhitespace();
if (!whitespaceTrimmedQuery.length)
return;
this._updatedMatchCountOnce = false;
this._matchesCountUpdateTimeout = null;
InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {});
},
_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)
return;
var nodeIdsArray = nodeIds.split(",");
for (var i = 0; i < nodeIdsArray.length; ++i) {
var nodeId = nodeIdsArray[i];
var node = WebInspector.domAgent.nodeForId(nodeId);
if (!node)
continue;
if (!this._searchResults.length) {
this._currentSearchResultIndex = 0;
// Only change the focusedDOMNode if the search was manually performed, because
// the search may have been performed programmatically and we wouldn't want to
// change the current focusedDOMNode.
if (WebInspector.currentFocusElement === document.getElementById("search"))
this.focusedDOMNode = node;
}
this._searchResults.push(node);
// Highlight the tree element to show it matched the search.
// FIXME: highlight the substrings in text nodes and attributes.
var treeElement = this.treeOutline.findTreeElement(node);
if (treeElement)
treeElement.highlighted = true;
}
this._updateMatchesCountSoon();
},
jumpToNextSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (++this._currentSearchResultIndex >= this._searchResults.length)
this._currentSearchResultIndex = 0;
this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
},
jumpToPreviousSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (--this._currentSearchResultIndex < 0)
this._currentSearchResultIndex = (this._searchResults.length - 1);
this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
},
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(new WebInspector.ConsoleTextMessage(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;
},
_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;
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.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null);
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.hoveredDOMNode = null;
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:
crumbTitle = current.nodeName.toLowerCase();
var nameElement = document.createElement("span");
nameElement.textContent = crumbTitle;
crumb.appendChild(nameElement);
var idAttribute = current.getAttribute("id");
if (idAttribute) {
var idElement = document.createElement("span");
crumb.appendChild(idElement);
var part = "#" + idAttribute;
crumbTitle += part;
idElement.appendChild(document.createTextNode(part));
// Mark the name as extra, since the ID is more important.
nameElement.className = "extra";
}
var classAttribute = current.getAttribute("class");
if (classAttribute) {
var classes = classAttribute.split(/\s+/);
var foundClasses = {};
if (classes.length) {
var classesElement = document.createElement("span");
classesElement.className = "extra";
crumb.appendChild(classesElement);
for (var i = 0; i < classes.length; ++i) {
var className = classes[i];
if (className && !(className in foundClasses)) {
var part = "." + className;
crumbTitle += part;
classesElement.appendChild(document.createTextNode(part));
foundClasses[className] = true;
}
}
}
}
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 = current.nodeName.toLowerCase();
}
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();
},
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;
if (!stylesSidebarPane.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;
},
handleKeyEvent: 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._nodeSearchButtonClicked(event);
event.preventDefault();
return;
}
}
this.treeOutline.handleKeyEvent(event);
},
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);
},
rightSidebarResizerDrag: function(event)
{
var x = event.pageX;
var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66);
this.sidebarElement.style.width = newWidth + "px";
this.contentElement.style.right = newWidth + "px";
this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
this.treeOutline.updateSelection();
event.preventDefault();
},
_nodeSearchButtonClicked: function(event)
{
InspectorBackend.toggleNodeSearch();
this.nodeSearchButton.toggled = InspectorBackend.searchingForNode();
}
}
WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.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.AbstractTimelinePanel.call(this);
this.element.addStyleClass("resources");
this._createPanelEnabler();
this.viewsContainerElement = document.createElement("div");
this.viewsContainerElement.id = "resource-views";
this.element.appendChild(this.viewsContainerElement);
this.createFilterPanel();
this.createInterface();
this._createStatusbarButtons();
this.reset();
this.filter(this.filterAllElement, false);
this.graphsTreeElement.children[0].select();
}
WebInspector.ResourcesPanel.prototype = {
toolbarItemClass: "resources",
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()));
},
populateSidebar: function()
{
var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
timeGraphItem.onselect = this._graphSelected.bind(this);
var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
timeGraphItem.sortingOptions = [
{ name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
{ name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
{ name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
{ name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
{ name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
];
timeGraphItem.selectedSortingOptionIndex = 1;
var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
sizeGraphItem.onselect = this._graphSelected.bind(this);
var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
sizeGraphItem.sortingOptions = [
{ name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
];
sizeGraphItem.selectedSortingOptionIndex = 0;
this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
this.sidebarTree.appendChild(this.graphsTreeElement);
this.graphsTreeElement.appendChild(timeGraphItem);
this.graphsTreeElement.appendChild(sizeGraphItem);
this.graphsTreeElement.expand();
this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
this.sidebarTree.appendChild(this.itemsTreeElement);
this.itemsTreeElement.expand();
},
_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");
this.largerResourcesButton.toggled = Preferences.resourcesLargeRows;
this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
if (!Preferences.resourcesLargeRows) {
Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
this._toggleLargerResources(); // this will toggle the preference back to the original
}
this.sortingSelectElement = document.createElement("select");
this.sortingSelectElement.className = "status-bar-item";
this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
},
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.AbstractTimelinePanel.prototype.show.call(this);
var visibleView = this.visibleView;
if (visibleView) {
visibleView.headersVisible = true;
visibleView.show(this.viewsContainerElement);
}
// 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;
}
},
resize: function()
{
WebInspector.AbstractTimelinePanel.prototype.resize.call(this);
var visibleView = this.visibleView;
if (visibleView && "resize" in visibleView)
visibleView.resize();
},
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)
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 null;
},
get sortingFunction()
{
return this._sortingFunction;
},
set sortingFunction(x)
{
this._sortingFunction = x;
this._sortResourcesIfNeeded();
},
refresh: function()
{
WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);
this._sortResourcesIfNeeded();
this._updateSummaryGraph();
},
_updateSummaryGraph: function()
{
this.summaryBar.update(this._resources);
},
resourceTrackingWasEnabled: function()
{
this.reset();
},
resourceTrackingWasDisabled: function()
{
this.reset();
},
reset: function()
{
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;
}
}
WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
this.mainResourceLoadTime = -1;
this.mainResourceDOMContentTime = -1;
this.viewsContainerElement.removeChildren();
this.summaryBar.reset();
if (InspectorBackend.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.prototype === resource._resourcesView.prototype)
return;
resource.warnings = 0;
resource.errors = 0;
if (!this.currentQuery && resource._itemsTreeElement)
resource._itemsTreeElement.updateErrorsAndWarnings();
var oldView = resource._resourcesView;
resource._resourcesView.detach();
delete resource._resourcesView;
resource._resourcesView = newView;
newView.headersVisible = oldView.headersVisible;
if (oldView.visible && oldView.element.parentNode)
newView.show(oldView.element.parentNode);
},
showResource: function(resource, line)
{
if (!resource)
return;
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) {
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 = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force);
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;
}
if (this.mainResourceLoadTime !== -1) {
var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime);
var loadDivider = document.createElement("div");
loadDivider.className = "resources-onload-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-ondomcontent-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;
this.sortingSelectElement.appendChild(option);
}
this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
this._changeSortingFunction();
this.closeVisibleResource();
this.containerElement.scrollTop = 0;
},
_toggleLargerResources: function()
{
if (!this.itemsTreeElement._childrenListNode)
return;
this.itemsTreeElement.smallChildren = !this.itemsTreeElement.smallChildren;
Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
InspectorFrontendHost.setSetting("resources-large-rows", Preferences.resourcesLargeRows);
if (this.itemsTreeElement.smallChildren) {
this.itemsGraphsElement.addStyleClass("small");
this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
this.largerResourcesButton.toggled = false;
this.adjustScrollPosition();
} else {
this.itemsGraphsElement.removeStyleClass("small");
this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
this.largerResourcesButton.toggled = true;
}
},
_changeSortingFunction: function()
{
var selectedOption = this.sortingSelectElement[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)
{
WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
this.viewsContainerElement.style.left = width + "px";
},
_enableResourceTracking: function()
{
if (InspectorBackend.resourceTrackingEnabled())
return;
this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
},
_toggleResourceTracking: function(optionalAlways)
{
if (InspectorBackend.resourceTrackingEnabled()) {
this.largerResourcesButton.visible = false;
this.sortingSelectElement.visible = false;
InspectorBackend.disableResourceTracking(true);
} else {
this.largerResourcesButton.visible = true;
this.sortingSelectElement.visible = true;
InspectorBackend.enableResourceTracking(!!optionalAlways);
}
},
get _resources()
{
return this.items;
}
}
WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;
WebInspector.ResourceTimeCalculator = function(startAtZero)
{
WebInspector.AbstractTimelineCalculator.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 leftLabel = "";
if (resource.latency > 0)
leftLabel = this.formatValue(resource.latency);
var rightLabel = "";
if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
if (leftLabel && rightLabel) {
var total = this.formatValue(resource.duration);
var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
} else if (leftLabel)
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.bind(WebInspector));
},
_lowerBound: function(resource)
{
return 0;
},
_upperBound: function(resource)
{
return 0;
}
}
WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
WebInspector.ResourceTransferTimeCalculator = function()
{
WebInspector.ResourceTimeCalculator.call(this, false);
}
WebInspector.ResourceTransferTimeCalculator.prototype = {
formatValue: function(value)
{
return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
},
_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.bind(WebInspector));
},
_upperBound: function(resource)
{
return resource.duration;
}
}
WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
WebInspector.ResourceTransferSizeCalculator = function()
{
WebInspector.AbstractTimelineCalculator.call(this);
}
WebInspector.ResourceTransferSizeCalculator.prototype = {
computeBarGraphLabels: function(resource)
{
const label = this.formatValue(this._value(resource));
var tooltip = label;
if (resource.cached)
tooltip = WebInspector.UIString("%s (from cache)", tooltip);
return {left: label, right: label, tooltip: tooltip};
},
_value: function(resource)
{
return resource.contentLength;
},
formatValue: function(value)
{
return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
}
}
WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.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(treeElement, event)
{
InjectedScriptAccess.openInInspectedWindow(this.resource.url, function() {});
},
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.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);
if (resource.cached)
this._graphElement.addStyleClass("resource-cached");
this._barAreaElement = document.createElement("div");
this._barAreaElement.className = "resources-graph-bar-area hidden";
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);
}
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 rightBarWidth = (this._barRightElement.offsetWidth - labelPadding);
const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding);
var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth);
var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth);
if (labelBefore) {
if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10))
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 ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10))
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)
{
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._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
this._barRightElement.style.setProperty("left", percentages.middle + "%");
this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
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;
}
}
/* 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);
this.element.addStyleClass("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.filesSelectElement.handleKeyEvent = this.handleKeyEvent.bind(this);
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.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.breakpoints = new WebInspector.BreakpointsSidebarPane();
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.breakpoints.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);
this.pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item");
this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
this._breakpointsURLMap = {};
this._shortcuts = {};
var handler, shortcut;
var platformSpecificModifier = WebInspector.isMac() ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
// Continue.
handler = this.pauseButton.click.bind(this.pauseButton);
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F8);
this._shortcuts[shortcut] = handler;
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Slash, platformSpecificModifier);
this._shortcuts[shortcut] = handler;
// Step over.
handler = this.stepOverButton.click.bind(this.stepOverButton);
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F10);
this._shortcuts[shortcut] = handler;
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.SingleQuote, platformSpecificModifier);
this._shortcuts[shortcut] = handler;
// Step into.
handler = this.stepIntoButton.click.bind(this.stepIntoButton);
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11);
this._shortcuts[shortcut] = handler;
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, platformSpecificModifier);
this._shortcuts[shortcut] = handler;
// Step out.
handler = this.stepOutButton.click.bind(this.stepOutButton);
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
this._shortcuts[shortcut] = handler;
shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
this._shortcuts[shortcut] = handler;
this.reset();
}
WebInspector.ScriptsPanel.prototype = {
toolbarItemClass: "scripts",
get toolbarItemLabel()
{
return WebInspector.UIString("Scripts");
},
get statusBarItems()
{
return [this.enableToggleButton.element, this.pauseOnExceptionButton.element];
},
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);
}
// 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 Resources panel then switched
// to the this panel.
for (var sourceID in this._sourceIDMap) {
var scriptOrResource = this._sourceIDMap[sourceID];
var view = this._sourceViewForScriptOrResource(scriptOrResource);
if (!view || view === this.visibleView)
continue;
view.visible = false;
}
if (this._attachDebuggerWhenShown) {
InspectorBackend.enableDebugger(false);
delete this._attachDebuggerWhenShown;
}
},
get searchableViews()
{
var views = [];
const visibleView = this.visibleView;
if (visibleView && visibleView.performSearch) {
visibleView.alreadySearching = true;
views.push(visibleView);
}
for (var sourceID in this._sourceIDMap) {
var scriptOrResource = this._sourceIDMap[sourceID];
var view = this._sourceViewForScriptOrResource(scriptOrResource);
if (!view || !view.performSearch || view.alreadySearching)
continue;
view.alreadySearching = true;
views.push(view);
}
for (var i = 0; i < views.length; ++i)
delete views[i].alreadySearching;
return views;
},
addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
{
var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage);
if (sourceURL in WebInspector.resourceURLMap) {
var resource = WebInspector.resourceURLMap[sourceURL];
resource.addScript(script);
}
sourceURL = script.sourceURL;
if (sourceID)
this._sourceIDMap[sourceID] = (resource || script);
if (sourceURL in this._breakpointsURLMap && sourceID) {
var breakpoints = this._breakpointsURLMap[sourceURL];
var breakpointsLength = breakpoints.length;
for (var i = 0; i < breakpointsLength; ++i) {
var breakpoint = breakpoints[i];
if (startingLine <= breakpoint.line) {
// remove and add the breakpoint, to clean up things like the sidebar
this.removeBreakpoint(breakpoint);
breakpoint.sourceID = sourceID;
this.addBreakpoint(breakpoint);
if (breakpoint.enabled)
InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
}
}
}
this._addScriptToFilesMenu(script);
},
scriptOrResourceForID: function(id)
{
return this._sourceIDMap[id];
},
addBreakpoint: function(breakpoint)
{
this.sidebarPanes.breakpoints.addBreakpoint(breakpoint);
var sourceFrame;
if (breakpoint.url) {
if (!(breakpoint.url in this._breakpointsURLMap))
this._breakpointsURLMap[breakpoint.url] = [];
this._breakpointsURLMap[breakpoint.url].unshift(breakpoint);
if (breakpoint.url in WebInspector.resourceURLMap) {
var resource = WebInspector.resourceURLMap[breakpoint.url];
sourceFrame = this._sourceFrameForScriptOrResource(resource);
}
}
if (breakpoint.sourceID && !sourceFrame) {
var object = this._sourceIDMap[breakpoint.sourceID]
sourceFrame = this._sourceFrameForScriptOrResource(object);
}
if (sourceFrame)
sourceFrame.addBreakpoint(breakpoint);
},
removeBreakpoint: function(breakpoint)
{
this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint);
var sourceFrame;
if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) {
var breakpoints = this._breakpointsURLMap[breakpoint.url];
breakpoints.remove(breakpoint);
if (!breakpoints.length)
delete this._breakpointsURLMap[breakpoint.url];
if (breakpoint.url in WebInspector.resourceURLMap) {
var resource = WebInspector.resourceURLMap[breakpoint.url];
sourceFrame = this._sourceFrameForScriptOrResource(resource);
}
}
if (breakpoint.sourceID && !sourceFrame) {
var object = this._sourceIDMap[breakpoint.sourceID]
sourceFrame = this._sourceFrameForScriptOrResource(object);
}
if (sourceFrame)
sourceFrame.removeBreakpoint(breakpoint);
},
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, exception)
{
callback(result, exception);
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(result.value, result.isException);
}
InjectedScriptAccess.evaluateInCallFrame(callFrame.id, code, objectGroup, evalCallback);
},
debuggerPaused: function(callFrames)
{
this._paused = true;
this._waitingToPause = false;
this._stepping = false;
this._updateDebuggerButtons();
this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap);
this.sidebarPanes.callstack.selectedCallFrame = callFrames[0];
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()
{
this.reset();
},
debuggerWasDisabled: function()
{
this.reset();
},
reset: function()
{
this.visibleView = null;
delete this.currentQuery;
this.searchCanceled();
if (!InspectorBackend.debuggerEnabled()) {
this._paused = false;
this._waitingToPause = false;
this._stepping = false;
}
this._clearInterface();
this._backForwardList = [];
this._currentBackForwardIndex = -1;
this._updateBackAndForwardButtons();
this._scriptsForURLsInFilesSelect = {};
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();
},
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);
},
canShowResource: function(resource)
{
return resource && resource.scripts.length && InspectorBackend.debuggerEnabled();
},
showScript: function(script, line)
{
this._showScriptOrResource(script, {line: line, shouldHighlightLine: true});
},
showResource: function(resource, line)
{
this._showScriptOrResource(resource, {line: line, shouldHighlightLine: true});
},
showView: function(view)
{
if (!view)
return;
this._showScriptOrResource((view.resource || view.script));
},
handleKeyEvent: function(event)
{
var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
var handler = this._shortcuts[shortcut];
if (handler) {
handler(event);
event.preventDefault();
event.handled = true;
} else {
this.sidebarPanes.callstack.handleKeyEvent(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;
},
_sourceViewForScriptOrResource: function(scriptOrResource)
{
if (scriptOrResource instanceof WebInspector.Resource) {
if (!WebInspector.panels.resources)
return null;
return WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
}
if (scriptOrResource instanceof WebInspector.Script)
return this.scriptViewForScript(scriptOrResource);
},
_sourceFrameForScriptOrResource: function(scriptOrResource)
{
if (scriptOrResource instanceof WebInspector.Resource) {
if (!WebInspector.panels.resources)
return null;
return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
}
if (scriptOrResource instanceof WebInspector.Script)
return this.sourceFrameForScript(scriptOrResource);
},
_showScriptOrResource: function(scriptOrResource, options)
{
// options = {line:, shouldHighlightLine:, fromBackForwardAction:, initialLoad:}
if (!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;
if (scriptOrResource.url in this._breakpointsURLMap) {
var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
if (sourceFrame && !sourceFrame.breakpoints.length) {
var breakpoints = this._breakpointsURLMap[scriptOrResource.url];
var breakpointsLength = breakpoints.length;
for (var i = 0; i < breakpointsLength; ++i)
sourceFrame.addBreakpoint(breakpoints[i]);
}
}
} else if (scriptOrResource instanceof WebInspector.Script)
view = this.scriptViewForScript(scriptOrResource);
if (!view)
return;
var url = scriptOrResource.url || scriptOrResource.sourceURL;
if (url && !options.initialLoad)
InspectorFrontendHost.setSetting("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, {force: true});
option = scriptOrResource.filesSelectOption;
}
console.assert(option);
} else {
var script = this._scriptsForURLsInFilesSelect[url];
if (script)
option = script.filesSelectOption;
}
if (option)
this.filesSelectElement.selectedIndex = option.index;
},
_addScriptToFilesMenu: function(script, options)
{
var force = options && options.force;
if (!script.sourceURL && !force)
return;
if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL])
return;
this._scriptsForURLsInFilesSelect[script.sourceURL] = script;
var select = this.filesSelectElement;
var option = document.createElement("option");
option.representedObject = (script.resource || script);
option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)"));
function optionCompare(a, b)
{
var aTitle = a.text.toLowerCase();
var bTitle = b.text.toLowerCase();
if (aTitle < bTitle)
return -1;
else if (aTitle > bTitle)
return 1;
var aSourceID = a.representedObject.sourceID;
var bSourceID = b.representedObject.sourceID;
if (aSourceID < bSourceID)
return -1;
else if (aSourceID > bSourceID)
return 1;
return 0;
}
var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
if (insertionIndex < 0)
select.appendChild(option);
else
select.insertBefore(option, select.childNodes.item(insertionIndex));
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 = InspectorFrontendHost.setting("LastViewedScriptFile");
if (url && url === lastURL)
this._showScriptOrResource(option.representedObject, {initialLoad: true});
}
},
_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;
},
_sidebarResizeDrag: function(event)
{
var x = event.pageX + this._dragOffset;
var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
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";
event.preventDefault();
},
_updatePauseOnExceptionsButton: function()
{
if (InspectorBackend.pauseOnExceptions()) {
this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.");
this.pauseOnExceptionButton.toggled = true;
} else {
this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions.");
this.pauseOnExceptionButton.toggled = false;
}
},
_updateDebuggerButtons: function()
{
if (InspectorBackend.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;
}
this._updatePauseOnExceptionsButton();
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 (InspectorBackend.debuggerEnabled())
return;
this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
},
_toggleDebugging: function(optionalAlways)
{
this._paused = false;
this._waitingToPause = false;
this._stepping = false;
if (InspectorBackend.debuggerEnabled())
InspectorBackend.disableDebugger(true);
else
InspectorBackend.enableDebugger(!!optionalAlways);
},
_togglePauseOnExceptions: function()
{
InspectorBackend.setPauseOnExceptions(!InspectorBackend.pauseOnExceptions());
this._updatePauseOnExceptionsButton();
},
_togglePause: function()
{
if (this._paused) {
this._paused = false;
this._waitingToPause = false;
InspectorBackend.resumeDebugger();
} else {
this._stepping = false;
this._waitingToPause = true;
InspectorBackend.pauseInDebugger();
}
this._clearInterface();
},
_stepOverClicked: function()
{
this._paused = false;
this._stepping = true;
this._clearInterface();
InspectorBackend.stepOverStatementInDebugger();
},
_stepIntoClicked: function()
{
this._paused = false;
this._stepping = true;
this._clearInterface();
InspectorBackend.stepIntoStatementInDebugger();
},
_stepOutClicked: function()
{
this._paused = false;
this._stepping = true;
this._clearInterface();
InspectorBackend.stepOutOfFunctionInDebugger();
}
}
WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
/* StoragePanel.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.StoragePanel = function(database)
{
WebInspector.Panel.call(this);
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.storageViews = document.createElement("div");
this.storageViews.id = "storage-views";
this.element.appendChild(this.storageViews);
this.storageViewStatusBarItemsContainer = document.createElement("div");
this.storageViewStatusBarItemsContainer.id = "storage-view-status-bar-items";
this.reset();
}
WebInspector.StoragePanel.prototype = {
toolbarItemClass: "storage",
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.databasesListTreeElement.removeChildren();
this.localStorageListTreeElement.removeChildren();
this.sessionStorageListTreeElement.removeChildren();
this.cookieListTreeElement.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);
},
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;
}
}
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].element);
},
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;
}
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]);
},
showCookies: function(cookieDomain)
{
if (this.visibleView)
this.visibleView.hide();
var view = this._cookieViews[cookieDomain];
if (!view) {
view = new WebInspector.CookieItemsView(cookieDomain);
this._cookieViews[cookieDomain] = 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(rows)
{
if (!rows.length)
return null;
var columns = {};
var numColumns = 0;
for (var columnIdentifier in rows[0]) {
var column = {};
column.width = columnIdentifier.length;
column.title = columnIdentifier;
columns[columnIdentifier] = column;
++numColumns;
}
var nodes = [];
var length = rows.length;
for (var i = 0; i < length; ++i) {
var data = {};
var row = rows[i];
for (var columnIdentifier in row) {
var text = row[columnIdentifier];
data[columnIdentifier] = text;
if (text.length > columns[columnIdentifier].width)
columns[columnIdentifier].width = text.length;
}
var node = new WebInspector.DataGridNode(data, false);
node.selectable = false;
nodes.push(node);
}
var totalColumnWidths = 0;
for (var columnIdentifier in columns)
totalColumnWidths += columns[columnIdentifier].width;
// Calculate the percentage width for the columns.
const minimumPrecent = Math.min(5, Math.floor(100/numColumns));
var recoupPercent = 0;
for (var columnIdentifier in columns) {
var width = columns[columnIdentifier].width;
width = Math.round((width / totalColumnWidths) * 100);
if (width < minimumPrecent) {
recoupPercent += (minimumPrecent - width);
width = minimumPrecent;
}
columns[columnIdentifier].width = width;
}
// Enforce the minimum percentage width.
while (recoupPercent > 0) {
for (var columnIdentifier in columns) {
if (columns[columnIdentifier].width > minimumPrecent) {
--columns[columnIdentifier].width;
--recoupPercent;
if (!recoupPercent)
break;
}
}
}
// Change the width property to a string suitable for a style width.
for (var columnIdentifier in columns)
columns[columnIdentifier].width += "%";
var dataGrid = new WebInspector.DataGrid(columns);
var length = nodes.length;
for (var i = 0; i < length; ++i)
dataGrid.appendChild(nodes[i]);
return dataGrid;
},
resize: function()
{
var visibleView = this.visibleView;
if (visibleView && "resize" in visibleView)
visibleView.resize();
},
updateDOMStorage: function(storageId)
{
var domStorage = this._domStorageForId(storageId);
if (!domStorage)
return;
var view = domStorage._domStorageView;
if (this.visibleView && view === this.visibleView)
domStorage._domStorageView.update();
},
_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";
}
}
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.refreshTitles();
}
WebInspector.CookieSidebarTreeElement.prototype = {
onselect: function()
{
WebInspector.panels.storage.showCookies(this._cookieDomain);
},
get mainTitle()
{
return this._cookieDomain ? this._cookieDomain : WebInspector.UIString("Local Files");
},
set mainTitle(x)
{
// Do nothing.
},
get subtitle()
{
return "";
},
set subtitle(x)
{
// Do nothing.
}
}
WebInspector.CookieSidebarTreeElement.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;
},
// 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);
this.createSidebar();
this.element.addStyleClass("profiles");
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.profileViewStatusBarItemsContainer = document.createElement("div");
this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
this._profiles = [];
this.reset();
}
WebInspector.ProfilesPanel.prototype = {
toolbarItemClass: "profiles",
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.profileViewStatusBarItemsContainer);
return items;
},
show: function()
{
WebInspector.Panel.prototype.show.call(this);
if (this._shouldPopulateProfiles)
this._populateProfiles();
},
populateInterface: function()
{
if (this.visible)
this._populateProfiles();
else
this._shouldPopulateProfiles = true;
},
profilerWasEnabled: function()
{
this.reset();
this.populateInterface();
},
profilerWasDisabled: function()
{
this.reset();
},
reset: function()
{
for (var i = 0; i < this._profiles.length; ++i)
delete this._profiles[i]._profileView;
delete this.currentQuery;
this.searchCanceled();
this._profiles = [];
this._profilesIdMap = {};
this._profileGroups = {};
this._profileGroupsForLinks = {}
this.sidebarTreeElement.removeStyleClass("some-expandable");
for (var typeId in this._profileTypesByIdMap)
this.getProfileType(typeId).treeElement.removeChildren();
this.profileViews.removeChildren();
this.profileViewStatusBarItemsContainer.removeChildren();
this._updateInterface();
},
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();
},
_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 (!this.visibleView)
this.showProfile(profile);
},
showProfile: function(profile)
{
if (!profile)
return;
if (this.visibleView)
this.visibleView.hide();
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]);
},
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;
}
},
resize: function()
{
var visibleView = this.visibleView;
if (visibleView && "resize" in visibleView)
visibleView.resize();
},
_updateInterface: function()
{
// FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
if (InspectorBackend.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.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.panelEnablerView.visible = true;
}
},
_enableProfiling: function()
{
if (InspectorBackend.profilerEnabled())
return;
this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
},
_toggleProfiling: function(optionalAlways)
{
if (InspectorBackend.profilerEnabled())
InspectorBackend.disableProfiler(true);
else
InspectorBackend.enableProfiler(!!optionalAlways);
},
_populateProfiles: function()
{
var sidebarTreeChildrenCount = this.sidebarTree.children.length;
for (var i = 0; i < sidebarTreeChildrenCount; ++i) {
var treeElement = this.sidebarTree.children[i];
if (treeElement.children.length)
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]);
}
var callId = WebInspector.Callback.wrap(populateCallback);
InspectorBackend.getProfileHeaders(callId);
delete this._shouldPopulateProfiles;
},
updateMainViewWidth: function(width)
{
this.profileViews.style.left = width + "px";
this.profileViewStatusBarItemsContainer.style.left = width + "px";
}
}
WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
WebInspector.ProfileSidebarTreeElement = function(profile)
{
this.profile = profile;
if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
this.refreshTitles();
}
WebInspector.ProfileSidebarTreeElement.prototype = {
onselect: function()
{
WebInspector.panels.profiles.showProfile(this.profile);
},
get mainTitle()
{
if (this._mainTitle)
return this._mainTitle;
if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
return WebInspector.UIString("Profile %d", 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()
{
WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
}
}
WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
WebInspector.didGetProfileHeaders = WebInspector.Callback.processCallback;
WebInspector.didGetProfile = WebInspector.Callback.processCallback;
/* 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);
}
WebInspector.ConsolePanel.prototype = {
toolbarItemClass: "console",
get toolbarItemLabel()
{
return WebInspector.UIString("Console");
},
show: function()
{
WebInspector.Panel.prototype.show.call(this);
this._previousConsoleState = WebInspector.drawer.state;
WebInspector.drawer.enterPanelMode();
WebInspector.showConsole();
},
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;
}
}
WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
/* 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.headersElement = document.createElement("div");
this.headersElement.className = "resource-view-headers";
this.element.appendChild(this.headersElement);
this.contentElement = document.createElement("div");
this.contentElement.className = "resource-view-content";
this.element.appendChild(this.contentElement);
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.httpInformationTreeElement = new TreeElement("", null, true);
this.httpInformationTreeElement.expanded = false;
this.httpInformationTreeElement.selectable = false;
this.headersTreeOutline.appendChild(this.httpInformationTreeElement);
this.requestHeadersTreeElement = new TreeElement("", null, true);
this.requestHeadersTreeElement.expanded = false;
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 = false;
this.queryStringTreeElement.selectable = false;
this.queryStringTreeElement.hidden = true;
this.headersTreeOutline.appendChild(this.queryStringTreeElement);
this.formDataTreeElement = new TreeElement("", null, true);
this.formDataTreeElement.expanded = false;
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 = false;
this.requestPayloadTreeElement.selectable = false;
this.requestPayloadTreeElement.hidden = true;
this.headersTreeOutline.appendChild(this.requestPayloadTreeElement);
this.responseHeadersTreeElement = new TreeElement("", null, true);
this.responseHeadersTreeElement.expanded = false;
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._refreshRequestHeaders();
this._refreshResponseHeaders();
this._refreshHTTPInformation();
}
WebInspector.ResourceView.prototype = {
get headersVisible()
{
return this._headersVisible;
},
set headersVisible(x)
{
if (x === this._headersVisible)
return;
this._headersVisible = x;
if (x)
this.element.addStyleClass("headers-visible");
else
this.element.removeStyleClass("headers-visible");
},
attach: function()
{
if (!this.element.parentNode) {
var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views"));
if (parentElement)
parentElement.appendChild(this.element);
}
},
_refreshURL: function()
{
var url = this.resource.url;
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";
statusCodeImage = "<img class=\"resource-status-image\" src=\"" + statusImageSource + "\" title=\"" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "\">";
}
this.urlTreeElement.title = statusCodeImage + "<span class=\"resource-url\">" + url.escapeHTML() + "</span>";
this._refreshQueryString();
},
_refreshQueryString: function()
{
var url = this.resource.url;
var hasQueryString = url.indexOf("?") >= 0;
if (!hasQueryString) {
this.queryStringTreeElement.hidden = true;
return;
}
this.queryStringTreeElement.hidden = false;
var parmString = url.split("?", 2)[1];
this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement);
},
_refreshFormData: function()
{
this.formDataTreeElement.hidden = true;
this.requestPayloadTreeElement.hidden = true;
var isFormData = this.resource.requestFormData;
if (!isFormData)
return;
var isFormEncoded = false;
var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type");
if (requestContentType && requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
isFormEncoded = true;
if (isFormEncoded) {
this.formDataTreeElement.hidden = false;
this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement);
} else {
this.requestPayloadTreeElement.hidden = false;
this._refreshRequestPayload(this.resource.requestFormData);
}
},
_refreshRequestPayload: function(formData)
{
this.requestPayloadTreeElement.removeChildren();
var title = "<div class=\"header-name\">&nbsp;</div>";
title += "<div class=\"raw-form-data header-value\">" + formData.escapeHTML() + "</div>";
var parmTreeElement = new TreeElement(title, null, false);
parmTreeElement.selectable = false;
this.requestPayloadTreeElement.appendChild(parmTreeElement);
},
_refreshParms: function(title, parmString, parmsTreeElement)
{
var parms = parmString.split("&");
for (var i = 0; i < parms.length; ++i) {
var parm = parms[i];
parm = parm.split("=", 2);
if (parm.length == 1)
parm.push("");
parms[i] = parm;
}
parmsTreeElement.removeChildren();
parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>";
for (var i = 0; i < parms.length; ++i) {
var key = parms[i][0];
var value = parms[i][1];
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\">" + key.escapeHTML() + ":</div>";
title += "<div class=\"header-value\">" + 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(treeElement, 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()
{
this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement);
this._refreshFormData();
},
_refreshResponseHeaders: function()
{
this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement);
},
_refreshHTTPInformation: function()
{
const listElements = 2;
var headerElement = this.httpInformationTreeElement;
headerElement.removeChildren();
headerElement.hidden = !this.resource.statusCode;
if (this.resource.statusCode) {
headerElement.title = WebInspector.UIString("HTTP Information") + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", listElements) + "</span>";
var title = "<div class=\"header-name\">" + WebInspector.UIString("Request Method") + ":</div>";
title += "<div class=\"header-value\">" + this.resource.requestMethod + "</div>"
var headerTreeElement = new TreeElement(title, null, false);
headerTreeElement.selectable = false;
headerElement.appendChild(headerTreeElement);
title = "<div class=\"header-name\">" + WebInspector.UIString("Status Code") + ":</div>";
title += "<div class=\"header-value\">" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "</div>"
headerTreeElement = new TreeElement(title, null, false);
headerTreeElement.selectable = false;
headerElement.appendChild(headerTreeElement);
}
},
_refreshHeaders: function(title, headers, 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\">" + headers[i].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) 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.SourceFrame = function(element, addBreakpointDelegate)
{
this.messages = [];
this.breakpoints = [];
this._shortcuts = {};
this.addBreakpointDelegate = addBreakpointDelegate;
this.element = element || document.createElement("iframe");
this.element.addStyleClass("source-view-frame");
this.element.setAttribute("viewsource", "true");
this.element.addEventListener("load", this._loaded.bind(this), false);
}
WebInspector.SourceFrame.prototype = {
get executionLine()
{
return this._executionLine;
},
set executionLine(x)
{
if (this._executionLine === x)
return;
var previousLine = this._executionLine;
this._executionLine = x;
this._updateExecutionLine(previousLine);
},
get autoSizesToFitContentHeight()
{
return this._autoSizesToFitContentHeight;
},
set autoSizesToFitContentHeight(x)
{
if (this._autoSizesToFitContentHeight === x)
return;
this._autoSizesToFitContentHeight = x;
if (this._autoSizesToFitContentHeight) {
this._windowResizeListener = this._windowResized.bind(this);
window.addEventListener("resize", this._windowResizeListener, false);
this.sizeToFitContentHeight();
} else {
this.element.style.removeProperty("height");
if (this.element.contentDocument)
this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit");
window.removeEventListener("resize", this._windowResizeListener, false);
delete this._windowResizeListener;
}
},
sourceRow: function(lineNumber)
{
if (!lineNumber || !this.element.contentDocument)
return;
var table = this.element.contentDocument.getElementsByTagName("table")[0];
if (!table)
return;
var rows = table.rows;
// Line numbers are a 1-based index, but the rows collection is 0-based.
--lineNumber;
return rows[lineNumber];
},
lineNumberForSourceRow: function(sourceRow)
{
// Line numbers are a 1-based index, but the rows collection is 0-based.
var lineNumber = 0;
while (sourceRow) {
++lineNumber;
sourceRow = sourceRow.previousSibling;
}
return lineNumber;
},
revealLine: function(lineNumber)
{
if (!this._isContentLoaded()) {
this._lineNumberToReveal = lineNumber;
return;
}
var row = this.sourceRow(lineNumber);
if (row)
row.scrollIntoViewIfNeeded(true);
},
addBreakpoint: function(breakpoint)
{
this.breakpoints.push(breakpoint);
breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
this._addBreakpointToSource(breakpoint);
},
removeBreakpoint: function(breakpoint)
{
this.breakpoints.remove(breakpoint);
breakpoint.removeEventListener("enabled", null, this);
breakpoint.removeEventListener("disabled", null, this);
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);
this._addMessageToSource(msg);
},
clearMessages: function()
{
this.messages = [];
if (!this.element.contentDocument)
return;
var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble");
if (!bubbles)
return;
for (var i = 0; i < bubbles.length; ++i) {
var bubble = bubbles[i];
bubble.parentNode.removeChild(bubble);
}
},
sizeToFitContentHeight: function()
{
if (this.element.contentDocument) {
this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px");
this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit");
}
},
_highlightLineEnds: function(event)
{
event.target.parentNode.removeStyleClass("webkit-highlighted-line");
},
highlightLine: function(lineNumber)
{
if (!this._isContentLoaded()) {
this._lineNumberToHighlight = lineNumber;
return;
}
var sourceRow = this.sourceRow(lineNumber);
if (!sourceRow)
return;
var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
// Trick to reset the animation if the user clicks on the same link
// Using a timeout to avoid coalesced style updates
line.style.setProperty("-webkit-animation-name", "none");
setTimeout(function () {
line.style.removeProperty("-webkit-animation-name");
sourceRow.addStyleClass("webkit-highlighted-line");
}, 0);
},
_loaded: function()
{
WebInspector.addMainEventListeners(this.element.contentDocument);
this.element.contentDocument.addEventListener("contextmenu", this._documentContextMenu.bind(this), true);
this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true);
this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true);
this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true);
this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false);
// Register 'eval' shortcut.
var platformSpecificModifier = WebInspector.isMac() ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift);
this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this);
var headElement = this.element.contentDocument.head;
if (!headElement) {
headElement = this.element.contentDocument.createElement("head");
this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild);
}
var linkElement = this.element.contentDocument.createElement("link");
linkElement.type = "text/css";
linkElement.rel = "stylesheet";
linkElement.href = "inspectorSyntaxHighlight.css";
headElement.appendChild(linkElement);
var styleElement = this.element.contentDocument.createElement("style");
headElement.appendChild(styleElement);
// Add these style rules here since they are specific to the Inspector. They also behave oddly and not
// all properties apply if added to view-source.css (because it is a user agent sheet.)
var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n";
styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n";
styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n";
styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n";
styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n";
styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n";
styleText += ".webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-conditional); }\n";
styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled-conditional); }\n";
styleText += ".webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-conditional-program-counter); }\n";
styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); }\n";
styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n";
styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n";
styleText += ".webkit-line-content { background-color: white; }\n";
styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n";
styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n";
// TODO: Move these styles into inspector.css once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed and popup moved into the top frame.
styleText += ".popup-content { position: absolute; z-index: 10000; padding: 4px; background-color: rgb(203, 226, 255); -webkit-border-radius: 7px; border: 2px solid rgb(169, 172, 203); }";
styleText += ".popup-glasspane { position: absolute; top: 0; left: 0; height: 100%; width: 100%; opacity: 0; z-index: 9900; }";
styleText += ".popup-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; font-size: 11px; text-align: left; text-shadow: none; color: rgb(85, 85, 85); cursor: default; margin: 0 0 2px 0; }";
styleText += ".popup-content.breakpoint-condition { width: 90%; }";
styleText += ".popup-content input#bp-condition { font-family: monospace; margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; box-shadow: none !important; outline: none !important; -webkit-user-modify: read-write; }";
// This class is already in inspector.css
styleText += ".hidden { display: none !important; }";
styleElement.textContent = styleText;
this._needsProgramCounterImage = true;
this._needsBreakpointImages = true;
this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass;
this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass;
this.element.contentWindow.Element.prototype.removeChildren = Element.prototype.removeChildren;
this.element.contentWindow.Element.prototype.positionAt = Element.prototype.positionAt;
this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses;
this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass;
this.element.contentWindow.Element.prototype.pageOffsetRelativeToWindow = Element.prototype.pageOffsetRelativeToWindow;
this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetLeft", Element.prototype.__lookupGetter__("totalOffsetLeft"));
this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetTop", Element.prototype.__lookupGetter__("totalOffsetTop"));
this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName;
this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray;
this._addExistingMessagesToSource();
this._addExistingBreakpointsToSource();
this._updateExecutionLine();
if (this._executionLine)
this.revealLine(this._executionLine);
if (this.autoSizesToFitContentHeight)
this.sizeToFitContentHeight();
if (this._lineNumberToReveal) {
this.revealLine(this._lineNumberToReveal);
delete this._lineNumberToReveal;
}
if (this._lineNumberToHighlight) {
this.highlightLine(this._lineNumberToHighlight);
delete this._lineNumberToHighlight;
}
this.dispatchEventToListeners("content loaded");
},
_isContentLoaded: function() {
var doc = this.element.contentDocument;
return doc && doc.getElementsByTagName("table")[0];
},
_windowResized: function(event)
{
if (!this._autoSizesToFitContentHeight)
return;
this.sizeToFitContentHeight();
},
_documentContextMenu: function(event)
{
if (!event.target.hasStyleClass("webkit-line-number"))
return;
var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
if (!sourceRow._breakpointObject && this.addBreakpointDelegate)
this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
var breakpoint = sourceRow._breakpointObject;
if (!breakpoint)
return;
this._editBreakpointCondition(event.target, sourceRow, breakpoint);
event.preventDefault();
},
_documentMouseDown: function(event)
{
if (!event.target.hasStyleClass("webkit-line-number"))
return;
if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
return;
var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
if (sourceRow._breakpointObject && sourceRow._breakpointObject.enabled)
sourceRow._breakpointObject.enabled = false;
else if (sourceRow._breakpointObject)
WebInspector.panels.scripts.removeBreakpoint(sourceRow._breakpointObject);
else if (this.addBreakpointDelegate)
this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
event.preventDefault();
},
_editBreakpointCondition: function(eventTarget, sourceRow, breakpoint)
{
// TODO: Migrate the popup to the top-level document and remove the blur listener from conditionElement once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
var popupDocument = this.element.contentDocument;
this._showBreakpointConditionPopup(eventTarget, breakpoint.line, popupDocument);
function committed(element, newText)
{
breakpoint.condition = newText;
if (breakpoint.condition)
sourceRow.addStyleClass("webkit-breakpoint-conditional");
else
sourceRow.removeStyleClass("webkit-breakpoint-conditional");
dismissed.call(this);
}
function dismissed()
{
this._popup.hide();
delete this._conditionEditorElement;
}
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(clickedElement, lineNumber, popupDocument)
{
var popupContentElement = this._createPopupElement(lineNumber, popupDocument);
var lineElement = clickedElement.enclosingNodeOrSelfWithNodeName("td").nextSibling;
if (this._popup) {
this._popup.hide();
this._popup.element = popupContentElement;
} else {
this._popup = new WebInspector.Popup(popupContentElement);
this._popup.autoHide = true;
}
this._popup.anchor = lineElement;
this._popup.show();
},
_createPopupElement: function(lineNumber, popupDocument)
{
var popupContentElement = popupDocument.createElement("div");
popupContentElement.className = "popup-content breakpoint-condition";
var labelElement = document.createElement("label");
labelElement.className = "popup-message";
labelElement.htmlFor = "bp-condition";
labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
popupContentElement.appendChild(labelElement);
var editorElement = document.createElement("input");
editorElement.id = "bp-condition";
editorElement.type = "text"
popupContentElement.appendChild(editorElement);
this._conditionEditorElement = editorElement;
return popupContentElement;
},
_documentKeyDown: function(event)
{
var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
var handler = this._shortcuts[shortcut];
if (handler) {
handler(event);
event.preventDefault();
} else {
WebInspector.documentKeyDown(event);
}
},
_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().trimWhitespace();
WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", function(result, exception) {
WebInspector.showConsole();
var commandMessage = new WebInspector.ConsoleCommand(expression);
WebInspector.console.addMessage(commandMessage);
WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
});
},
_breakpointEnableChanged: function(event)
{
var breakpoint = event.target;
var sourceRow = this.sourceRow(breakpoint.line);
if (!sourceRow)
return;
sourceRow.addStyleClass("webkit-breakpoint");
if (breakpoint.enabled)
sourceRow.removeStyleClass("webkit-breakpoint-disabled");
else
sourceRow.addStyleClass("webkit-breakpoint-disabled");
},
_updateExecutionLine: function(previousLine)
{
if (previousLine) {
var sourceRow = this.sourceRow(previousLine);
if (sourceRow)
sourceRow.removeStyleClass("webkit-execution-line");
}
if (!this._executionLine)
return;
this._drawProgramCounterImageIfNeeded();
var sourceRow = this.sourceRow(this._executionLine);
if (sourceRow)
sourceRow.addStyleClass("webkit-execution-line");
},
_addExistingBreakpointsToSource: function()
{
var length = this.breakpoints.length;
for (var i = 0; i < length; ++i)
this._addBreakpointToSource(this.breakpoints[i]);
},
_addBreakpointToSource: function(breakpoint)
{
var sourceRow = this.sourceRow(breakpoint.line);
if (!sourceRow)
return;
breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent;
this._drawBreakpointImagesIfNeeded();
sourceRow._breakpointObject = breakpoint;
sourceRow.addStyleClass("webkit-breakpoint");
if (!breakpoint.enabled)
sourceRow.addStyleClass("webkit-breakpoint-disabled");
if (breakpoint.condition)
sourceRow.addStyleClass("webkit-breakpoint-conditional");
},
_removeBreakpointFromSource: function(breakpoint)
{
var sourceRow = this.sourceRow(breakpoint.line);
if (!sourceRow)
return;
delete sourceRow._breakpointObject;
sourceRow.removeStyleClass("webkit-breakpoint");
sourceRow.removeStyleClass("webkit-breakpoint-disabled");
sourceRow.removeStyleClass("webkit-breakpoint-conditional");
},
_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);
},
_addExistingMessagesToSource: function()
{
var length = this.messages.length;
for (var i = 0; i < length; ++i)
this._addMessageToSource(this.messages[i]);
},
_addMessageToSource: function(msg)
{
var row = this.sourceRow(msg.line);
if (!row)
return;
var cell = row.cells[1];
if (!cell)
return;
var messageBubbleElement = cell.lastChild;
if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
messageBubbleElement = this.element.contentDocument.createElement("div");
messageBubbleElement.className = "webkit-html-message-bubble";
cell.appendChild(messageBubbleElement);
}
if (!row.messages)
row.messages = [];
for (var i = 0; i < row.messages.length; ++i) {
if (row.messages[i].isEqual(msg, true)) {
this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta);
return;
}
}
row.messages.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 = this.element.contentDocument.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";
// Adopt the image element since it wasn't created in element's contentDocument.
image = this.element.contentDocument.adoptNode(image);
messageLineElement.appendChild(image);
messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message));
msg._resourceMessageLineElement = messageLineElement;
},
_drawProgramCounterInContext: function(ctx, glow)
{
if (glow)
ctx.save();
ctx.beginPath();
ctx.moveTo(17, 2);
ctx.lineTo(19, 2);
ctx.lineTo(19, 0);
ctx.lineTo(21, 0);
ctx.lineTo(26, 5.5);
ctx.lineTo(21, 11);
ctx.lineTo(19, 11);
ctx.lineTo(19, 9);
ctx.lineTo(17, 9);
ctx.closePath();
ctx.fillStyle = "rgb(142, 5, 4)";
if (glow) {
ctx.shadowBlur = 4;
ctx.shadowColor = "rgb(255, 255, 255)";
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = 0;
}
ctx.fill();
ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels.
if (glow)
ctx.restore();
},
_drawProgramCounterImageIfNeeded: function()
{
if (!this._needsProgramCounterImage || !this.element.contentDocument)
return;
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11);
ctx.clearRect(0, 0, 26, 11);
this._drawProgramCounterInContext(ctx, true);
delete this._needsProgramCounterImage;
},
_drawBreakpointImagesIfNeeded: function(conditional)
{
if (!this._needsBreakpointImages || !this.element.contentDocument)
return;
function drawBreakpoint(ctx, disabled, conditional)
{
ctx.beginPath();
ctx.moveTo(0, 2);
ctx.lineTo(2, 0);
ctx.lineTo(21, 0);
ctx.lineTo(26, 5.5);
ctx.lineTo(21, 11);
ctx.lineTo(2, 11);
ctx.lineTo(0, 9);
ctx.closePath();
ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)";
ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)";
ctx.lineWidth = 3;
ctx.fill();
ctx.save();
ctx.clip();
ctx.stroke();
ctx.restore();
if (!disabled)
return;
ctx.save();
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillRect(0, 0, 26, 11);
ctx.restore();
}
// Unconditional breakpoints.
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx);
ctx.clearRect(20, 0, 6, 11);
this._drawProgramCounterInContext(ctx, true);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, true);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, true);
ctx.clearRect(20, 0, 6, 11);
this._drawProgramCounterInContext(ctx, true);
// Conditional breakpoints.
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, false, true);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, false, true);
ctx.clearRect(20, 0, 6, 11);
this._drawProgramCounterInContext(ctx, true);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, true, true);
var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11);
ctx.clearRect(0, 0, 26, 11);
drawBreakpoint(ctx, true, true);
ctx.clearRect(20, 0, 6, 11);
this._drawProgramCounterInContext(ctx, true);
delete this._needsBreakpointImages;
},
syntaxHighlightJavascript: function()
{
var table = this.element.contentDocument.getElementsByTagName("table")[0];
if (!table)
return;
var jsSyntaxHighlighter = new WebInspector.JavaScriptSourceSyntaxHighlighter(table, this);
jsSyntaxHighlighter.process();
},
syntaxHighlightCSS: function()
{
var table = this.element.contentDocument.getElementsByTagName("table")[0];
if (!table)
return;
var cssSyntaxHighlighter = new WebInspector.CSSSourceSyntaxHighlighter(table, this);
cssSyntaxHighlighter.process();
}
}
WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;
WebInspector.SourceSyntaxHighlighter = function(table, sourceFrame)
{
this.table = table;
this.sourceFrame = sourceFrame;
}
WebInspector.SourceSyntaxHighlighter.prototype = {
createSpan: function(content, className)
{
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(content));
return span;
},
process: function()
{
// Split up the work into chunks so we don't block the
// UI thread while processing.
var rows = this.table.rows;
var rowsLength = rows.length;
const tokensPerChunk = 100;
const lineLengthLimit = 20000;
var boundProcessChunk = processChunk.bind(this);
var processChunkInterval = setInterval(boundProcessChunk, 25);
boundProcessChunk();
function processChunk()
{
for (var i = 0; i < tokensPerChunk; i++) {
if (this.cursor >= this.lineCode.length)
moveToNextLine.call(this);
if (this.lineIndex >= rowsLength) {
this.sourceFrame.dispatchEventToListeners("syntax highlighting complete");
return;
}
if (this.cursor > lineLengthLimit) {
var codeFragment = this.lineCode.substring(this.cursor);
this.nonToken += codeFragment;
this.cursor += codeFragment.length;
}
this.lex();
}
}
function moveToNextLine()
{
this.appendNonToken();
var row = rows[this.lineIndex];
var line = row ? row.cells[1] : null;
if (line && this.newLine) {
line.removeChildren();
if (this.messageBubble)
this.newLine.appendChild(this.messageBubble);
line.parentNode.insertBefore(this.newLine, line);
line.parentNode.removeChild(line);
this.newLine = null;
}
this.lineIndex++;
if (this.lineIndex >= rowsLength && processChunkInterval) {
clearInterval(processChunkInterval);
this.sourceFrame.dispatchEventToListeners("syntax highlighting complete");
return;
}
row = rows[this.lineIndex];
line = row ? row.cells[1] : null;
this.messageBubble = null;
if (line.lastChild && line.lastChild.nodeType === Node.ELEMENT_NODE && line.lastChild.hasStyleClass("webkit-html-message-bubble")) {
this.messageBubble = line.lastChild;
line.removeChild(this.messageBubble);
}
this.lineCode = line.textContent;
this.newLine = line.cloneNode(false);
this.cursor = 0;
if (!line)
moveToNextLine();
}
},
lex: function()
{
var token = null;
var codeFragment = this.lineCode.substring(this.cursor);
for (var i = 0; i < this.rules.length; i++) {
var rule = this.rules[i];
var ruleContinueStateCondition = typeof rule.continueStateCondition === "undefined" ? this.ContinueState.None : rule.continueStateCondition;
if (this.continueState === ruleContinueStateCondition) {
if (typeof rule.lexStateCondition !== "undefined" && this.lexState !== rule.lexStateCondition)
continue;
var match = rule.pattern.exec(codeFragment);
if (match) {
token = match[0];
if (token) {
if (!rule.dontAppendNonToken)
this.appendNonToken();
return rule.action.call(this, token);
}
}
}
}
this.nonToken += codeFragment[0];
this.cursor++;
},
appendNonToken: function ()
{
if (this.nonToken.length > 0) {
this.newLine.appendChild(document.createTextNode(this.nonToken));
this.nonToken = "";
}
},
syntaxHighlightNode: function(node)
{
this.lineCode = node.textContent;
node.removeChildren();
this.newLine = node;
this.cursor = 0;
while (true) {
if (this.cursor >= this.lineCode.length) {
var codeFragment = this.lineCode.substring(this.cursor);
this.nonToken += codeFragment;
this.cursor += codeFragment.length;
this.appendNonToken();
this.newLine = null;
return;
}
this.lex();
}
}
}
WebInspector.CSSSourceSyntaxHighlighter = function(table, sourceFrame) {
WebInspector.SourceSyntaxHighlighter.call(this, table, sourceFrame);
this.LexState = {
Initial: 1,
DeclarationProperty: 2,
DeclarationValue: 3,
AtMedia: 4,
AtRule: 5,
AtKeyframes: 6
};
this.ContinueState = {
None: 0,
Comment: 1
};
this.nonToken = "";
this.cursor = 0;
this.lineIndex = -1;
this.lineCode = "";
this.newLine = null;
this.lexState = this.LexState.Initial;
this.continueState = this.ContinueState.None;
const urlPattern = /^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i;
const stringPattern = /^(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')/i;
const identPattern = /^-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*/i;
const startBlockPattern = /^{/i;
const endBlockPattern = /^}/i;
this.rules = [{
pattern: /^\/\*[^\*]*\*+([^\/*][^*]*\*+)*\//i,
action: commentAction
}, {
pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*)/i,
action: commentStartAction
}, {
pattern: /^(?:(?:[^\*]|\*[^\/])*\*+\/)/i,
action: commentEndAction,
continueStateCondition: this.ContinueState.Comment
}, {
pattern: /^.*/i,
action: commentMiddleAction,
continueStateCondition: this.ContinueState.Comment
}, {
pattern: /^(?:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\*)(?:#-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\.-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\[\s*-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*(?:(?:=|~=|\|=)\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*'))\s*)?\]|:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\(\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*)?\)))*|(?:#-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\.-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|\[\s*-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*(?:(?:=|~=|\|=)\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*'))\s*)?\]|:(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*|-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\(\s*(?:-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*\s*)?\)))+)/i,
action: selectorAction,
lexStateCondition: this.LexState.Initial
}, {
pattern: startBlockPattern,
action: startRulesetBlockAction,
lexStateCondition: this.LexState.Initial,
dontAppendNonToken: true
}, {
pattern: identPattern,
action: propertyAction,
lexStateCondition: this.LexState.DeclarationProperty,
dontAppendNonToken: true
}, {
pattern: /^:/i,
action: declarationColonAction,
lexStateCondition: this.LexState.DeclarationProperty,
dontAppendNonToken: true
}, {
pattern: /^(?:#(?:[\da-f]{6}|[\da-f]{3})|rgba\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|hsla\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|rgb\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\)|hsl\(\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*,\s*(?:\d+|\d*\.\d+)%?\s*\))/i,
action: colorAction,
lexStateCondition: this.LexState.DeclarationValue
}, {
pattern: /^(?:-?(?:\d+|\d*\.\d+)(?:em|rem|__qem|ex|px|cm|mm|in|pt|pc|deg|rad|grad|turn|ms|s|Hz|kHz|%)?)/i,
action: numvalueAction,
lexStateCondition: this.LexState.DeclarationValue
}, {
pattern: urlPattern,
action: urlAction,
lexStateCondition: this.LexState.DeclarationValue
}, {
pattern: stringPattern,
action: stringAction,
lexStateCondition: this.LexState.DeclarationValue
}, {
pattern: /^!\s*important/i,
action: importantAction,
lexStateCondition: this.LexState.DeclarationValue
}, {
pattern: identPattern,
action: valueIdentAction,
lexStateCondition: this.LexState.DeclarationValue,
dontAppendNonToken: true
}, {
pattern: /^;/i,
action: declarationSemicolonAction,
lexStateCondition: this.LexState.DeclarationValue,
dontAppendNonToken: true
}, {
pattern: endBlockPattern,
action: endRulesetBlockAction,
lexStateCondition: this.LexState.DeclarationProperty,
dontAppendNonToken: true
}, {
pattern: endBlockPattern,
action: endRulesetBlockAction,
lexStateCondition: this.LexState.DeclarationValue,
dontAppendNonToken: true
}, {
pattern: /^@media/i,
action: atMediaAction,
lexStateCondition: this.LexState.Initial
}, {
pattern: startBlockPattern,
action: startAtMediaBlockAction,
lexStateCondition: this.LexState.AtMedia,
dontAppendNonToken: true
}, {
pattern: /^@-webkit-keyframes/i,
action: atKeyframesAction,
lexStateCondition: this.LexState.Initial
}, {
pattern: startBlockPattern,
action: startAtMediaBlockAction,
lexStateCondition: this.LexState.AtKeyframes,
dontAppendNonToken: true
}, {
pattern: /^@-?(?:\w|(?:\\[\da-f]{1,6}\s?|\.))(?:[-\w]|(?:\\[\da-f]{1,6}\s?|\.))*/i,
action: atRuleAction,
lexStateCondition: this.LexState.Initial
}, {
pattern: /^;/i,
action: endAtRuleAction,
lexStateCondition: this.LexState.AtRule
}, {
pattern: urlPattern,
action: urlAction,
lexStateCondition: this.LexState.AtRule
}, {
pattern: stringPattern,
action: stringAction,
lexStateCondition: this.LexState.AtRule
}, {
pattern: stringPattern,
action: stringAction,
lexStateCondition: this.LexState.AtKeyframes
}, {
pattern: identPattern,
action: atRuleIdentAction,
lexStateCondition: this.LexState.AtRule,
dontAppendNonToken: true
}, {
pattern: identPattern,
action: atRuleIdentAction,
lexStateCondition: this.LexState.AtMedia,
dontAppendNonToken: true
}, {
pattern: startBlockPattern,
action: startAtRuleBlockAction,
lexStateCondition: this.LexState.AtRule,
dontAppendNonToken: true
}];
function commentAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
}
function commentStartAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
this.continueState = this.ContinueState.Comment;
}
function commentEndAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
this.continueState = this.ContinueState.None;
}
function commentMiddleAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-comment"));
}
function selectorAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-selector"));
}
function startRulesetBlockAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.DeclarationProperty;
}
function endRulesetBlockAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
const propertyKeywords = {
"background": true,
"background-attachment": true,
"background-clip": true,
"background-color": true,
"background-image": true,
"background-origin": true,
"background-position": true,
"background-position-x": true,
"background-position-y": true,
"background-repeat": true,
"background-repeat-x": true,
"background-repeat-y": true,
"background-size": true,
"border": true,
"border-bottom": true,
"border-bottom-color": true,
"border-bottom-left-radius": true,
"border-bottom-right-radius": true,
"border-bottom-style": true,
"border-bottom-width": true,
"border-collapse": true,
"border-color": true,
"border-left": true,
"border-left-color": true,
"border-left-style": true,
"border-left-width": true,
"border-radius": true,
"border-right": true,
"border-right-color": true,
"border-right-style": true,
"border-right-width": true,
"border-spacing": true,
"border-style": true,
"border-top": true,
"border-top-color": true,
"border-top-left-radius": true,
"border-top-right-radius": true,
"border-top-style": true,
"border-top-width": true,
"border-width": true,
"bottom": true,
"caption-side": true,
"clear": true,
"clip": true,
"color": true,
"content": true,
"counter-increment": true,
"counter-reset": true,
"cursor": true,
"direction": true,
"display": true,
"empty-cells": true,
"float": true,
"font": true,
"font-family": true,
"font-size": true,
"font-stretch": true,
"font-style": true,
"font-variant": true,
"font-weight": true,
"height": true,
"left": true,
"letter-spacing": true,
"line-height": true,
"list-style": true,
"list-style-image": true,
"list-style-position": true,
"list-style-type": true,
"margin": true,
"margin-bottom": true,
"margin-left": true,
"margin-right": true,
"margin-top": true,
"max-height": true,
"max-width": true,
"min-height": true,
"min-width": true,
"opacity": true,
"orphans": true,
"outline": true,
"outline-color": true,
"outline-offset": true,
"outline-style": true,
"outline-width": true,
"overflow": true,
"overflow-x": true,
"overflow-y": true,
"padding": true,
"padding-bottom": true,
"padding-left": true,
"padding-right": true,
"padding-top": true,
"page": true,
"page-break-after": true,
"page-break-before": true,
"page-break-inside": true,
"pointer-events": true,
"position": true,
"quotes": true,
"resize": true,
"right": true,
"size": true,
"src": true,
"table-layout": true,
"text-align": true,
"text-decoration": true,
"text-indent": true,
"text-line-through": true,
"text-line-through-color": true,
"text-line-through-mode": true,
"text-line-through-style": true,
"text-line-through-width": true,
"text-overflow": true,
"text-overline": true,
"text-overline-color": true,
"text-overline-mode": true,
"text-overline-style": true,
"text-overline-width": true,
"text-rendering": true,
"text-shadow": true,
"text-transform": true,
"text-underline": true,
"text-underline-color": true,
"text-underline-mode": true,
"text-underline-style": true,
"text-underline-width": true,
"top": true,
"unicode-bidi": true,
"unicode-range": true,
"vertical-align": true,
"visibility": true,
"white-space": true,
"widows": true,
"width": true,
"word-break": true,
"word-spacing": true,
"word-wrap": true,
"z-index": true,
"zoom": true,
"-webkit-animation": true,
"-webkit-animation-delay": true,
"-webkit-animation-direction": true,
"-webkit-animation-duration": true,
"-webkit-animation-iteration-count": true,
"-webkit-animation-name": true,
"-webkit-animation-play-state": true,
"-webkit-animation-timing-function": true,
"-webkit-appearance": true,
"-webkit-backface-visibility": true,
"-webkit-background-clip": true,
"-webkit-background-composite": true,
"-webkit-background-origin": true,
"-webkit-background-size": true,
"-webkit-binding": true,
"-webkit-border-fit": true,
"-webkit-border-horizontal-spacing": true,
"-webkit-border-image": true,
"-webkit-border-radius": true,
"-webkit-border-vertical-spacing": true,
"-webkit-box-align": true,
"-webkit-box-direction": true,
"-webkit-box-flex": true,
"-webkit-box-flex-group": true,
"-webkit-box-lines": true,
"-webkit-box-ordinal-group": true,
"-webkit-box-orient": true,
"-webkit-box-pack": true,
"-webkit-box-reflect": true,
"-webkit-box-shadow": true,
"-webkit-box-sizing": true,
"-webkit-column-break-after": true,
"-webkit-column-break-before": true,
"-webkit-column-break-inside": true,
"-webkit-column-count": true,
"-webkit-column-gap": true,
"-webkit-column-rule": true,
"-webkit-column-rule-color": true,
"-webkit-column-rule-style": true,
"-webkit-column-rule-width": true,
"-webkit-column-width": true,
"-webkit-columns": true,
"-webkit-font-size-delta": true,
"-webkit-font-smoothing": true,
"-webkit-highlight": true,
"-webkit-line-break": true,
"-webkit-line-clamp": true,
"-webkit-margin-bottom-collapse": true,
"-webkit-margin-collapse": true,
"-webkit-margin-start": true,
"-webkit-margin-top-collapse": true,
"-webkit-marquee": true,
"-webkit-marquee-direction": true,
"-webkit-marquee-increment": true,
"-webkit-marquee-repetition": true,
"-webkit-marquee-speed": true,
"-webkit-marquee-style": true,
"-webkit-mask": true,
"-webkit-mask-attachment": true,
"-webkit-mask-box-image": true,
"-webkit-mask-clip": true,
"-webkit-mask-composite": true,
"-webkit-mask-image": true,
"-webkit-mask-origin": true,
"-webkit-mask-position": true,
"-webkit-mask-position-x": true,
"-webkit-mask-position-y": true,
"-webkit-mask-repeat": true,
"-webkit-mask-repeat-x": true,
"-webkit-mask-repeat-y": true,
"-webkit-mask-size": true,
"-webkit-match-nearest-mail-blockquote-color": true,
"-webkit-nbsp-mode": true,
"-webkit-padding-start": true,
"-webkit-perspective": true,
"-webkit-perspective-origin": true,
"-webkit-perspective-origin-x": true,
"-webkit-perspective-origin-y": true,
"-webkit-rtl-ordering": true,
"-webkit-text-decorations-in-effect": true,
"-webkit-text-fill-color": true,
"-webkit-text-security": true,
"-webkit-text-size-adjust": true,
"-webkit-text-stroke": true,
"-webkit-text-stroke-color": true,
"-webkit-text-stroke-width": true,
"-webkit-transform": true,
"-webkit-transform-origin": true,
"-webkit-transform-origin-x": true,
"-webkit-transform-origin-y": true,
"-webkit-transform-origin-z": true,
"-webkit-transform-style": true,
"-webkit-transition": true,
"-webkit-transition-delay": true,
"-webkit-transition-duration": true,
"-webkit-transition-property": true,
"-webkit-transition-timing-function": true,
"-webkit-user-drag": true,
"-webkit-user-modify": true,
"-webkit-user-select": true,
"-webkit-variable-declaration-block": true
};
function propertyAction(token)
{
this.cursor += token.length;
if (token in propertyKeywords) {
this.appendNonToken.call(this);
this.newLine.appendChild(this.createSpan(token, "webkit-css-property"));
} else
this.nonToken += token;
}
function declarationColonAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.DeclarationValue;
}
const valueKeywords = {
"inherit": true,
"initial": true,
"none": true,
"hidden": true,
"inset": true,
"groove": true,
"ridge": true,
"outset": true,
"dotted": true,
"dashed": true,
"solid": true,
"double": true,
"caption": true,
"icon": true,
"menu": true,
"message-box": true,
"small-caption": true,
"-webkit-mini-control": true,
"-webkit-small-control": true,
"-webkit-control": true,
"status-bar": true,
"italic": true,
"oblique": true,
"all": true,
"small-caps": true,
"normal": true,
"bold": true,
"bolder": true,
"lighter": true,
"xx-small": true,
"x-small": true,
"small": true,
"medium": true,
"large": true,
"x-large": true,
"xx-large": true,
"-webkit-xxx-large": true,
"smaller": true,
"larger": true,
"wider": true,
"narrower": true,
"ultra-condensed": true,
"extra-condensed": true,
"condensed": true,
"semi-condensed": true,
"semi-expanded": true,
"expanded": true,
"extra-expanded": true,
"ultra-expanded": true,
"serif": true,
"sans-serif": true,
"cursive": true,
"fantasy": true,
"monospace": true,
"-webkit-body": true,
"aqua": true,
"black": true,
"blue": true,
"fuchsia": true,
"gray": true,
"green": true,
"lime": true,
"maroon": true,
"navy": true,
"olive": true,
"orange": true,
"purple": true,
"red": true,
"silver": true,
"teal": true,
"white": true,
"yellow": true,
"transparent": true,
"-webkit-link": true,
"-webkit-activelink": true,
"activeborder": true,
"activecaption": true,
"appworkspace": true,
"background": true,
"buttonface": true,
"buttonhighlight": true,
"buttonshadow": true,
"buttontext": true,
"captiontext": true,
"graytext": true,
"highlight": true,
"highlighttext": true,
"inactiveborder": true,
"inactivecaption": true,
"inactivecaptiontext": true,
"infobackground": true,
"infotext": true,
"match": true,
"menutext": true,
"scrollbar": true,
"threeddarkshadow": true,
"threedface": true,
"threedhighlight": true,
"threedlightshadow": true,
"threedshadow": true,
"window": true,
"windowframe": true,
"windowtext": true,
"-webkit-focus-ring-color": true,
"currentcolor": true,
"grey": true,
"-webkit-text": true,
"repeat": true,
"repeat-x": true,
"repeat-y": true,
"no-repeat": true,
"clear": true,
"copy": true,
"source-over": true,
"source-in": true,
"source-out": true,
"source-atop": true,
"destination-over": true,
"destination-in": true,
"destination-out": true,
"destination-atop": true,
"xor": true,
"plus-darker": true,
"plus-lighter": true,
"baseline": true,
"middle": true,
"sub": true,
"super": true,
"text-top": true,
"text-bottom": true,
"top": true,
"bottom": true,
"-webkit-baseline-middle": true,
"-webkit-auto": true,
"left": true,
"right": true,
"center": true,
"justify": true,
"-webkit-left": true,
"-webkit-right": true,
"-webkit-center": true,
"outside": true,
"inside": true,
"disc": true,
"circle": true,
"square": true,
"decimal": true,
"decimal-leading-zero": true,
"lower-roman": true,
"upper-roman": true,
"lower-greek": true,
"lower-alpha": true,
"lower-latin": true,
"upper-alpha": true,
"upper-latin": true,
"hebrew": true,
"armenian": true,
"georgian": true,
"cjk-ideographic": true,
"hiragana": true,
"katakana": true,
"hiragana-iroha": true,
"katakana-iroha": true,
"inline": true,
"block": true,
"list-item": true,
"run-in": true,
"compact": true,
"inline-block": true,
"table": true,
"inline-table": true,
"table-row-group": true,
"table-header-group": true,
"table-footer-group": true,
"table-row": true,
"table-column-group": true,
"table-column": true,
"table-cell": true,
"table-caption": true,
"-webkit-box": true,
"-webkit-inline-box": true,
"-wap-marquee": true,
"auto": true,
"crosshair": true,
"default": true,
"pointer": true,
"move": true,
"vertical-text": true,
"cell": true,
"context-menu": true,
"alias": true,
"progress": true,
"no-drop": true,
"not-allowed": true,
"-webkit-zoom-in": true,
"-webkit-zoom-out": true,
"e-resize": true,
"ne-resize": true,
"nw-resize": true,
"n-resize": true,
"se-resize": true,
"sw-resize": true,
"s-resize": true,
"w-resize": true,
"ew-resize": true,
"ns-resize": true,
"nesw-resize": true,
"nwse-resize": true,
"col-resize": true,
"row-resize": true,
"text": true,
"wait": true,
"help": true,
"all-scroll": true,
"-webkit-grab": true,
"-webkit-grabbing": true,
"ltr": true,
"rtl": true,
"capitalize": true,
"uppercase": true,
"lowercase": true,
"visible": true,
"collapse": true,
"above": true,
"absolute": true,
"always": true,
"avoid": true,
"below": true,
"bidi-override": true,
"blink": true,
"both": true,
"close-quote": true,
"crop": true,
"cross": true,
"embed": true,
"fixed": true,
"hand": true,
"hide": true,
"higher": true,
"invert": true,
"landscape": true,
"level": true,
"line-through": true,
"local": true,
"loud": true,
"lower": true,
"-webkit-marquee": true,
"mix": true,
"no-close-quote": true,
"no-open-quote": true,
"nowrap": true,
"open-quote": true,
"overlay": true,
"overline": true,
"portrait": true,
"pre": true,
"pre-line": true,
"pre-wrap": true,
"relative": true,
"scroll": true,
"separate": true,
"show": true,
"static": true,
"thick": true,
"thin": true,
"underline": true,
"-webkit-nowrap": true,
"stretch": true,
"start": true,
"end": true,
"reverse": true,
"horizontal": true,
"vertical": true,
"inline-axis": true,
"block-axis": true,
"single": true,
"multiple": true,
"forwards": true,
"backwards": true,
"ahead": true,
"up": true,
"down": true,
"slow": true,
"fast": true,
"infinite": true,
"slide": true,
"alternate": true,
"read-only": true,
"read-write": true,
"read-write-plaintext-only": true,
"element": true,
"ignore": true,
"intrinsic": true,
"min-intrinsic": true,
"clip": true,
"ellipsis": true,
"discard": true,
"dot-dash": true,
"dot-dot-dash": true,
"wave": true,
"continuous": true,
"skip-white-space": true,
"break-all": true,
"break-word": true,
"space": true,
"after-white-space": true,
"checkbox": true,
"radio": true,
"push-button": true,
"square-button": true,
"button": true,
"button-bevel": true,
"default-button": true,
"list-button": true,
"listbox": true,
"listitem": true,
"media-fullscreen-button": true,
"media-mute-button": true,
"media-play-button": true,
"media-seek-back-button": true,
"media-seek-forward-button": true,
"media-rewind-button": true,
"media-return-to-realtime-button": true,
"media-slider": true,
"media-sliderthumb": true,
"media-volume-slider-container": true,
"media-volume-slider": true,
"media-volume-sliderthumb": true,
"media-controls-background": true,
"media-current-time-display": true,
"media-time-remaining-display": true,
"menulist": true,
"menulist-button": true,
"menulist-text": true,
"menulist-textfield": true,
"slider-horizontal": true,
"slider-vertical": true,
"sliderthumb-horizontal": true,
"sliderthumb-vertical": true,
"caret": true,
"searchfield": true,
"searchfield-decoration": true,
"searchfield-results-decoration": true,
"searchfield-results-button": true,
"searchfield-cancel-button": true,
"textfield": true,
"textarea": true,
"caps-lock-indicator": true,
"round": true,
"border": true,
"border-box": true,
"content": true,
"content-box": true,
"padding": true,
"padding-box": true,
"contain": true,
"cover": true,
"logical": true,
"visual": true,
"lines": true,
"running": true,
"paused": true,
"flat": true,
"preserve-3d": true,
"ease": true,
"linear": true,
"ease-in": true,
"ease-out": true,
"ease-in-out": true,
"document": true,
"reset": true,
"visiblePainted": true,
"visibleFill": true,
"visibleStroke": true,
"painted": true,
"fill": true,
"stroke": true,
"antialiased": true,
"subpixel-antialiased": true,
"optimizeSpeed": true,
"optimizeLegibility": true,
"geometricPrecision": true
};
function valueIdentAction(token) {
this.cursor += token.length;
if (token in valueKeywords) {
this.appendNonToken.call(this);
this.newLine.appendChild(this.createSpan(token, "webkit-css-keyword"));
} else
this.nonToken += token;
}
function numvalueAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-number"));
}
function declarationSemicolonAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.DeclarationProperty;
}
function urlAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-url"));
}
function stringAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-string"));
}
function colorAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-color"));
}
function importantAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-important"));
}
function atMediaAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
this.lexState = this.LexState.AtMedia;
}
function startAtMediaBlockAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
function atKeyframesAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
this.lexState = this.LexState.AtKeyframes;
}
function startAtKeyframesBlockAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
function atRuleAction(token) {
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-css-at-rule"));
this.lexState = this.LexState.AtRule;
}
function endAtRuleAction(token) {
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
function startAtRuleBlockAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.DeclarationProperty;
}
const mediaTypes = ["all", "aural", "braille", "embossed", "handheld", "print", "projection", "screen", "tty", "tv"];
function atRuleIdentAction(token) {
this.cursor += token.length;
if (mediaTypes.indexOf(token) === -1)
this.nonToken += token;
else {
this.appendNonToken.call(this);
this.newLine.appendChild(this.createSpan(token, "webkit-css-keyword"));
}
}
}
WebInspector.CSSSourceSyntaxHighlighter.prototype.__proto__ = WebInspector.SourceSyntaxHighlighter.prototype;
WebInspector.JavaScriptSourceSyntaxHighlighter = function(table, sourceFrame) {
WebInspector.SourceSyntaxHighlighter.call(this, table, sourceFrame);
this.LexState = {
Initial: 1,
DivisionAllowed: 2,
};
this.ContinueState = {
None: 0,
Comment: 1,
SingleQuoteString: 2,
DoubleQuoteString: 3,
RegExp: 4
};
this.nonToken = "";
this.cursor = 0;
this.lineIndex = -1;
this.lineCode = "";
this.newLine = null;
this.lexState = this.LexState.Initial;
this.continueState = this.ContinueState.None;
this.rules = [{
pattern: /^(?:\/\/.*)/,
action: singleLineCommentAction
}, {
pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*\*+\/)/,
action: multiLineSingleLineCommentAction
}, {
pattern: /^(?:\/\*(?:[^\*]|\*[^\/])*)/,
action: multiLineCommentStartAction
}, {
pattern: /^(?:(?:[^\*]|\*[^\/])*\*+\/)/,
action: multiLineCommentEndAction,
continueStateCondition: this.ContinueState.Comment
}, {
pattern: /^.*/,
action: multiLineCommentMiddleAction,
continueStateCondition: this.ContinueState.Comment
}, {
pattern: /^(?:(?:0|[1-9]\d*)\.\d+?(?:[eE](?:\d+|\+\d+|-\d+))?|\.\d+(?:[eE](?:\d+|\+\d+|-\d+))?|(?:0|[1-9]\d*)(?:[eE](?:\d+|\+\d+|-\d+))?|0x[0-9a-fA-F]+|0X[0-9a-fA-F]+)/,
action: numericLiteralAction
}, {
pattern: /^(?:"(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*"|'(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*')/,
action: stringLiteralAction
}, {
pattern: /^(?:'(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
action: singleQuoteStringStartAction
}, {
pattern: /^(?:(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*')/,
action: singleQuoteStringEndAction,
continueStateCondition: this.ContinueState.SingleQuoteString
}, {
pattern: /^(?:(?:[^'\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
action: singleQuoteStringMiddleAction,
continueStateCondition: this.ContinueState.SingleQuoteString
}, {
pattern: /^(?:"(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
action: doubleQuoteStringStartAction
}, {
pattern: /^(?:(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*")/,
action: doubleQuoteStringEndAction,
continueStateCondition: this.ContinueState.DoubleQuoteString
}, {
pattern: /^(?:(?:[^"\\]|\\(?:['"\bfnrtv]|[^'"\bfnrtv0-9xu]|0|x[0-9a-fA-F][0-9a-fA-F]|(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))*)\\$/,
action: doubleQuoteStringMiddleAction,
continueStateCondition: this.ContinueState.DoubleQuoteString
}, {
pattern: /^(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
action: identOrKeywordAction
}, {
pattern: /^\)/,
action: rightParenAction,
dontAppendNonToken: true
}, {
pattern: /^(?:<=|>=|===|==|!=|!==|\+\+|\-\-|<<|>>|>>>|&&|\|\||\+=|\-=|\*=|%=|<<=|>>=|>>>=|&=|\|=|^=|[{}\(\[\]\.;,<>\+\-\*%&\|\^!~\?:=])/,
action: punctuatorAction,
dontAppendNonToken: true
}, {
pattern: /^(?:\/=?)/,
action: divPunctuatorAction,
lexStateCondition: this.LexState.DivisionAllowed,
dontAppendNonToken: true
}, {
pattern: /^(?:\/(?:(?:\\.)|[^\\*\/])(?:(?:\\.)|[^\\/])*\/(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
action: regExpLiteralAction
}, {
pattern: /^(?:\/(?:(?:\\.)|[^\\*\/])(?:(?:\\.)|[^\\/])*)\\$/,
action: regExpStartAction
}, {
pattern: /^(?:(?:(?:\\.)|[^\\/])*\/(?:(?:[a-zA-Z]|[$_]|\\(?:u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))|[0-9])*)/,
action: regExpEndAction,
continueStateCondition: this.ContinueState.RegExp
}, {
pattern: /^(?:(?:(?:\\.)|[^\\/])*)\\$/,
action: regExpMiddleAction,
continueStateCondition: this.ContinueState.RegExp
}];
function singleLineCommentAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
}
function multiLineSingleLineCommentAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
}
function multiLineCommentStartAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
this.continueState = this.ContinueState.Comment;
}
function multiLineCommentEndAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
this.continueState = this.ContinueState.None;
}
function multiLineCommentMiddleAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-comment"));
}
function numericLiteralAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-number"));
this.lexState = this.LexState.DivisionAllowed;
}
function stringLiteralAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
this.lexState = this.LexState.Initial;
}
function singleQuoteStringStartAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
this.continueState = this.ContinueState.SingleQuoteString;
}
function singleQuoteStringEndAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
this.continueState = this.ContinueState.None;
}
function singleQuoteStringMiddleAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
}
function doubleQuoteStringStartAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
this.continueState = this.ContinueState.DoubleQuoteString;
}
function doubleQuoteStringEndAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
this.continueState = this.ContinueState.None;
}
function doubleQuoteStringMiddleAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-string"));
}
function regExpLiteralAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
this.lexState = this.LexState.Initial;
}
function regExpStartAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
this.continueState = this.ContinueState.RegExp;
}
function regExpEndAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
this.continueState = this.ContinueState.None;
}
function regExpMiddleAction(token)
{
this.cursor += token.length;
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-regexp"));
}
const keywords = {
"null": true,
"true": true,
"false": true,
"break": true,
"case": true,
"catch": true,
"const": true,
"default": true,
"finally": true,
"for": true,
"instanceof": true,
"new": true,
"var": true,
"continue": true,
"function": true,
"return": true,
"void": true,
"delete": true,
"if": true,
"this": true,
"do": true,
"while": true,
"else": true,
"in": true,
"switch": true,
"throw": true,
"try": true,
"typeof": true,
"debugger": true,
"class": true,
"enum": true,
"export": true,
"extends": true,
"import": true,
"super": true,
"get": true,
"set": true
};
function identOrKeywordAction(token)
{
this.cursor += token.length;
if (token in keywords) {
this.newLine.appendChild(this.createSpan(token, "webkit-javascript-keyword"));
this.lexState = this.LexState.Initial;
} else {
var identElement = this.createSpan(token, "webkit-javascript-ident");
identElement.addEventListener("mouseover", showDatatip, false);
this.newLine.appendChild(identElement);
this.lexState = this.LexState.DivisionAllowed;
}
}
function showDatatip(event) {
if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
return;
var node = event.target;
var parts = [node.textContent];
while (node.previousSibling && node.previousSibling.textContent === ".") {
node = node.previousSibling.previousSibling;
if (!node || !node.hasStyleClass("webkit-javascript-ident"))
break;
parts.unshift(node.textContent);
}
var expression = parts.join(".");
WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", callback);
function callback(result, exception)
{
if (exception)
return;
event.target.setAttribute("title", result.description);
event.target.addEventListener("mouseout", onmouseout, false);
function onmouseout(event)
{
event.target.removeAttribute("title");
event.target.removeEventListener("mouseout", onmouseout, false);
}
}
}
function divPunctuatorAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
function rightParenAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.DivisionAllowed;
}
function punctuatorAction(token)
{
this.cursor += token.length;
this.nonToken += token;
this.lexState = this.LexState.Initial;
}
}
WebInspector.JavaScriptSourceSyntaxHighlighter.prototype.__proto__ = WebInspector.SourceSyntaxHighlighter.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)
{
// Set the sourceFrame first since WebInspector.ResourceView will set headersVisible
// and our override of headersVisible needs the sourceFrame.
this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));
WebInspector.ResourceView.call(this, resource);
resource.addEventListener("finished", this._resourceLoadingFinished, this);
this.element.addStyleClass("source");
this._frameNeedsSetup = true;
this.contentElement.appendChild(this.sourceFrame.element);
var gutterElement = document.createElement("div");
gutterElement.className = "webkit-line-gutter-backdrop";
this.element.appendChild(gutterElement);
}
WebInspector.SourceView.prototype = {
set headersVisible(x)
{
if (x === this._headersVisible)
return;
var superSetter = WebInspector.ResourceView.prototype.__lookupSetter__("headersVisible");
if (superSetter)
superSetter.call(this, x);
this.sourceFrame.autoSizesToFitContentHeight = x;
},
show: function(parentElement)
{
WebInspector.ResourceView.prototype.show.call(this, parentElement);
this.setupSourceFrameIfNeeded();
},
hide: function()
{
WebInspector.View.prototype.hide.call(this);
this._currentSearchResultIndex = -1;
},
resize: function()
{
if (this.sourceFrame.autoSizesToFitContentHeight)
this.sourceFrame.sizeToFitContentHeight();
},
detach: function()
{
WebInspector.ResourceView.prototype.detach.call(this);
// FIXME: We need to mark the frame for setup on detach because the frame DOM is cleared
// when it is removed from the document. Is this a bug?
this._frameNeedsSetup = true;
this._sourceFrameSetup = false;
},
setupSourceFrameIfNeeded: function()
{
if (!this._frameNeedsSetup)
return;
this.attach();
delete this._frameNeedsSetup;
this.sourceFrame.addEventListener("content loaded", this._contentLoaded, this);
InspectorFrontendHost.addResourceSourceToFrame(this.resource.identifier, this.sourceFrame.element);
},
_contentLoaded: function()
{
delete this._frameNeedsSetup;
this.sourceFrame.removeEventListener("content loaded", this._contentLoaded, this);
if (this.resource.type === WebInspector.Resource.Type.Script
|| this.resource.mimeType === "application/json"
|| this.resource.mimeType === "application/javascript"
|| /\.js(on)?$/.test(this.resource.lastPathComponent) ) {
this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
this.sourceFrame.syntaxHighlightJavascript();
} else if (this.resource.type === WebInspector.Resource.Type.Stylesheet
|| this.resource.mimeType === "text/css"
|| /\.css$/.test(this.resource.lastPathComponent) ) {
this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
this.sourceFrame.syntaxHighlightCSS();
} else
this._sourceFrameSetupFinished();
},
_resourceLoadingFinished: function(event)
{
this._frameNeedsSetup = true;
this._sourceFrameSetup = false;
if (this.visible)
this.setupSourceFrameIfNeeded();
this.resource.removeEventListener("finished", this._resourceLoadingFinished, this);
},
_addBreakpoint: 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;
}
}
if (WebInspector.panels.scripts) {
var breakpoint = new WebInspector.Breakpoint(this.resource.url, line, sourceID);
WebInspector.panels.scripts.addBreakpoint(breakpoint);
}
},
// 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 = [];
delete this._delayedFindSearchMatches;
},
performSearch: function(query, finishedCallback)
{
// Call searchCanceled since it will reset everything we need before doing a new search.
this.searchCanceled();
var lineQueryRegex = /(^|\s)(?:#|line:\s*)(\d+)(\s|$)/i;
var lineQueryMatch = query.match(lineQueryRegex);
if (lineQueryMatch) {
var lineToSearch = parseInt(lineQueryMatch[2]);
// If there was a space before and after the line query part, replace with a space.
// Otherwise replace with an empty string to eat the prefix or postfix space.
var lineQueryReplacement = (lineQueryMatch[1] && lineQueryMatch[3] ? " " : "");
var filterlessQuery = query.replace(lineQueryRegex, lineQueryReplacement);
}
this._searchFinishedCallback = finishedCallback;
function findSearchMatches(query, finishedCallback)
{
if (isNaN(lineToSearch)) {
// Search the whole document since there was no line to search.
this._searchResults = (InspectorFrontendHost.search(this.sourceFrame.element.contentDocument, query) || []);
} else {
var sourceRow = this.sourceFrame.sourceRow(lineToSearch);
if (sourceRow) {
if (filterlessQuery) {
// There is still a query string, so search for that string in the line.
this._searchResults = (InspectorFrontendHost.search(sourceRow, filterlessQuery) || []);
} else {
// Match the whole line, since there was no remaining query string to match.
var rowRange = this.sourceFrame.element.contentDocument.createRange();
rowRange.selectNodeContents(sourceRow);
this._searchResults = [rowRange];
}
}
// Attempt to search for the whole query, just incase it matches a color like "#333".
var wholeQueryMatches = InspectorFrontendHost.search(this.sourceFrame.element.contentDocument, query);
if (wholeQueryMatches)
this._searchResults = this._searchResults.concat(wholeQueryMatches);
}
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);
},
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;
var selection = this.sourceFrame.element.contentWindow.getSelection();
selection.removeAllRanges();
selection.addRange(foundRange);
if (foundRange.startContainer.scrollIntoViewIfNeeded)
foundRange.startContainer.scrollIntoViewIfNeeded(true);
else if (foundRange.startContainer.parentNode)
foundRange.startContainer.parentNode.scrollIntoViewIfNeeded(true);
},
_sourceFrameSetupFinished: function()
{
this._sourceFrameSetup = true;
if (this._delayedFindSearchMatches) {
this._delayedFindSearchMatches();
delete this._delayedFindSearchMatches;
}
},
_syntaxHighlightingComplete: function(event)
{
this._sourceFrameSetupFinished();
this.sourceFrame.removeEventListener("syntax highlighting complete", null, this);
}
}
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");
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.fontPreviewElement.className = "preview";
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.updateFontPreviewSize();
}
WebInspector.FontView.prototype = {
show: function(parentElement)
{
WebInspector.ResourceView.prototype.show.call(this, parentElement);
this.updateFontPreviewSize();
},
resize: function()
{
this.updateFontPreviewSize();
},
updateFontPreviewSize: function ()
{
if (!this.fontPreviewElement || !this.visible)
return;
this.fontPreviewElement.removeStyleClass("preview");
var measureFontSize = 50;
this.fontPreviewElement.style.setProperty("position", "absolute", null);
this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
this.fontPreviewElement.style.removeProperty("height");
var height = this.fontPreviewElement.offsetHeight;
var width = this.fontPreviewElement.offsetWidth;
var containerWidth = this.contentElement.offsetWidth;
// Subtract some padding. This should match the padding in the CSS plus room for the scrollbar.
containerWidth -= 40;
if (!height || !width || !containerWidth) {
this.fontPreviewElement.style.removeProperty("font-size");
this.fontPreviewElement.style.removeProperty("position");
this.fontPreviewElement.addStyleClass("preview");
return;
}
var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;
var realLineHeight = Math.floor(height / lineCount);
var fontSizeLineRatio = measureFontSize / realLineHeight;
var widthRatio = containerWidth / width;
var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1;
this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
this.fontPreviewElement.style.removeProperty("position");
this.fontPreviewElement.addStyleClass("preview");
}
}
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");
var container = document.createElement("div");
container.className = "image";
this.contentElement.appendChild(container);
this.imagePreviewElement = document.createElement("img");
this.imagePreviewElement.addStyleClass("resource-image-view");
this.imagePreviewElement.setAttribute("src", this.resource.url);
container.appendChild(this.imagePreviewElement);
container = document.createElement("div");
container.className = "info";
this.contentElement.appendChild(container);
var imageNameElement = document.createElement("h1");
imageNameElement.className = "title";
imageNameElement.textContent = this.resource.displayName;
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.contentLength, WebInspector.UIString.bind(WebInspector)) },
{ 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;
container.appendChild(infoListElement);
}
WebInspector.ImageView.prototype = {
}
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];
},
update: function()
{
this.database.executeSql("SELECT * FROM " + this.tableName, this._queryFinished.bind(this), this._queryError.bind(this));
},
_queryFinished: function(result)
{
this.element.removeChildren();
var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
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);
},
_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.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.handleKeyEvent = this._promptKeyDown.bind(this);
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;
}
this.prompt.handleKeyEvent(event);
},
_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, result)
{
var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
if (!dataGrid)
return;
dataGrid.element.addStyleClass("inline");
this._appendQueryResult(query, dataGrid.element);
if (query.match(/^create /i) || query.match(/^drop table /i))
WebInspector.panels.storage.updateDatabaseTables(this.database);
},
_queryError: function(query, error)
{
if (error.code == 1)
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;
this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));
this.element.appendChild(this.sourceFrame.element);
}
WebInspector.ScriptView.prototype = {
show: function(parentElement)
{
WebInspector.View.prototype.show.call(this, parentElement);
this.setupSourceFrameIfNeeded();
},
hide: function()
{
WebInspector.View.prototype.hide.call(this);
this._currentSearchResultIndex = -1;
},
setupSourceFrameIfNeeded: function()
{
if (!this._frameNeedsSetup)
return;
this.attach();
if (!InspectorFrontendHost.addSourceToFrame("text/javascript", this.script.source, this.sourceFrame.element))
return;
delete this._frameNeedsSetup;
this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
this.sourceFrame.syntaxHighlightJavascript();
},
attach: function()
{
if (!this.element.parentNode)
document.getElementById("script-resource-views").appendChild(this.element);
},
_addBreakpoint: function(line)
{
var breakpoint = new WebInspector.Breakpoint(this.script.sourceURL, line, this.script.sourceID);
WebInspector.panels.scripts.addBreakpoint(breakpoint);
},
// The follow methods are pulled from SourceView, since they are
// generic and work with ScriptView just fine.
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,
_sourceFrameSetupFinished: WebInspector.SourceView.prototype._sourceFrameSetupFinished,
_syntaxHighlightingComplete: WebInspector.SourceView.prototype._syntaxHighlightingComplete
}
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.bind(WebInspector), !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 fileName = WebInspector.displayNameForURL(this.profileNode.url);
var urlElement = document.createElement("a");
urlElement.className = "profile-node-file webkit-html-resource-link";
urlElement.href = this.profileNode.url;
urlElement.lineNumber = this.profileNode.lineNumber;
if (this.profileNode.lineNumber > 0)
urlElement.textContent = fileName + ":" + this.profileNode.lineNumber;
else
urlElement.textContent = fileName;
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 = profile;
self._assignParentsInProfile();
self.profileDataGridTree = self.bottomUpProfileDataGridTree;
self.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator("selfTime", false));
self.refresh();
self._updatePercentButton();
}
var callId = WebInspector.Callback.wrap(profileCallback);
InspectorBackend.getProfile(callId, this.profile.uid);
}
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.trimWhitespace();
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();
},
setRecordingProfile: function(isProfiling)
{
this._recording = isProfiling;
},
createSidebarTreeElementForProfile: function(profile)
{
return new WebInspector.ProfileSidebarTreeElement(profile);
},
createView: function(profile)
{
return new WebInspector.CPUProfileView(profile);
}
}
WebInspector.CPUProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
/* DOMAgent.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.
*/
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;
}
}
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;
var self = this;
var callback = function()
{
self._nodeValue = value;
self.textContent = value;
};
this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
},
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);
},
_setAttributesPayload: function(attrs)
{
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)
// First node
this.children = [ 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);
},
_setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
{
this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);
for (var name in styleAttributes) {
if (this._attributesMap[name])
this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
}
this._matchedCSSRules = [];
for (var i = 0; i < matchedCSSRules.length; i++)
this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
},
_clearStyles: function()
{
this.computedStyle = null;
this.style = null;
for (var name in this._attributesMap)
this._attributesMap[name].style = null;
this._matchedCSSRules = null;
}
}
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()
{
},
getComputedStyle: function(node)
{
return node._computedStyle;
},
getMatchedCSSRules: function(node, pseudoElement, authorOnly)
{
return node._matchedCSSRules;
}
}
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);
}
var callId = WebInspector.Callback.wrap(mycallback);
InspectorBackend.getChildNodes(callId, parent.id);
},
setAttributeAsync: function(node, name, value, callback)
{
var mycallback = this._didApplyDomChange.bind(this, node, callback);
InspectorBackend.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value);
},
removeAttributeAsync: function(node, name, callback)
{
var mycallback = this._didApplyDomChange.bind(this, node, callback);
InspectorBackend.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name);
},
setTextNodeValueAsync: function(node, text, callback)
{
var mycallback = this._didApplyDomChange.bind(this, node, callback);
InspectorBackend.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text);
},
_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);
},
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);
} 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];
}
}
WebInspector.Cookies = {}
WebInspector.Cookies.getCookiesAsync = function(callback, cookieDomain)
{
function mycallback(cookies, cookiesString) {
if (cookiesString)
callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
else
callback(cookies, true);
}
var callId = WebInspector.Callback.wrap(mycallback);
InspectorBackend.getCookies(callId, cookieDomain);
}
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.EventListeners = {}
WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
{
if (!node)
return;
var callId = WebInspector.Callback.wrap(callback);
InspectorBackend.getEventListenersForNode(callId, node.id);
}
WebInspector.CSSStyleDeclaration = function(payload)
{
this.id = payload.id;
this.width = payload.width;
this.height = payload.height;
this.__disabledProperties = payload.__disabledProperties;
this.__disabledPropertyValues = payload.__disabledPropertyValues;
this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
this.uniqueStyleProperties = payload.uniqueStyleProperties;
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.
for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
var name = this.uniqueStyleProperties[i];
var property = this._propertyMap[name];
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;
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.getPropertyValue(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.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.didGetCookies = WebInspector.Callback.processCallback;
WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback;
/* 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 InjectedScript = {};
// Called from within InspectorController on the 'inspected page' side.
InjectedScript.reset = function()
{
InjectedScript._styles = {};
InjectedScript._styleRules = {};
InjectedScript._lastStyleId = 0;
InjectedScript._lastStyleRuleId = 0;
InjectedScript._searchResults = [];
InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults";
}
InjectedScript.reset();
InjectedScript.dispatch = function(methodName, args, callId)
{
var argsArray = JSON.parse(args);
if (callId)
argsArray.splice(0, 0, callId); // Methods that run asynchronously have a call back id parameter.
var result = InjectedScript[methodName].apply(InjectedScript, argsArray);
if (typeof result === "undefined") {
InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
result = null;
}
return JSON.stringify(result);
}
InjectedScript.getStyles = function(nodeId, authorOnly)
{
var node = InjectedScript._nodeForId(nodeId);
if (!node)
return false;
var defaultView = node.ownerDocument.defaultView;
var matchedRules = defaultView.getMatchedCSSRules(node, "", authorOnly);
var matchedCSSRules = [];
for (var i = 0; matchedRules && i < matchedRules.length; ++i)
matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i]));
var styleAttributes = {};
var attributes = node.attributes;
for (var i = 0; attributes && i < attributes.length; ++i) {
if (attributes[i].style)
styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true);
}
var result = {};
result.inlineStyle = InjectedScript._serializeStyle(node.style, true);
result.computedStyle = InjectedScript._serializeStyle(defaultView.getComputedStyle(node));
result.matchedCSSRules = matchedCSSRules;
result.styleAttributes = styleAttributes;
return result;
}
InjectedScript.getComputedStyle = function(nodeId)
{
var node = InjectedScript._nodeForId(nodeId);
if (!node)
return false;
return InjectedScript._serializeStyle(node.ownerDocument.defaultView.getComputedStyle(node));
}
InjectedScript.getInlineStyle = function(nodeId)
{
var node = InjectedScript._nodeForId(nodeId);
if (!node)
return false;
return InjectedScript._serializeStyle(node.style, true);
}
InjectedScript.applyStyleText = function(styleId, styleText, propertyName)
{
var style = InjectedScript._styles[styleId];
if (!style)
return false;
var styleTextLength = styleText.length;
// Create a new element to parse the user input CSS.
var parseElement = document.createElement("span");
parseElement.setAttribute("style", styleText);
var tempStyle = parseElement.style;
if (tempStyle.length || !styleTextLength) {
// The input was parsable or the user deleted everything, so remove the
// original property from the real style declaration. If this represents
// a shorthand remove all the longhand properties.
if (style.getPropertyShorthand(propertyName)) {
var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
for (var i = 0; i < longhandProperties.length; ++i)
style.removeProperty(longhandProperties[i]);
} else
style.removeProperty(propertyName);
}
// Notify caller that the property was successfully deleted.
if (!styleTextLength)
return [null, [propertyName]];
if (!tempStyle.length)
return false;
// Iterate of the properties on the test element's style declaration and
// add them to the real style declaration. We take care to move shorthands.
var foundShorthands = {};
var changedProperties = [];
var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle);
for (var i = 0; i < uniqueProperties.length; ++i) {
var name = uniqueProperties[i];
var shorthand = tempStyle.getPropertyShorthand(name);
if (shorthand && shorthand in foundShorthands)
continue;
if (shorthand) {
var value = InjectedScript._getShorthandValue(tempStyle, shorthand);
var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand);
foundShorthands[shorthand] = true;
} else {
var value = tempStyle.getPropertyValue(name);
var priority = tempStyle.getPropertyPriority(name);
}
// Set the property on the real style declaration.
style.setProperty((shorthand || name), value, priority);
changedProperties.push(shorthand || name);
}
return [InjectedScript._serializeStyle(style, true), changedProperties];
}
InjectedScript.setStyleText = function(style, cssText)
{
style.cssText = cssText;
return true;
}
InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
{
var style = InjectedScript._styles[styleId];
if (!style)
return false;
if (disabled) {
if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) {
var inspectedWindow = InjectedScript._window();
style.__disabledProperties = new inspectedWindow.Object;
style.__disabledPropertyValues = new inspectedWindow.Object;
style.__disabledPropertyPriorities = new inspectedWindow.Object;
}
style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName);
style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName);
if (style.getPropertyShorthand(propertyName)) {
var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
for (var i = 0; i < longhandProperties.length; ++i) {
style.__disabledProperties[longhandProperties[i]] = true;
style.removeProperty(longhandProperties[i]);
}
} else {
style.__disabledProperties[propertyName] = true;
style.removeProperty(propertyName);
}
} else if (style.__disabledProperties && style.__disabledProperties[propertyName]) {
var value = style.__disabledPropertyValues[propertyName];
var priority = style.__disabledPropertyPriorities[propertyName];
style.setProperty(propertyName, value, priority);
delete style.__disabledProperties[propertyName];
delete style.__disabledPropertyValues[propertyName];
delete style.__disabledPropertyPriorities[propertyName];
}
return InjectedScript._serializeStyle(style, true);
}
InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId)
{
var rule = InjectedScript._styleRules[ruleId];
if (!rule)
return false;
var selectedNode = InjectedScript._nodeForId(selectedNodeId);
try {
var stylesheet = rule.parentStyleSheet;
stylesheet.addRule(newContent);
var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
newRule.style.cssText = rule.style.cssText;
var parentRules = stylesheet.cssRules;
for (var i = 0; i < parentRules.length; ++i) {
if (parentRules[i] === rule) {
rule.parentStyleSheet.removeRule(i);
break;
}
}
return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)];
} catch(e) {
// Report invalid syntax.
return false;
}
}
InjectedScript.addStyleSelector = function(newContent, selectedNodeId)
{
var selectedNode = InjectedScript._nodeForId(selectedNodeId);
if (!selectedNode)
return false;
var ownerDocument = selectedNode.ownerDocument;
var stylesheet = ownerDocument.__stylesheet;
if (!stylesheet) {
var head = ownerDocument.head;
var styleElement = ownerDocument.createElement("style");
styleElement.type = "text/css";
head.appendChild(styleElement);
stylesheet = ownerDocument.styleSheets[ownerDocument.styleSheets.length - 1];
ownerDocument.__stylesheet = stylesheet;
}
try {
stylesheet.addRule(newContent);
} catch (e) {
// Invalid Syntax for a Selector
return false;
}
var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
rule.__isViaInspector = true;
return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ];
}
InjectedScript._doesSelectorAffectNode = function(selectorText, node)
{
if (!node)
return false;
var nodes = node.ownerDocument.querySelectorAll(selectorText);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] === node) {
return true;
}
}
return false;
}
InjectedScript.setStyleProperty = function(styleId, name, value)
{
var style = InjectedScript._styles[styleId];
if (!style)
return false;
style.setProperty(name, value, "");
return true;
}
InjectedScript._serializeRule = function(rule)
{
var parentStyleSheet = rule.parentStyleSheet;
var ruleValue = {};
ruleValue.selectorText = rule.selectorText;
if (parentStyleSheet) {
ruleValue.parentStyleSheet = {};
ruleValue.parentStyleSheet.href = parentStyleSheet.href;
}
ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
ruleValue.isViaInspector = !!rule.__isViaInspector;
// Bind editable scripts only.
var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);
if (doBind) {
if (!rule.id) {
rule.id = InjectedScript._lastStyleRuleId++;
InjectedScript._styleRules[rule.id] = rule;
}
ruleValue.id = rule.id;
}
return ruleValue;
}
InjectedScript._serializeStyle = function(style, doBind)
{
var result = {};
result.width = style.width;
result.height = style.height;
result.__disabledProperties = style.__disabledProperties;
result.__disabledPropertyValues = style.__disabledPropertyValues;
result.__disabledPropertyPriorities = style.__disabledPropertyPriorities;
result.properties = [];
result.shorthandValues = {};
var foundShorthands = {};
for (var i = 0; i < style.length; ++i) {
var property = {};
var name = style[i];
property.name = name;
property.priority = style.getPropertyPriority(name);
property.implicit = style.isPropertyImplicit(name);
var shorthand = style.getPropertyShorthand(name);
property.shorthand = shorthand;
if (shorthand && !(shorthand in foundShorthands)) {
foundShorthands[shorthand] = true;
result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand);
}
property.value = style.getPropertyValue(name);
result.properties.push(property);
}
result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);
if (doBind) {
if (!style.id) {
style.id = InjectedScript._lastStyleId++;
InjectedScript._styles[style.id] = style;
}
result.id = style.id;
}
return result;
}
InjectedScript._getUniqueStyleProperties = function(style)
{
var properties = [];
var foundProperties = {};
for (var i = 0; i < style.length; ++i) {
var property = style[i];
if (property in foundProperties)
continue;
foundProperties[property] = true;
properties.push(property);
}
return properties;
}
InjectedScript._getLonghandProperties = function(style, shorthandProperty)
{
var properties = [];
var foundProperties = {};
for (var i = 0; i < style.length; ++i) {
var individualProperty = style[i];
if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
continue;
foundProperties[individualProperty] = true;
properties.push(individualProperty);
}
return properties;
}
InjectedScript._getShorthandValue = function(style, shorthandProperty)
{
var value = style.getPropertyValue(shorthandProperty);
if (!value) {
// Some shorthands (like border) return a null value, so compute a shorthand value.
// FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
var foundProperties = {};
for (var i = 0; i < style.length; ++i) {
var individualProperty = style[i];
if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
continue;
var individualValue = style.getPropertyValue(individualProperty);
if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
continue;
foundProperties[individualProperty] = true;
if (!value)
value = "";
else if (value.length)
value += " ";
value += individualValue;
}
}
return value;
}
InjectedScript._getShorthandPriority = function(style, shorthandProperty)
{
var priority = style.getPropertyPriority(shorthandProperty);
if (!priority) {
for (var i = 0; i < style.length; ++i) {
var individualProperty = style[i];
if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
continue;
priority = style.getPropertyPriority(individualProperty);
break;
}
}
return priority;
}
InjectedScript.getPrototypes = function(nodeId)
{
var node = InjectedScript._nodeForId(nodeId);
if (!node)
return false;
var result = [];
for (var prototype = node; prototype; prototype = prototype.__proto__) {
var title = Object.describe(prototype, true);
if (title.match(/Prototype$/)) {
title = title.replace(/Prototype$/, "");
}
result.push(title);
}
return result;
}
InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
{
var object = InjectedScript._resolveObject(objectProxy);
if (!object)
return false;
var properties = [];
// Go over properties, prepare results.
for (var propertyName in object) {
if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
continue;
var property = {};
property.name = propertyName;
property.parentObjectProxy = objectProxy;
var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
if (!property.isGetter) {
var childObject = object[propertyName];
var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, true);
childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : [];
childObjectProxy.path.push(propertyName);
childObjectProxy.protoDepth = objectProxy.protoDepth || 0;
property.value = childObjectProxy;
} else {
// FIXME: this should show something like "getter" (bug 16734).
property.value = { description: "\u2014" }; // em dash
property.isGetter = true;
}
properties.push(property);
}
return properties;
}
InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression)
{
var object = InjectedScript._resolveObject(objectProxy);
if (!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 = InjectedScript._window().eval("(" + expression + ")");
// Store the result in the property.
object[propertyName] = result;
return true;
} catch(e) {
try {
var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\"");
object[propertyName] = result;
return true;
} catch(e) {
return false;
}
}
}
InjectedScript.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 = InjectedScript._callFrameForId(callFrameId);
if (!callFrame)
return props;
if (expression)
expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression);
else {
// Evaluate into properties in scope of the selected call frame.
var scopeChain = callFrame.scopeChain;
for (var i = 0; i < scopeChain.length; ++i) {
var scopeObject = scopeChain[i];
try {
for (var propertyName in scopeObject)
props[propertyName] = true;
} catch (e) {
}
}
}
} else {
if (!expression)
expression = "this";
expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression);
}
if (expressionResult)
for (var prop in expressionResult)
props[prop] = true;
if (includeInspectorCommandLineAPI)
for (var prop in InjectedScript._window().console._inspectorCommandLineAPI)
if (prop.charAt(0) !== '_')
props[prop] = true;
} catch(e) {
}
return props;
}
InjectedScript.evaluate = function(expression, objectGroup)
{
return InjectedScript._evaluateAndWrap(InjectedScript._window().eval, InjectedScript._window(), expression, objectGroup);
}
InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup)
{
var result = {};
try {
result.value = InjectedScriptHost.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression), objectGroup);
// Handle error that might have happened while describing result.
if (result.value.errorText) {
result.value = result.value.errorText;
result.isException = true;
}
} catch (e) {
result.value = e.toString();
result.isException = true;
}
return result;
}
InjectedScript._evaluateOn = function(evalFunction, object, expression)
{
InjectedScript._ensureCommandLineAPIInstalled(evalFunction, object);
// 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.
expression = "with (window.console._inspectorCommandLineAPI) { with (window) {\n" + expression + "\n} }";
var value = evalFunction.call(object, expression);
// When evaluating on call frame error is not thrown, but returned as a value.
if (Object.type(value) === "error")
throw value.toString();
return value;
}
InjectedScript.addInspectedNode = function(nodeId)
{
var node = InjectedScript._nodeForId(nodeId);
if (!node)
return false;
InjectedScript._ensureCommandLineAPIInstalled(InjectedScript._window().eval, InjectedScript._window());
var inspectedNodes = InjectedScript._window().console._inspectorCommandLineAPI._inspectedNodes;
inspectedNodes.unshift(node);
if (inspectedNodes.length >= 5)
inspectedNodes.pop();
return true;
}
InjectedScript.performSearch = function(whitespaceTrimmedQuery)
{
var tagNameQuery = whitespaceTrimmedQuery;
var attributeNameQuery = whitespaceTrimmedQuery;
var startTagFound = (tagNameQuery.indexOf("<") === 0);
var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));
if (startTagFound || endTagFound) {
var tagNameQueryLength = tagNameQuery.length;
tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
}
// Check the tagNameQuery is it is a possibly valid tag name.
if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
tagNameQuery = null;
// Check the attributeNameQuery is it is a possibly valid tag name.
if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
attributeNameQuery = null;
const escapedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null);
const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
function addNodesToResults(nodes, length, getItem)
{
if (!length)
return;
var nodeIds = [];
for (var i = 0; i < length; ++i) {
var node = getItem.call(nodes, i);
// Skip this node if it already has the property.
if (searchResultsProperty in node)
continue;
if (!InjectedScript._searchResults.length) {
InjectedScript._currentSearchResultIndex = 0;
}
node[searchResultsProperty] = true;
InjectedScript._searchResults.push(node);
var nodeId = InjectedScriptHost.pushNodePathToFrontend(node, false);
nodeIds.push(nodeId);
}
InjectedScriptHost.addNodesToSearchResult(nodeIds.join(","));
}
function matchExactItems(doc)
{
matchExactId.call(this, doc);
matchExactClassNames.call(this, doc);
matchExactTagNames.call(this, doc);
matchExactAttributeNames.call(this, doc);
}
function matchExactId(doc)
{
const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
}
function matchExactClassNames(doc)
{
const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
addNodesToResults.call(this, result, result.length, result.item);
}
function matchExactTagNames(doc)
{
if (!tagNameQuery)
return;
const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
addNodesToResults.call(this, result, result.length, result.item);
}
function matchExactAttributeNames(doc)
{
if (!attributeNameQuery)
return;
const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
addNodesToResults.call(this, result, result.length, result.item);
}
function matchPartialTagNames(doc)
{
if (!tagNameQuery)
return;
const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function matchStartOfTagNames(doc)
{
if (!tagNameQuery)
return;
const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function matchPartialTagNamesAndAttributeValues(doc)
{
if (!tagNameQuery) {
matchPartialAttributeValues.call(this, doc);
return;
}
const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function matchPartialAttributeValues(doc)
{
const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function matchStyleSelector(doc)
{
const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
addNodesToResults.call(this, result, result.length, result.item);
}
function matchPlainText(doc)
{
const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function matchXPathQuery(doc)
{
const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
}
function finishedSearching()
{
// Remove the searchResultsProperty now that the search is finished.
for (var i = 0; i < InjectedScript._searchResults.length; ++i)
delete InjectedScript._searchResults[i][searchResultsProperty];
}
const mainFrameDocument = InjectedScript._window().document;
const searchDocuments = [mainFrameDocument];
var searchFunctions;
if (tagNameQuery && startTagFound && endTagFound)
searchFunctions = [matchExactTagNames, matchPlainText];
else if (tagNameQuery && startTagFound)
searchFunctions = [matchStartOfTagNames, matchPlainText];
else if (tagNameQuery && endTagFound) {
// FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
// This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
searchFunctions = [matchPartialTagNames, matchPlainText];
} else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
// These queries will match every node. Matching everything isn't useful and can be slow for large pages,
// so limit the search functions list to plain text and attribute matching.
searchFunctions = [matchPartialAttributeValues, matchPlainText];
} else
searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];
// Find all frames, iframes and object elements to search their documents.
const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll;
const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");
for (var i = 0; i < subdocumentResult.length; ++i) {
var element = subdocumentResult.item(i);
if (element.contentDocument)
searchDocuments.push(element.contentDocument);
}
const panel = InjectedScript;
var documentIndex = 0;
var searchFunctionIndex = 0;
var chunkIntervalIdentifier = null;
// Split up the work into chunks so we don't block the UI thread while processing.
function processChunk()
{
var searchDocument = searchDocuments[documentIndex];
var searchFunction = searchFunctions[searchFunctionIndex];
if (++searchFunctionIndex > searchFunctions.length) {
searchFunction = searchFunctions[0];
searchFunctionIndex = 0;
if (++documentIndex > searchDocuments.length) {
if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
delete panel._currentSearchChunkIntervalIdentifier;
clearInterval(chunkIntervalIdentifier);
finishedSearching.call(panel);
return;
}
searchDocument = searchDocuments[documentIndex];
}
if (!searchDocument || !searchFunction)
return;
try {
searchFunction.call(panel, searchDocument);
} catch(err) {
// ignore any exceptions. the query might be malformed, but we allow that.
}
}
processChunk();
chunkIntervalIdentifier = setInterval(processChunk, 25);
InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
return true;
}
InjectedScript.searchCanceled = function()
{
if (InjectedScript._searchResults) {
const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
for (var i = 0; i < this._searchResults.length; ++i) {
var node = this._searchResults[i];
// Remove the searchResultsProperty since there might be an unfinished search.
delete node[searchResultsProperty];
}
}
if (InjectedScript._currentSearchChunkIntervalIdentifier) {
clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier);
delete InjectedScript._currentSearchChunkIntervalIdentifier;
}
InjectedScript._searchResults = [];
return true;
}
InjectedScript.openInInspectedWindow = function(url)
{
// Don't call window.open on wrapper - popup blocker mutes it.
// URIs should have no double quotes.
InjectedScript._window().eval("window.open(\"" + url + "\")");
return true;
}
InjectedScript.getCallFrames = function()
{
var callFrame = InjectedScriptHost.currentCallFrame();
if (!callFrame)
return false;
var result = [];
var depth = 0;
do {
result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
callFrame = callFrame.caller;
} while (callFrame);
return result;
}
InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup)
{
var callFrame = InjectedScript._callFrameForId(callFrameId);
if (!callFrame)
return false;
return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup);
}
InjectedScript._callFrameForId = function(id)
{
var callFrame = InjectedScriptHost.currentCallFrame();
while (--id >= 0 && callFrame)
callFrame = callFrame.caller;
return callFrame;
}
InjectedScript._clearConsoleMessages = function()
{
InjectedScriptHost.clearMessages(true);
}
InjectedScript._inspectObject = function(o)
{
if (arguments.length === 0)
return;
var inspectedWindow = InjectedScript._window();
inspectedWindow.console.log(o);
if (Object.type(o) === "node") {
InjectedScriptHost.pushNodePathToFrontend(o, true);
} else {
switch (Object.describe(o)) {
case "Database":
InjectedScriptHost.selectDatabase(o);
break;
case "Storage":
InjectedScriptHost.selectDOMStorage(o);
break;
}
}
}
InjectedScript._copy = function(o)
{
if (Object.type(o) === "node") {
var nodeId = InjectedScriptHost.pushNodePathToFrontend(o, false);
InjectedScriptHost.copyNode(nodeId);
} else {
InjectedScriptHost.copyText(o);
}
}
InjectedScript._ensureCommandLineAPIInstalled = function(evalFunction, evalObject)
{
if (evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI"))
return;
var inspectorCommandLineAPI = evalFunction.call(evalObject, "window.console._inspectorCommandLineAPI = { \n\
$: function() { return document.getElementById.apply(document, arguments) }, \n\
$$: function() { return document.querySelectorAll.apply(document, arguments) }, \n\
$x: function(xpath, context) \n\
{ \n\
var nodes = []; \n\
try { \n\
var doc = context || document; \n\
var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \n\
var node; \n\
while (node = results.iterateNext()) nodes.push(node); \n\
} catch (e) {} \n\
return nodes; \n\
}, \n\
dir: function() { return console.dir.apply(console, arguments) }, \n\
dirxml: function() { return console.dirxml.apply(console, arguments) }, \n\
keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \n\
values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \n\
profile: function() { return console.profile.apply(console, arguments) }, \n\
profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \n\
_logEvent: function _inspectorCommandLineAPI_logEvent(e) { console.log(e.type, e); }, \n\
_allEventTypes: [\"mouse\", \"key\", \"load\", \"unload\", \"abort\", \"error\", \n\
\"select\", \"change\", \"submit\", \"reset\", \"focus\", \"blur\", \n\
\"resize\", \"scroll\"], \n\
_normalizeEventTypes: function(t) \n\
{ \n\
if (typeof t === \"undefined\") \n\
t = console._inspectorCommandLineAPI._allEventTypes; \n\
else if (typeof t === \"string\") \n\
t = [t]; \n\
var i, te = []; \n\
for (i = 0; i < t.length; i++) { \n\
if (t[i] === \"mouse\") \n\
te.splice(0, 0, \"mousedown\", \"mouseup\", \"click\", \"dblclick\", \n\
\"mousemove\", \"mouseover\", \"mouseout\"); \n\
else if (t[i] === \"key\") \n\
te.splice(0, 0, \"keydown\", \"keyup\", \"keypress\"); \n\
else \n\
te.push(t[i]); \n\
} \n\
return te; \n\
}, \n\
monitorEvents: function(o, t) \n\
{ \n\
if (!o || !o.addEventListener || !o.removeEventListener) \n\
return; \n\
t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
for (i = 0; i < t.length; i++) { \n\
o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
o.addEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
} \n\
}, \n\
unmonitorEvents: function(o, t) \n\
{ \n\
if (!o || !o.removeEventListener) \n\
return; \n\
t = console._inspectorCommandLineAPI._normalizeEventTypes(t); \n\
for (i = 0; i < t.length; i++) { \n\
o.removeEventListener(t[i], console._inspectorCommandLineAPI._logEvent, false); \n\
} \n\
}, \n\
_inspectedNodes: [], \n\
get $0() { return console._inspectorCommandLineAPI._inspectedNodes[0] }, \n\
get $1() { return console._inspectorCommandLineAPI._inspectedNodes[1] }, \n\
get $2() { return console._inspectorCommandLineAPI._inspectedNodes[2] }, \n\
get $3() { return console._inspectorCommandLineAPI._inspectedNodes[3] }, \n\
get $4() { return console._inspectorCommandLineAPI._inspectedNodes[4] }, \n\
};");
inspectorCommandLineAPI.clear = InjectedScriptHost.wrapCallback(InjectedScript._clearConsoleMessages);
inspectorCommandLineAPI.inspect = InjectedScriptHost.wrapCallback(InjectedScript._inspectObject);
inspectorCommandLineAPI.copy = InjectedScriptHost.wrapCallback(InjectedScript._copy);
}
InjectedScript._resolveObject = function(objectProxy)
{
var object = InjectedScript._objectForId(objectProxy.objectId);
var path = objectProxy.path;
var protoDepth = objectProxy.protoDepth;
// Follow the property path.
for (var i = 0; object && path && i < path.length; ++i)
object = object[path[i]];
// Get to the necessary proto layer.
for (var i = 0; object && protoDepth && i < protoDepth; ++i)
object = object.__proto__;
return object;
}
InjectedScript._window = function()
{
// TODO: replace with 'return window;' once this script is injected into
// the page's context.
return InjectedScriptHost.inspectedWindow();
}
InjectedScript._nodeForId = function(nodeId)
{
if (!nodeId)
return null;
return InjectedScriptHost.nodeForId(nodeId);
}
InjectedScript._objectForId = function(objectId)
{
// There are three types of object ids used:
// - numbers point to DOM Node via the InspectorDOMAgent mapping
// - strings point to console objects cached in InspectorController for lazy evaluation upon them
// - objects contain complex ids and are currently used for scoped objects
if (typeof objectId === "number") {
return InjectedScript._nodeForId(objectId);
} else if (typeof objectId === "string") {
return InjectedScriptHost.unwrapObject(objectId);
} else if (typeof objectId === "object") {
var callFrame = InjectedScript._callFrameForId(objectId.callFrame);
if (objectId.thisObject)
return callFrame.thisObject;
else
return callFrame.scopeChain[objectId.chainIndex];
}
return objectId;
}
InjectedScript.pushNodeToFrontend = function(objectProxy)
{
var object = InjectedScript._resolveObject(objectProxy);
if (!object || Object.type(object) !== "node")
return false;
return InjectedScriptHost.pushNodePathToFrontend(object, false);
}
InjectedScript.nodeByPath = function(path)
{
// We make this call through the injected script only to get a nice
// callback for it.
return InjectedScriptHost.pushNodeByPathToFrontend(path.join(","));
}
// Called from within InspectorController on the 'inspected page' side.
InjectedScript.createProxyObject = function(object, objectId, abbreviate)
{
var result = {};
result.objectId = objectId;
result.type = Object.type(object);
var type = typeof object;
if ((type === "object" && object !== null) || type === "function") {
for (var subPropertyName in object) {
result.hasChildren = true;
break;
}
}
try {
result.description = Object.describe(object, abbreviate);
} catch (e) {
result.errorText = e.toString();
}
return result;
}
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);
}
InjectedScript.CallFrameProxy.prototype = {
_wrapScopeChain: function(callFrame)
{
var foundLocalScope = false;
var scopeChain = callFrame.scopeChain;
var scopeChainProxy = [];
for (var i = 0; i < scopeChain.length; ++i) {
var scopeObject = scopeChain[i];
var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }, true);
if (Object.prototype.toString.call(scopeObject) === "[object JSActivation]") {
if (!foundLocalScope)
scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }, true);
else
scopeObjectProxy.isClosure = true;
foundLocalScope = true;
scopeObjectProxy.isLocal = true;
} else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element)
scopeObjectProxy.isElement = true;
else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document)
scopeObjectProxy.isDocument = true;
else if (!foundLocalScope)
scopeObjectProxy.isWithBlock = true;
scopeChainProxy.push(scopeObjectProxy);
}
return scopeChainProxy;
}
}
InjectedScript.executeSql = function(callId, databaseId, query)
{
function successCallback(tx, result)
{
var rows = result.rows;
var result = [];
var length = rows.length;
for (var i = 0; i < length; ++i) {
var data = {};
result.push(data);
var row = rows.item(i);
for (var columnIdentifier in row) {
// FIXME: (Bug 19439) We should specially format SQL NULL here
// (which is represented by JavaScript null here, and turned
// into the string "null" by the String() function).
var text = row[columnIdentifier];
data[columnIdentifier] = String(text);
}
}
InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, JSON.stringify(result), false);
}
function errorCallback(tx, error)
{
InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, JSON.stringify(error), false);
}
function queryTransaction(tx)
{
tx.executeSql(query, null, InjectedScriptHost.wrapCallback(successCallback), InjectedScriptHost.wrapCallback(errorCallback));
}
var database = InjectedScriptHost.databaseForId(databaseId);
if (!database)
errorCallback(null, { code : 2 }); // Return as unexpected version.
database.transaction(InjectedScriptHost.wrapCallback(queryTransaction), InjectedScriptHost.wrapCallback(errorCallback));
return true;
}
Object.type = function(obj)
{
if (obj === null)
return "null";
var type = typeof obj;
if (type !== "object" && type !== "function")
return type;
var win = InjectedScript._window();
if (obj instanceof win.Node)
return (obj.nodeType === undefined ? type : "node");
if (obj instanceof win.String)
return "string";
if (obj instanceof win.Array)
return "array";
if (obj instanceof win.Boolean)
return "boolean";
if (obj instanceof win.Number)
return "number";
if (obj instanceof win.Date)
return "date";
if (obj instanceof win.RegExp)
return "regexp";
if (obj instanceof win.NodeList)
return "array";
if (obj instanceof win.HTMLCollection || obj instanceof win.HTMLAllCollection)
return "array";
if (obj instanceof win.Error)
return "error";
return type;
}
Object.hasProperties = function(obj)
{
if (typeof obj === "undefined" || typeof obj === "null")
return false;
for (var name in obj)
return true;
return false;
}
Object.describe = function(obj, abbreviated)
{
var type1 = Object.type(obj);
var type2 = Object.className(obj);
switch (type1) {
case "object":
case "node":
case "array":
return type2;
case "string":
if (!abbreviated)
return obj;
if (obj.length > 100)
return "\"" + obj.substring(0, 100) + "\u2026\"";
return "\"" + obj + "\"";
case "function":
var objectText = String(obj);
if (!/^function /.test(objectText))
objectText = (type2 == "object") ? type1 : type2;
else if (abbreviated)
objectText = /.*/.exec(obj)[0].replace(/ +$/g, "");
return objectText;
default:
return String(obj);
}
}
Object.className = function(obj)
{
return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1")
}
// Although Function.prototype.bind and String.prototype.escapeCharacters are defined in utilities.js they will soon become
// unavailable in the InjectedScript context. So we define them here for the local use.
// TODO: remove this comment once InjectedScript runs in a separate context.
Function.prototype.bind = function(thisObject)
{
var func = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
}
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;
}
/* 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.
*/
var InjectedScriptAccess = {};
InjectedScriptAccess._installHandler = function(methodName, async)
{
InjectedScriptAccess[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(JSON.parse(result));
else
WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage("Error dispatching: " + methodName));
}
var callId = WebInspector.Callback.wrap(myCallback);
InspectorBackend.dispatchOnInjectedScript(callId, methodName, argsString, !!async);
};
}
// 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("addInspectedNode");
InjectedScriptAccess._installHandler("addStyleSelector");
InjectedScriptAccess._installHandler("applyStyleRuleText");
InjectedScriptAccess._installHandler("applyStyleText");
InjectedScriptAccess._installHandler("evaluate");
InjectedScriptAccess._installHandler("evaluateInCallFrame");
InjectedScriptAccess._installHandler("getCompletions");
InjectedScriptAccess._installHandler("getComputedStyle");
InjectedScriptAccess._installHandler("getInlineStyle");
InjectedScriptAccess._installHandler("getProperties");
InjectedScriptAccess._installHandler("getPrototypes");
InjectedScriptAccess._installHandler("getStyles");
InjectedScriptAccess._installHandler("openInInspectedWindow");
InjectedScriptAccess._installHandler("performSearch");
InjectedScriptAccess._installHandler("pushNodeToFrontend");
InjectedScriptAccess._installHandler("nodeByPath");
InjectedScriptAccess._installHandler("searchCanceled");
InjectedScriptAccess._installHandler("setPropertyValue");
InjectedScriptAccess._installHandler("setStyleProperty");
InjectedScriptAccess._installHandler("setStyleText");
InjectedScriptAccess._installHandler("toggleStyleEnabled");
// Some methods can't run synchronously even on the injected script side (such as DB transactions).
// Mark them as asynchronous here.
InjectedScriptAccess._installHandler("executeSql", true);
WebInspector.didDispatchOnInjectedScript = WebInspector.Callback.processCallback;
/* 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
};
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);
this.element.addStyleClass("timeline");
this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories);
this._overviewPane.addEventListener("window changed", this._scheduleRefresh, this);
this._overviewPane.addEventListener("filter changed", this._refresh, this);
this.element.appendChild(this._overviewPane.element);
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._createStatusbarButtons();
this._records = [];
this._sendRequestRecords = {};
this._calculator = new WebInspector.TimelineCalculator();
}
WebInspector.TimelinePanel.prototype = {
toolbarItemClass: "timeline",
get toolbarItemLabel()
{
return WebInspector.UIString("Timeline");
},
get statusBarItems()
{
return [this.toggleTimelineButton.element, this.clearButton.element];
},
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;
},
_createStatusbarButtons: function()
{
this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);
this.clearButton = new WebInspector.StatusBarButton("", "timeline-clear-status-bar-item");
this.clearButton.addEventListener("click", this.reset.bind(this), false);
},
_toggleTimelineButtonClicked: function()
{
if (this.toggleTimelineButton.toggled)
InspectorBackend.stopTimelineProfiler();
else
InspectorBackend.startTimelineProfiler();
},
timelineWasStarted: function()
{
this.toggleTimelineButton.toggled = true;
},
timelineWasStopped: function()
{
this.toggleTimelineButton.toggled = false;
},
addRecordToTimeline: function(record)
{
this._innerAddRecordToTimeline(record, this._records);
this._scheduleRefresh();
},
_innerAddRecordToTimeline: function(record, collection)
{
var formattedRecord = this._formatRecord(record);
// Glue subsequent records with same category and title together if they are closer than 100ms to each other.
if (this._lastRecord && (!record.children || !record.children.length) &&
this._lastRecord.category == formattedRecord.category &&
this._lastRecord.title == formattedRecord.title &&
this._lastRecord.details == formattedRecord.details &&
formattedRecord.startTime - this._lastRecord.endTime < 0.1) {
this._lastRecord.endTime = formattedRecord.endTime;
this._lastRecord.count++;
} else {
collection.push(formattedRecord);
for (var i = 0; record.children && i < record.children.length; ++i) {
if (!formattedRecord.children)
formattedRecord.children = [];
var formattedChild = this._innerAddRecordToTimeline(record.children[i], formattedRecord.children);
formattedChild.parent = formattedRecord;
}
this._lastRecord = record.children && record.children.length ? null : formattedRecord;
}
return formattedRecord;
},
_formatRecord: function(record)
{
var recordTypes = WebInspector.TimelineAgent.RecordType;
if (!this._recordStyles) {
this._recordStyles = {};
this._recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
this._recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
this._recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
this._recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
this._recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
this._recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
this._recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
this._recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
this._recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
this._recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
this._recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
this._recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
this._recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
this._recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
this._recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
}
var style = this._recordStyles[record.type];
if (!style)
style = this._recordStyles[recordTypes.EventDispatch];
var formattedRecord = {};
formattedRecord.category = style.category;
formattedRecord.title = style.title;
formattedRecord.startTime = record.startTime / 1000;
formattedRecord.data = record.data;
formattedRecord.count = 1;
formattedRecord.type = record.type;
formattedRecord.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : formattedRecord.startTime;
formattedRecord.record = record;
// Make resource receive record last since request was sent; make finish record last since response received.
if (record.type === WebInspector.TimelineAgent.RecordType.ResourceSendRequest) {
this._sendRequestRecords[record.data.identifier] = formattedRecord;
} else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse) {
var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
sendRequestRecord._responseReceivedFormattedTime = formattedRecord.startTime;
formattedRecord.startTime = sendRequestRecord.startTime;
sendRequestRecord.details = this._getRecordDetails(record);
}
} else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceFinish) {
var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
if (sendRequestRecord) // False for main resource.
formattedRecord.startTime = sendRequestRecord._responseReceivedFormattedTime;
}
formattedRecord.details = this._getRecordDetails(record);
return formattedRecord;
},
_getRecordDetails: function(record)
{
switch (record.type) {
case WebInspector.TimelineAgent.RecordType.EventDispatch:
return record.data ? record.data.type : "";
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:
case WebInspector.TimelineAgent.RecordType.TimerFire:
return record.data.timerId;
case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
case WebInspector.TimelineAgent.RecordType.XHRLoad:
case WebInspector.TimelineAgent.RecordType.EvaluateScript:
case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
return WebInspector.displayNameForURL(record.data.url);
case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
case WebInspector.TimelineAgent.RecordType.ResourceFinish:
var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
return sendRequestRecord ? WebInspector.displayNameForURL(sendRequestRecord.data.url) : "";
case WebInspector.TimelineAgent.RecordType.MarkTimeline:
return record.data.message;
default:
return "";
}
},
setSidebarWidth: function(width)
{
WebInspector.Panel.prototype.setSidebarWidth.call(this, width);
this._sidebarBackgroundElement.style.width = width + "px";
this._overviewPane.setSidebarWidth(width);
},
updateMainViewWidth: function(width)
{
this._containerContentElement.style.left = width + "px";
this._scheduleRefresh();
this._overviewPane.updateMainViewWidth(width);
},
resize: function() {
this._scheduleRefresh();
},
reset: function()
{
this._lastRecord = null;
this._sendRequestRecords = {};
this._overviewPane.reset();
this._records = [];
this._refresh();
},
show: function()
{
WebInspector.Panel.prototype.show.call(this);
if (this._needsRefresh)
this._refresh();
},
_onScroll: function(event)
{
var scrollTop = this._containerElement.scrollTop;
var dividersTop = Math.max(0, scrollTop);
this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
this._scheduleRefresh(true);
},
_scheduleRefresh: function(immediate)
{
if (this._needsRefresh)
return;
this._needsRefresh = true;
if (this.visible && !("_refreshTimeout" in this))
this._refreshTimeout = setTimeout(this._refresh.bind(this), immediate ? 0 : 100);
},
_refresh: function()
{
this._needsRefresh = false;
if ("_refreshTimeout" in this) {
clearTimeout(this._refreshTimeout);
delete this._refreshTimeout;
}
this._overviewPane.update(this._records);
this._refreshRecords();
},
_refreshRecords: function()
{
this._calculator.windowLeft = this._overviewPane.windowLeft;
this._calculator.windowRight = this._overviewPane.windowRight;
this._calculator.reset();
for (var i = 0; i < this._records.length; ++i)
this._calculator.updateBoundaries(this._records[i]);
var recordsInWindow = [];
for (var i = 0; i < this._records.length; ++i) {
var record = this._records[i];
var percentages = this._calculator.computeBarGraphPercentages(record);
if (percentages.start < 100 && percentages.end >= 0 && !record.category.hidden)
this._addToRecordsWindow(record, recordsInWindow);
}
// Calculate the visible area.
var visibleTop = this._containerElement.scrollTop;
var visibleBottom = visibleTop + this._containerElement.clientHeight;
// Define row height, should be in sync with styles for timeline graphs.
const rowHeight = 18;
const expandOffset = 15;
// Convert visible area to visible indexes. Always include top-level record for a visible nested record.
var startIndex = Math.max(0, Math.floor(visibleTop / rowHeight) - 1);
while (startIndex > 0 && recordsInWindow[startIndex].parent)
startIndex--;
var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
while (endIndex < recordsInWindow.length - 1 && recordsInWindow[startIndex].parent)
endIndex++;
// 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 graphRowElement = this._graphRowsElement.firstChild;
var width = this._graphRowsElement.offsetWidth;
var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
for (var i = startIndex; i < endIndex; ++i) {
var record = recordsInWindow[i];
var isEven = !(i % 2);
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.listRow.update(record, isEven);
graphRowElement.graphRow.update(record, isEven, this._calculator, width, expandOffset);
listRowElement = listRowElement.nextSibling;
graphRowElement = graphRowElement.nextSibling;
}
// Remove extra rows.
while (listRowElement) {
var nextElement = listRowElement.nextSibling;
listRowElement.listRow.dispose();
listRowElement = nextElement;
}
while (graphRowElement) {
var nextElement = graphRowElement.nextSibling;
graphRowElement.graphRow.dispose();
graphRowElement = nextElement;
}
// Reserve some room for expand / collapse controls to the left for records that start at 0ms.
var timelinePaddingLeft = this._calculator.windowLeft === 0 ? expandOffset : 0;
this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft);
this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight);
},
_addToRecordsWindow: function(record, recordsWindow)
{
recordsWindow.push(record);
if (!record.collapsed) {
var index = recordsWindow.length;
for (var i = 0; record.children && i < record.children.length; ++i)
this._addToRecordsWindow(record.children[i], recordsWindow);
record.visibleChildrenCount = recordsWindow.length - index;
}
},
_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);
}
}
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.windowLeft = 0.0;
this.windowRight = 1.0;
this._uiString = WebInspector.UIString.bind(WebInspector);
}
WebInspector.TimelineCalculator.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};
},
get minimumBoundary()
{
if (typeof this._minimumBoundary === "number")
return this._minimumBoundary;
if (typeof this.windowLeft === "number")
this._minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
else
this._minimumBoundary = this._absoluteMinimumBoundary;
return this._minimumBoundary;
},
get maximumBoundary()
{
if (typeof this._maximumBoundary === "number")
return this._maximumBoundary;
if (typeof this.windowLeft === "number")
this._maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
else
this._maximumBoundary = this._absoluteMaximumBoundary;
return this._maximumBoundary;
},
reset: function()
{
delete this._absoluteMinimumBoundary;
delete this._absoluteMaximumBoundary;
delete this._minimumBoundary;
delete this._maximumBoundary;
},
updateBoundaries: function(record)
{
var didChange = false;
var lowerBound = record.startTime;
if (typeof this._absoluteMinimumBoundary === "undefined" || lowerBound < this._absoluteMinimumBoundary) {
this._absoluteMinimumBoundary = lowerBound;
delete this._minimumBoundary;
didChange = true;
}
var upperBound = record.endTime;
if (typeof this._absoluteMaximumBoundary === "undefined" || upperBound > this._absoluteMaximumBoundary) {
this._absoluteMaximumBoundary = upperBound;
delete this._maximumBoundary;
didChange = true;
}
return didChange;
},
get boundarySpan()
{
return this.maximumBoundary - this.minimumBoundary;
},
formatValue: function(value)
{
return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, this._uiString);
}
}
WebInspector.TimelineRecordListRow = function()
{
this.element = document.createElement("div");
this.element.listRow = this;
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._repeatCountElement = document.createElement("span");
this._repeatCountElement.className = "count";
this.element.appendChild(separatorElement);
this.element.appendChild(this._dataElement);
this.element.appendChild(this._repeatCountElement);
}
WebInspector.TimelineRecordListRow.prototype = {
update: function(record, isEven)
{
this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
this._typeElement.textContent = record.title;
if (record.details) {
this._dataElement.textContent = "(" + record.details + ")";
this._dataElement.title = record.details;
} else {
this._dataElement.textContent = "";
this._dataElement.title = "";
}
if (record.count > 1)
this._repeatCountElement.textContent = "\u2009\u00d7\u2009" + record.count;
else
this._repeatCountElement.textContent = "";
},
dispose: function()
{
this.element.parentElement.removeChild(this.element);
}
}
WebInspector.TimelineRecordGraphRow = function(graphContainer, refreshCallback, rowHeight)
{
this.element = document.createElement("div");
this.element.graphRow = this;
this._barAreaElement = document.createElement("div");
this._barAreaElement.className = "timeline-graph-bar-area";
this.element.appendChild(this._barAreaElement);
this._barElement = document.createElement("div");
this._barElement.className = "timeline-graph-bar";
this._barAreaElement.appendChild(this._barElement);
this._expandElement = document.createElement("div");
this._expandElement.className = "timeline-expandable";
graphContainer.appendChild(this._expandElement);
var leftBorder = document.createElement("div");
leftBorder.className = "timeline-expandable-left";
this._expandElement.appendChild(leftBorder);
this._expandElement.addEventListener("click", this._onClick.bind(this));
this._refreshCallback = refreshCallback;
this._rowHeight = rowHeight;
}
WebInspector.TimelineRecordGraphRow.prototype = {
update: function(record, isEven, calculator, clientWidth, expandOffset)
{
this._record = record;
this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
var percentages = calculator.computeBarGraphPercentages(record);
var left = percentages.start / 100 * clientWidth;
var width = (percentages.end - percentages.start) / 100 * clientWidth;
this._barElement.style.left = (left + expandOffset) + "px";
this._barElement.style.width = width + "px";
if (record.visibleChildrenCount) {
this._expandElement.style.top = this.element.offsetTop + "px";
this._expandElement.style.left = left + "px";
this._expandElement.style.width = Math.max(12, width + 25) + "px";
if (!record.collapsed) {
this._expandElement.style.height = (record.visibleChildrenCount + 1) * this._rowHeight + "px";
this._expandElement.addStyleClass("timeline-expandable-expanded");
this._expandElement.removeStyleClass("timeline-expandable-collapsed");
} else {
this._expandElement.style.height = this._rowHeight + "px";
this._expandElement.addStyleClass("timeline-expandable-collapsed");
this._expandElement.removeStyleClass("timeline-expandable-expanded");
}
this._expandElement.removeStyleClass("hidden");
} else {
this._expandElement.addStyleClass("hidden");
}
},
_onClick: function(event)
{
this._record.collapsed = !this._record.collapsed;
this._refreshCallback();
},
dispose: function()
{
this.element.parentElement.removeChild(this.element);
this._expandElement.parentElement.removeChild(this._expandElement);
}
}
/* 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.element = document.createElement("div");
this.element.id = "timeline-overview-panel";
this._categories = categories;
this._overviewSidebarElement = document.createElement("div");
this._overviewSidebarElement.id = "timeline-overview-sidebar";
this.element.appendChild(this._overviewSidebarElement);
var overviewTreeElement = document.createElement("ol");
overviewTreeElement.className = "sidebar-tree";
this._overviewSidebarElement.appendChild(overviewTreeElement);
var sidebarTree = new TreeOutline(overviewTreeElement);
var categoriesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("TIMELINES"), {}, true);
categoriesTreeElement.expanded = true;
sidebarTree.appendChild(categoriesTreeElement);
for (var categoryName in this._categories) {
var category = this._categories[categoryName];
categoriesTreeElement.appendChild(new WebInspector.TimelineCategoryTreeElement(category, this._onCheckboxClicked.bind(this, category)));
}
this._overviewGrid = new WebInspector.TimelineGrid();
this._overviewGrid.element.id = "timeline-overview-grid";
this._overviewGrid.itemsGraphsElement.id = "timeline-overview-graphs";
this.element.appendChild(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._overviewWindowElement.addEventListener("mousedown", this._dragWindow.bind(this), false);
this._overviewGrid.element.appendChild(this._overviewWindowElement);
this._leftResizeElement = document.createElement("div");
this._leftResizeElement.className = "timeline-window-resizer";
this._leftResizeElement.style.left = 0;
this._overviewGrid.element.appendChild(this._leftResizeElement);
this._leftResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._leftResizeElement), false);
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._rightResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._rightResizeElement), false);
this._overviewCalculator = new WebInspector.TimelineOverviewCalculator();
var separatorElement = document.createElement("div");
separatorElement.id = "timeline-overview-separator";
this.element.appendChild(separatorElement);
this.windowLeft = 0.0;
this.windowRight = 1.0;
}
WebInspector.TimelineOverviewPane.prototype = {
_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");
},
update: function(records)
{
// Clear summary bars.
var timelines = {};
for (var category in this._categories) {
timelines[category] = [];
this._categoryGraphs[category].clearChunks();
}
function forAllRecords(recordsArray, callback)
{
if (!recordsArray)
return;
for (var i = 0; i < recordsArray.length; ++i) {
callback(recordsArray[i]);
forAllRecords(recordsArray[i].children, callback);
}
}
// Create sparse arrays with 101 cells each to fill with chunks for a given category.
this._overviewCalculator.reset();
forAllRecords(records, this._overviewCalculator.updateBoundaries.bind(this._overviewCalculator));
function markTimeline(record)
{
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;
}
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._overviewGrid.updateDividers(true, this._overviewCalculator);
},
setSidebarWidth: function(width)
{
this._overviewSidebarElement.style.width = width + "px";
},
updateMainViewWidth: function(width)
{
this._overviewGrid.element.style.left = width + "px";
},
reset: function()
{
this._overviewCalculator.reset();
this._overviewGrid.updateDividers(true, this._overviewCalculator);
this.windowLeft = 0.0;
this.windowRight = 1.0;
},
_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)
{
WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX,
this._leftResizeElement.offsetLeft, this._rightResizeElement.offsetLeft), this._endWindowDragging.bind(this), event, "ew-resize");
},
_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;
this._setWindowPosition(start, null);
},
_resizeWindowRight: function(end)
{
// Glue to edge.
if (end > this._overviewGrid.element.clientWidth - 10)
end = this._overviewGrid.element.clientWidth;
this._setWindowPosition(null, end);
},
_setWindowPosition: function(start, end)
{
if (typeof start === "number") {
if (start > this._rightResizeElement.offsetLeft - 4)
start = this._rightResizeElement.offsetLeft - 4;
this.windowLeft = start / this._overviewGrid.element.clientWidth;
this._leftResizeElement.style.left = this.windowLeft * 100 + "%";
this._overviewWindowElement.style.left = this.windowLeft * 100 + "%";
}
if (typeof end === "number") {
if (end < this._leftResizeElement.offsetLeft + 12)
end = this._leftResizeElement.offsetLeft + 12;
this.windowRight = end / this._overviewGrid.element.clientWidth;
this._rightResizeElement.style.left = this.windowRight * 100 + "%";
}
this._overviewWindowElement.style.width = (this.windowRight - this.windowLeft) * 100 + "%";
this.dispatchEventToListeners("window changed");
},
_endWindowDragging: function(event)
{
WebInspector.elementDragEnd(event);
}
}
WebInspector.TimelineOverviewPane.prototype.__proto__ = WebInspector.Object.prototype;
WebInspector.TimelineOverviewCalculator = function()
{
this._uiString = WebInspector.UIString.bind(WebInspector);
}
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, this._uiString);
}
}
WebInspector.TimelineCategoryTreeElement = function(category, onCheckboxClicked)
{
this._category = category;
this._onCheckboxClicked = onCheckboxClicked;
// Pass an empty title, the title gets made later in onattach.
TreeElement.call(this, "", null, false);
}
WebInspector.TimelineCategoryTreeElement.prototype = {
onattach: function()
{
this.listItemElement.removeChildren();
this.listItemElement.addStyleClass("timeline-category-tree-item");
this.listItemElement.addStyleClass("timeline-category-" + this._category.name);
var label = document.createElement("label");
var checkElement = document.createElement("input");
checkElement.type = "checkbox";
checkElement.className = "timeline-category-checkbox";
checkElement.checked = true;
checkElement.addEventListener("click", this._onCheckboxClicked);
label.appendChild(checkElement);
var typeElement = document.createElement("span");
typeElement.className = "type";
typeElement.textContent = this._category.title;
label.appendChild(typeElement);
this.listItemElement.appendChild(label);
}
}
WebInspector.TimelineCategoryTreeElement.prototype.__proto__ = TreeElement.prototype;
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);
}
}
/* 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;
}
WebInspector.TestController.prototype = {
waitUntilDone: function()
{
this._waitUntilDone = true;
},
notifyDone: function(result)
{
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);
}
/* base.js */
// Copyright 2006 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.
//
// 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.
// NOTE: This file has been changed from the one on doctype. The following
// changes were made:
// - Removed goog.globalEval because it calls eval() which is not allowed from
// inside v8 extensions. If we ever need to use globalEval, we will need to
// find a way to work around this problem.
// - Remove Function.prototype.apply() emulation for the same reason. This one
// is useless anyway because V8 supports apply() natively.
/**
* @fileoverview Bootstrap for the Google JS Library
*/
/**
* @define {boolean} Overridden to true by the compiler when
* --mark_as_compiled is specified.
*/
var COMPILED = true;
/**
* Base namespace for the Google JS library. Checks to see goog is
* already defined in the current scope before assigning to prevent
* clobbering if base.js is loaded more than once.
*/
var goog = {}; // Check to see if already defined in current scope
/**
* Reference to the global context. In most cases this will be 'window'.
*/
goog.global = this;
/**
* Indicates whether or not we can call 'eval' directly to eval code in the
* global scope. Set to a Boolean by the first call to goog.globalEval (which
* empirically tests whether eval works for globals). @see goog.globalEval
* @type {boolean?}
* @private
*/
goog.evalWorksForGlobals_ = null;
/**
* Creates object stubs for a namespace. When present in a file, goog.provide
* also indicates that the file defines the indicated object.
* @param {string} name name of the object that this file defines.
*/
goog.provide = function(name) {
if (!COMPILED) {
// Ensure that the same namespace isn't provided twice. This is intended
// to teach new developers that 'goog.provide' is effectively a variable
// declaration. And when JSCompiler transforms goog.provide into a real
// variable declaration, the compiled JS should work the same as the raw
// JS--even when the raw JS uses goog.provide incorrectly.
if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
throw 'Namespace "' + name + '" already declared.';
}
var namespace = name;
while ((namespace = namespace.substr(0, namespace.lastIndexOf('.')))) {
goog.implicitNamespaces_[namespace] = true;
}
}
goog.exportPath_(name);
};
if (!COMPILED) {
/**
* Namespaces implicitly defined by goog.provide. For example,
* goog.provide('goog.events.Event') implicitly declares
* that 'goog' and 'goog.events' must be namespaces.
*
* @type {Object}
* @private
*/
goog.implicitNamespaces_ = {};
}
/**
* Builds an object structure for the provided namespace path,
* ensuring that names that already exist are not overwritten. For
* example:
* "a.b.c" -> a = {};a.b={};a.b.c={};
* Used by goog.provide and goog.exportSymbol.
* @param {string} name name of the object that this file defines.
* @param {Object} opt_object the object to expose at the end of the path.
* @private
*/
goog.exportPath_ = function(name, opt_object) {
var parts = name.split('.');
var cur = goog.global;
var part;
// Internet Explorer exhibits strange behavior when throwing errors from
// methods externed in this manner. See the testExportSymbolExceptions in
// base_test.html for an example.
if (!(parts[0] in cur) && cur.execScript) {
cur.execScript('var ' + parts[0]);
}
// Parentheses added to eliminate strict JS warning in Firefox.
while ((part = parts.shift())) {
if (!parts.length && goog.isDef(opt_object)) {
// last part and we have an object; use it
cur[part] = opt_object;
} else if (cur[part]) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
};
/**
* Returns an object based on its fully qualified name
* @param {string} name The fully qualified name.
* @return {Object?} The object or, if not found, null.
*/
goog.getObjectByName = function(name) {
var parts = name.split('.');
var cur = goog.global;
for (var part; part = parts.shift(); ) {
if (cur[part]) {
cur = cur[part];
} else {
return null;
}
}
return cur;
};
/**
* Globalizes a whole namespace, such as goog or goog.lang.
*
* @param {Object} obj The namespace to globalize.
* @param {Object} opt_global The object to add the properties to.
* @deprecated Properties may be explicitly exported to the global scope, but
* this should no longer be done in bulk.
*/
goog.globalize = function(obj, opt_global) {
var global = opt_global || goog.global;
for (var x in obj) {
global[x] = obj[x];
}
};
/**
* Adds a dependency from a file to the files it requires.
* @param {string} relPath The path to the js file.
* @param {Array} provides An array of strings with the names of the objects
* this file provides.
* @param {Array} requires An array of strings with the names of the objects
* this file requires.
*/
goog.addDependency = function(relPath, provides, requires) {
if (!COMPILED) {
var provide, require;
var path = relPath.replace(/\\/g, '/');
var deps = goog.dependencies_;
for (var i = 0; provide = provides[i]; i++) {
deps.nameToPath[provide] = path;
if (!(path in deps.pathToNames)) {
deps.pathToNames[path] = {};
}
deps.pathToNames[path][provide] = true;
}
for (var j = 0; require = requires[j]; j++) {
if (!(path in deps.requires)) {
deps.requires[path] = {};
}
deps.requires[path][require] = true;
}
}
};
/**
* Implements a system for the dynamic resolution of dependencies
* that works in parallel with the BUILD system.
* @param {string} rule Rule to include, in the form goog.package.part.
*/
goog.require = function(rule) {
// if the object already exists we do not need do do anything
if (!COMPILED) {
if (goog.getObjectByName(rule)) {
return;
}
var path = goog.getPathFromDeps_(rule);
if (path) {
goog.included_[path] = true;
goog.writeScripts_();
} else {
// NOTE(nicksantos): We could throw an error, but this would break
// legacy users that depended on this failing silently. Instead, the
// compiler should warn us when there are invalid goog.require calls.
}
}
};
/**
* Path for included scripts
* @type {string}
*/
goog.basePath = '';
/**
* Null function used for default values of callbacks, etc.
* @type {Function}
*/
goog.nullFunction = function() {};
/**
* When defining a class Foo with an abstract method bar(), you can do:
*
* Foo.prototype.bar = goog.abstractMethod
*
* Now if a subclass of Foo fails to override bar(), an error
* will be thrown when bar() is invoked.
*
* Note: This does not take the name of the function to override as
* an argument because that would make it more difficult to obfuscate
* our JavaScript code.
*
* @throws {Error} when invoked to indicate the method should be
* overridden.
*/
goog.abstractMethod = function() {
throw Error('unimplemented abstract method');
};
if (!COMPILED) {
/**
* Object used to keep track of urls that have already been added. This
* record allows the prevention of circular dependencies.
* @type {Object}
* @private
*/
goog.included_ = {};
/**
* This object is used to keep track of dependencies and other data that is
* used for loading scripts
* @private
* @type {Object}
*/
goog.dependencies_ = {
pathToNames: {}, // 1 to many
nameToPath: {}, // 1 to 1
requires: {}, // 1 to many
visited: {}, // used when resolving dependencies to prevent us from
// visiting the file twice
written: {} // used to keep track of script files we have written
};
/**
* Tries to detect the base path of the base.js script that bootstraps
* Google JS Library
* @private
*/
goog.findBasePath_ = function() {
var doc = goog.global.document;
if (typeof doc == 'undefined') {
return;
}
if (goog.global.GOOG_BASE_PATH) {
goog.basePath = goog.global.GOOG_BASE_PATH;
return;
} else {
goog.global.GOOG_BASE_PATH = null;
}
var scripts = doc.getElementsByTagName('script');
for (var script, i = 0; script = scripts[i]; i++) {
var src = script.src;
var l = src.length;
if (src.substr(l - 7) == 'base.js') {
goog.basePath = src.substr(0, l - 7);
return;
}
}
};
/**
* Writes a script tag if, and only if, that script hasn't already been added
* to the document. (Must be called at execution time)
* @param {string} src Script source.
* @private
*/
goog.writeScriptTag_ = function(src) {
var doc = goog.global.document;
if (typeof doc != 'undefined' &&
!goog.dependencies_.written[src]) {
goog.dependencies_.written[src] = true;
doc.write('<script type="text/javascript" src="' +
src + '"></' + 'script>');
}
};
/**
* Resolves dependencies based on the dependencies added using addDependency
* and calls writeScriptTag_ in the correct order.
* @private
*/
goog.writeScripts_ = function() {
// the scripts we need to write this time
var scripts = [];
var seenScript = {};
var deps = goog.dependencies_;
function visitNode(path) {
if (path in deps.written) {
return;
}
// we have already visited this one. We can get here if we have cyclic
// dependencies
if (path in deps.visited) {
if (!(path in seenScript)) {
seenScript[path] = true;
scripts.push(path);
}
return;
}
deps.visited[path] = true;
if (path in deps.requires) {
for (var requireName in deps.requires[path]) {
visitNode(deps.nameToPath[requireName]);
}
}
if (!(path in seenScript)) {
seenScript[path] = true;
scripts.push(path);
}
}
for (var path in goog.included_) {
if (!deps.written[path]) {
visitNode(path);
}
}
for (var i = 0; i < scripts.length; i++) {
if (scripts[i]) {
goog.writeScriptTag_(goog.basePath + scripts[i]);
} else {
throw Error('Undefined script input');
}
}
};
/**
* Looks at the dependency rules and tries to determine the script file that
* fulfills a particular rule.
* @param {string} rule In the form goog.namespace.Class or project.script.
* @return {string?} Url corresponding to the rule, or null.
* @private
*/
goog.getPathFromDeps_ = function(rule) {
if (rule in goog.dependencies_.nameToPath) {
return goog.dependencies_.nameToPath[rule];
} else {
return null;
}
};
goog.findBasePath_();
goog.writeScriptTag_(goog.basePath + 'deps.js');
}
//==============================================================================
// Language Enhancements
//==============================================================================
/**
* This is a "fixed" version of the typeof operator. It differs from the typeof
* operator in such a way that null returns 'null' and arrays return 'array'.
* @param {*} value The value to get the type of.
* @return {string} The name of the type.
*/
goog.typeOf = function(value) {
var s = typeof value;
if (s == 'object') {
if (value) {
// We cannot use constructor == Array or instanceof Array because
// different frames have different Array objects. In IE6, if the iframe
// where the array was created is destroyed, the array loses its
// prototype. Then dereferencing val.splice here throws an exception, so
// we can't use goog.isFunction. Calling typeof directly returns 'unknown'
// so that will work. In this case, this function will return false and
// most array functions will still work because the array is still
// array-like (supports length and []) even though it has lost its
// prototype. Custom object cannot have non enumerable length and
// NodeLists don't have a slice method.
if (typeof value.length == 'number' &&
typeof value.splice != 'undefined' &&
!goog.propertyIsEnumerable_(value, 'length')) {
return 'array';
}
// IE in cross-window calls does not correctly marshal the function type
// (it appears just as an object) so we cannot use just typeof val ==
// 'function'. However, if the object has a call property, it is a
// function.
if (typeof value.call != 'undefined') {
return 'function';
}
} else {
return 'null';
}
// In Safari typeof nodeList returns function. We would like to return
// object for those and we can detect an invalid function by making sure that
// the function object has a call method
} else if (s == 'function' && typeof value.call == 'undefined') {
return 'object';
}
return s;
};
if (Object.prototype.propertyIsEnumerable) {
/**
* Safe way to test whether a property is enumarable. It allows testing
* for enumarable on objects where 'propertyIsEnumerable' is overridden or
* does not exist (like DOM nodes in IE).
* @param {Object} object The object to test if the property is enumerable.
* @param {string} propName The property name to check for.
* @return {boolean} True if the property is enumarable.
* @private
*/
goog.propertyIsEnumerable_ = function(object, propName) {
return Object.prototype.propertyIsEnumerable.call(object, propName);
};
} else {
/**
* Safe way to test whether a property is enumarable. It allows testing
* for enumarable on objects where 'propertyIsEnumerable' is overridden or
* does not exist (like DOM nodes in IE).
* @param {Object} object The object to test if the property is enumerable.
* @param {string} propName The property name to check for.
* @return {boolean} True if the property is enumarable.
* @private
*/
goog.propertyIsEnumerable_ = function(object, propName) {
// KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
// such as propertyIsEnumerable. We therefore use a workaround.
// Does anyone know a more efficient work around?
if (propName in object) {
for (var key in object) {
if (key == propName) {
return true;
}
}
}
return false;
};
}
/**
* Returns true if the specified value is not |undefined|.
* WARNING: Do not use this to test if an object has a property. Use the in
* operator instead.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined.
*/
goog.isDef = function(val) {
return typeof val != 'undefined';
};
/**
* Returns true if the specified value is |null|
* @param {*} val Variable to test.
* @return {boolean} Whether variable is null.
*/
goog.isNull = function(val) {
return val === null;
};
/**
* Returns true if the specified value is defined and not null
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined and not null.
*/
goog.isDefAndNotNull = function(val) {
return goog.isDef(val) && !goog.isNull(val);
};
/**
* Returns true if the specified value is an array
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArray = function(val) {
return goog.typeOf(val) == 'array';
};
/**
* Returns true if the object looks like an array. To qualify as array like
* the value needs to be either a NodeList or an object with a Number length
* property.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArrayLike = function(val) {
var type = goog.typeOf(val);
return type == 'array' || type == 'object' && typeof val.length == 'number';
};
/**
* Returns true if the object looks like a Date. To qualify as Date-like
* the value needs to be an object and have a getFullYear() function.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a like a Date.
*/
goog.isDateLike = function(val) {
return goog.isObject(val) && typeof val.getFullYear == 'function';
};
/**
* Returns true if the specified value is a string
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a string.
*/
goog.isString = function(val) {
return typeof val == 'string';
};
/**
* Returns true if the specified value is a boolean
* @param {*} val Variable to test.
* @return {boolean} Whether variable is boolean.
*/
goog.isBoolean = function(val) {
return typeof val == 'boolean';
};
/**
* Returns true if the specified value is a number
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a number.
*/
goog.isNumber = function(val) {
return typeof val == 'number';
};
/**
* Returns true if the specified value is a function
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a function.
*/
goog.isFunction = function(val) {
return goog.typeOf(val) == 'function';
};
/**
* Returns true if the specified value is an object. This includes arrays
* and functions.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an object.
*/
goog.isObject = function(val) {
var type = goog.typeOf(val);
return type == 'object' || type == 'array' || type == 'function';
};
/**
* Adds a hash code field to an object. The hash code is unique for the
* given object.
* @param {Object} obj The object to get the hash code for.
* @return {number} The hash code for the object.
*/
goog.getHashCode = function(obj) {
// In IE, DOM nodes do not extend Object so they do not have this method.
// we need to check hasOwnProperty because the proto might have this set.
if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
return obj[goog.HASH_CODE_PROPERTY_];
}
if (!obj[goog.HASH_CODE_PROPERTY_]) {
obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
}
return obj[goog.HASH_CODE_PROPERTY_];
};
/**
* Removes the hash code field from an object.
* @param {Object} obj The object to remove the field from.
*/
goog.removeHashCode = function(obj) {
// DOM nodes in IE are not instance of Object and throws exception
// for delete. Instead we try to use removeAttribute
if ('removeAttribute' in obj) {
obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
}
/** @preserveTry */
try {
delete obj[goog.HASH_CODE_PROPERTY_];
} catch (ex) {
}
};
/**
* {String} Name for hash code property
* @private
*/
goog.HASH_CODE_PROPERTY_ = 'goog_hashCode_';
/**
* @type {number} Counter for hash codes.
* @private
*/
goog.hashCodeCounter_ = 0;
/**
* Clone an object/array (recursively)
* @param {Object} proto Object to clone.
* @return {Object} Clone of x;.
*/
goog.cloneObject = function(proto) {
var type = goog.typeOf(proto);
if (type == 'object' || type == 'array') {
if (proto.clone) {
return proto.clone();
}
var clone = type == 'array' ? [] : {};
for (var key in proto) {
clone[key] = goog.cloneObject(proto[key]);
}
return clone;
}
return proto;
};
/**
* Partially applies this function to a particular 'this object' and zero or
* more arguments. The result is a new function with some arguments of the first
* function pre-filled and the value of |this| 'pre-specified'.<br><br>
*
* Remaining arguments specified at call-time are appended to the pre-
* specified ones.<br><br>
*
* Also see: {@link #partial}.<br><br>
*
* Note that bind and partial are optimized such that repeated calls to it do
* not create more than one function object, so there is no additional cost for
* something like:<br>
*
* <pre>var g = bind(f, obj);
* var h = partial(g, 1, 2, 3);
* var k = partial(h, a, b, c);</pre>
*
* Usage:
* <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
* barMethBound('arg3', 'arg4');</pre>
*
* @param {Function} fn A function to partially apply.
* @param {Object} self Specifies the object which |this| should point to
* when the function is run. If the value is null or undefined, it will
* default to the global object.
* @param {Object} var_args Additional arguments that are partially
* applied to the function.
*
* @return {Function} A partially-applied form of the function bind() was
* invoked as a method of.
*/
goog.bind = function(fn, self, var_args) {
var boundArgs = fn.boundArgs_;
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
if (boundArgs) {
args.unshift.apply(args, boundArgs);
}
boundArgs = args;
}
self = fn.boundSelf_ || self;
fn = fn.boundFn_ || fn;
var newfn;
var context = self || goog.global;
if (boundArgs) {
newfn = function() {
// Combine the static args and the new args into one big array
var args = Array.prototype.slice.call(arguments);
args.unshift.apply(args, boundArgs);
return fn.apply(context, args);
}
} else {
newfn = function() {
return fn.apply(context, arguments);
}
}
newfn.boundArgs_ = boundArgs;
newfn.boundSelf_ = self;
newfn.boundFn_ = fn;
return newfn;
};
/**
* Like bind(), except that a 'this object' is not required. Useful when the
* target function is already bound.
*
* Usage:
* var g = partial(f, arg1, arg2);
* g(arg3, arg4);
*
* @param {Function} fn A function to partially apply.
* @param {Object} var_args Additional arguments that are partially
* applied to fn.
* @return {Function} A partially-applied form of the function bind() was
* invoked as a method of.
*/
goog.partial = function(fn, var_args) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift(fn, null);
return goog.bind.apply(null, args);
};
/**
* Copies all the members of a source object to a target object.
* This is deprecated. Use goog.object.extend instead.
* @param {Object} target Target.
* @param {Object} source Source.
* @deprecated
*/
goog.mixin = function(target, source) {
for (var x in source) {
target[x] = source[x];
}
// For IE the for-in-loop does not contain any properties that are not
// enumerable on the prototype object (for example, isPrototypeOf from
// Object.prototype) but also it will not include 'replace' on objects that
// extend String and change 'replace' (not that it is common for anyone to
// extend anything except Object).
};
/**
* A simple wrapper for new Date().getTime().
*
* @return {number} An integer value representing the number of milliseconds
* between midnight, January 1, 1970 and the current time.
*/
goog.now = Date.now || (function() {
return new Date().getTime();
});
/**
* Abstract implementation of goog.getMsg for use with localized messages
* @param {string} str Translatable string, places holders in the form.{$foo}
* @param {Object} opt_values Map of place holder name to value.
*/
goog.getMsg = function(str, opt_values) {
var values = opt_values || {};
for (var key in values) {
str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
}
return str;
};
/**
* Exposes an unobfuscated global namespace path for the given object.
* Note that fields of the exported object *will* be obfuscated,
* unless they are exported in turn via this function or
* goog.exportProperty
*
* <p>Also handy for making public items that are defined in anonymous
* closures.
*
* ex. goog.exportSymbol('Foo', Foo);
*
* ex. goog.exportSymbol('public.path.Foo.staticFunction',
* Foo.staticFunction);
* public.path.Foo.staticFunction();
*
* ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
* Foo.prototype.myMethod);
* new public.path.Foo().myMethod();
*
* @param {string} publicPath Unobfuscated name to export.
* @param {Object} object Object the name should point to.
*/
goog.exportSymbol = function(publicPath, object) {
goog.exportPath_(publicPath, object);
};
/**
* Exports a property unobfuscated into the object's namespace.
* ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
* ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
* @param {Object} object Object whose static property is being exported.
* @param {string} publicName Unobfuscated name to export.
* @param {Object} symbol Object the name should point to.
*/
goog.exportProperty = function(object, publicName, symbol) {
object[publicName] = symbol;
};
//==============================================================================
// Extending Function
//==============================================================================
/**
* An alias to the {@link goog.bind()} global function.
*
* Usage:
* var g = f.bind(obj, arg1, arg2);
* g(arg3, arg4);
*
* @param {Object} self Specifies the object to which |this| should point
* when the function is run. If the value is null or undefined, it will
* default to the global object.
* @param {Object} var_args Additional arguments that are partially
* applied to fn.
* @return {Function} A partially-applied form of the Function on which bind()
* was invoked as a method.
* @deprecated
*/
Function.prototype.bind = function(self, var_args) {
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift(this, self);
return goog.bind.apply(null, args);
} else {
return goog.bind(this, self);
}
};
/**
* An alias to the {@link goog.partial()} global function.
*
* Usage:
* var g = f.partial(arg1, arg2);
* g(arg3, arg4);
*
* @param {Object} var_args Additional arguments that are partially
* applied to fn.
* @return {Function} A partially-applied form of the function partial() was
* invoked as a method of.
* @deprecated
*/
Function.prototype.partial = function(var_args) {
var args = Array.prototype.slice.call(arguments);
args.unshift(this, null);
return goog.bind.apply(null, args);
};
/**
* Inherit the prototype methods from one constructor into another.
*
* Usage:
* <pre>
* function ParentClass(a, b) { }
* ParentClass.prototype.foo = function(a) { }
*
* function ChildClass(a, b, c) {
* ParentClass.call(this, a, b);
* }
*
* ChildClass.inherits(ParentClass);
*
* var child = new ChildClass('a', 'b', 'see');
* child.foo(); // works
* </pre>
*
* In addition, a superclass' implementation of a method can be invoked
* as follows:
*
* <pre>
* ChildClass.prototype.foo = function(a) {
* ChildClass.superClass_.foo.call(this, a);
* // other code
* };
* </pre>
*
* @param {Function} parentCtor Parent class.
*/
Function.prototype.inherits = function(parentCtor) {
goog.inherits(this, parentCtor);
};
/**
* Static variant of Function.prototype.inherits.
* @param {Function} childCtor Child class.
* @param {Function} parentCtor Parent class.
*/
goog.inherits = function(childCtor, parentCtor) {
/** @constructor */
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
/**
* Mixes in an object's properties and methods into the callee's prototype.
* Basically mixin based inheritance, thus providing an alternative method for
* adding properties and methods to a class' prototype.
*
* <pre>
* function X() {}
* X.mixin({
* one: 1,
* two: 2,
* three: 3,
* doit: function() { return this.one + this.two + this.three; }
* });
*
* function Y() { }
* Y.mixin(X.prototype);
* Y.prototype.four = 15;
* Y.prototype.doit2 = function() { return this.doit() + this.four; }
* });
*
* // or
*
* function Y() { }
* Y.inherits(X);
* Y.mixin({
* one: 10,
* four: 15,
* doit2: function() { return this.doit() + this.four; }
* });
* </pre>
*
* @param {Object} source from which to copy properties.
* @see goog.mixin
* @deprecated
*/
Function.prototype.mixin = function(source) {
goog.mixin(this.prototype, source);
};
/* inspector_controller_impl.js */
// Copyright (c) 2009 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.
/**
* @fileoverview DevTools' implementation of the InspectorController API.
*/
goog.provide('devtools.InspectorBackendImpl');
goog.provide('devtools.InspectorFrontendHostImpl');
devtools.InspectorBackendImpl = function() {
WebInspector.InspectorBackendStub.call(this);
this.installInspectorControllerDelegate_('clearMessages');
this.installInspectorControllerDelegate_('copyNode');
this.installInspectorControllerDelegate_('deleteCookie');
this.installInspectorControllerDelegate_('disableResourceTracking');
this.installInspectorControllerDelegate_('disableTimeline');
this.installInspectorControllerDelegate_('enableResourceTracking');
this.installInspectorControllerDelegate_('enableTimeline');
this.installInspectorControllerDelegate_('getChildNodes');
this.installInspectorControllerDelegate_('getCookies');
this.installInspectorControllerDelegate_('getDatabaseTableNames');
this.installInspectorControllerDelegate_('getDOMStorageEntries');
this.installInspectorControllerDelegate_('getEventListenersForNode');
this.installInspectorControllerDelegate_('highlightDOMNode');
this.installInspectorControllerDelegate_('hideDOMNodeHighlight');
this.installInspectorControllerDelegate_('releaseWrapperObjectGroup');
this.installInspectorControllerDelegate_('removeAttribute');
this.installInspectorControllerDelegate_('removeDOMStorageItem');
this.installInspectorControllerDelegate_('removeNode');
this.installInspectorControllerDelegate_('setAttribute');
this.installInspectorControllerDelegate_('setDOMStorageItem');
this.installInspectorControllerDelegate_('setTextNodeValue');
this.installInspectorControllerDelegate_('startTimelineProfiler');
this.installInspectorControllerDelegate_('stopTimelineProfiler');
this.installInspectorControllerDelegate_('storeLastActivePanel');
};
goog.inherits(devtools.InspectorBackendImpl,
WebInspector.InspectorBackendStub);
/**
* {@inheritDoc}.
*/
devtools.InspectorBackendImpl.prototype.toggleNodeSearch = function() {
WebInspector.InspectorBackendStub.prototype.toggleNodeSearch.call(this);
this.callInspectorController_.call(this, 'toggleNodeSearch');
if (!this.searchingForNode()) {
// This is called from ElementsPanel treeOutline's focusNodeChanged().
DevToolsHost.activateWindow();
}
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.debuggerEnabled = function() {
return true;
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.profilerEnabled = function() {
return true;
};
devtools.InspectorBackendImpl.prototype.addBreakpoint = function(
sourceID, line, condition) {
devtools.tools.getDebuggerAgent().addBreakpoint(sourceID, line, condition);
};
devtools.InspectorBackendImpl.prototype.removeBreakpoint = function(
sourceID, line) {
devtools.tools.getDebuggerAgent().removeBreakpoint(sourceID, line);
};
devtools.InspectorBackendImpl.prototype.updateBreakpoint = function(
sourceID, line, condition) {
devtools.tools.getDebuggerAgent().updateBreakpoint(
sourceID, line, condition);
};
devtools.InspectorBackendImpl.prototype.pauseInDebugger = function() {
devtools.tools.getDebuggerAgent().pauseExecution();
};
devtools.InspectorBackendImpl.prototype.resumeDebugger = function() {
devtools.tools.getDebuggerAgent().resumeExecution();
};
devtools.InspectorBackendImpl.prototype.stepIntoStatementInDebugger =
function() {
devtools.tools.getDebuggerAgent().stepIntoStatement();
};
devtools.InspectorBackendImpl.prototype.stepOutOfFunctionInDebugger =
function() {
devtools.tools.getDebuggerAgent().stepOutOfFunction();
};
devtools.InspectorBackendImpl.prototype.stepOverStatementInDebugger =
function() {
devtools.tools.getDebuggerAgent().stepOverStatement();
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.pauseOnExceptions = function() {
return devtools.tools.getDebuggerAgent().pauseOnExceptions();
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.setPauseOnExceptions = function(
value) {
return devtools.tools.getDebuggerAgent().setPauseOnExceptions(value);
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.startProfiling = function() {
devtools.tools.getDebuggerAgent().startProfiling(
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.stopProfiling = function() {
devtools.tools.getDebuggerAgent().stopProfiling(
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.getProfileHeaders = function(callId) {
WebInspector.didGetProfileHeaders(callId, []);
};
/**
* Emulate WebKit InspectorController behavior. It stores profiles on renderer side,
* and is able to retrieve them by uid using 'getProfile'.
*/
devtools.InspectorBackendImpl.prototype.addFullProfile = function(profile) {
WebInspector.__fullProfiles = WebInspector.__fullProfiles || {};
WebInspector.__fullProfiles[profile.uid] = profile;
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.getProfile = function(callId, uid) {
if (WebInspector.__fullProfiles && (uid in WebInspector.__fullProfiles)) {
WebInspector.didGetProfile(callId, WebInspector.__fullProfiles[uid]);
}
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.takeHeapSnapshot = function() {
devtools.tools.getDebuggerAgent().startProfiling(
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT
| devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_STATS
| devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_JS_CONSTRUCTORS);
};
/**
* @override
*/
devtools.InspectorBackendImpl.prototype.dispatchOnInjectedScript = function(
callId, methodName, argsString, async) {
var callback = function(result, isException) {
WebInspector.didDispatchOnInjectedScript(callId, result, isException);
};
RemoteToolsAgent.DispatchOnInjectedScript(
WebInspector.Callback.wrap(callback),
async ? methodName + "_async" : methodName,
argsString);
};
/**
* Installs delegating handler into the inspector controller.
* @param {string} methodName Method to install delegating handler for.
*/
devtools.InspectorBackendImpl.prototype.installInspectorControllerDelegate_
= function(methodName) {
this[methodName] = goog.bind(this.callInspectorController_, this,
methodName);
};
/**
* Bound function with the installInjectedScriptDelegate_ actual
* implementation.
*/
devtools.InspectorBackendImpl.prototype.callInspectorController_ =
function(methodName, var_arg) {
var args = Array.prototype.slice.call(arguments, 1);
RemoteToolsAgent.DispatchOnInspectorController(
WebInspector.Callback.wrap(function(){}),
methodName,
JSON.stringify(args));
};
InspectorBackend = new devtools.InspectorBackendImpl();
/* debugger_agent.js */
// Copyright (c) 2009 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.
/**
* @fileoverview Provides communication interface to remote v8 debugger. See
* protocol decription at http://code.google.com/p/v8/wiki/DebuggerProtocol
*/
goog.provide('devtools.DebuggerAgent');
/**
* @constructor
*/
devtools.DebuggerAgent = function() {
RemoteDebuggerAgent.DebuggerOutput =
goog.bind(this.handleDebuggerOutput_, this);
RemoteDebuggerAgent.SetContextId =
goog.bind(this.setContextId_, this);
RemoteDebuggerAgent.DidGetActiveProfilerModules =
goog.bind(this.didGetActiveProfilerModules_, this);
RemoteDebuggerAgent.DidGetNextLogLines =
goog.bind(this.didGetNextLogLines_, this);
/**
* Id of the inspected page global context. It is used for filtering scripts.
* @type {number}
*/
this.contextId_ = null;
/**
* Mapping from script id to script info.
* @type {Object}
*/
this.parsedScripts_ = null;
/**
* Mapping from the request id to the devtools.BreakpointInfo for the
* breakpoints whose v8 ids are not set yet. These breakpoints are waiting for
* 'setbreakpoint' responses to learn their ids in the v8 debugger.
* @see #handleSetBreakpointResponse_
* @type {Object}
*/
this.requestNumberToBreakpointInfo_ = null;
/**
* Information on current stack frames.
* @type {Array.<devtools.CallFrame>}
*/
this.callFrames_ = [];
/**
* Whether to stop in the debugger on the exceptions.
* @type {boolean}
*/
this.pauseOnExceptions_ = false;
/**
* Mapping: request sequence number->callback.
* @type {Object}
*/
this.requestSeqToCallback_ = null;
/**
* Whether the scripts panel has been shown and initialilzed.
* @type {boolean}
*/
this.scriptsPanelInitialized_ = false;
/**
* Whether the scripts list should be requested next time when context id is
* set.
* @type {boolean}
*/
this.requestScriptsWhenContextIdSet_ = false;
/**
* Whether the agent is waiting for initial scripts response.
* @type {boolean}
*/
this.waitingForInitialScriptsResponse_ = false;
/**
* If backtrace response is received when initial scripts response
* is not yet processed the backtrace handling will be postponed until
* after the scripts response processing. The handler bound to its arguments
* and this agent will be stored in this field then.
* @type {?function()}
*/
this.pendingBacktraceResponseHandler_ = null;
/**
* Active profiler modules flags.
* @type {number}
*/
this.activeProfilerModules_ =
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;
/**
* Interval for polling profiler state.
* @type {number}
*/
this.getActiveProfilerModulesInterval_ = null;
/**
* Whether log contents retrieval must be forced next time.
* @type {boolean}
*/
this.forceGetLogLines_ = false;
/**
* Profiler processor instance.
* @type {devtools.profiler.Processor}
*/
this.profilerProcessor_ = new devtools.profiler.Processor();
/**
* Container of all breakpoints set using resource URL. These breakpoints
* survive page reload. Breakpoints set by script id(for scripts that don't
* have URLs) are stored in ScriptInfo objects.
* @type {Object}
*/
this.urlToBreakpoints_ = {};
/**
* Exception message that is shown to user while on exception break.
* @type {WebInspector.ConsoleMessage}
*/
this.currentExceptionMessage_ = null;
};
/**
* A copy of the scope types from v8/src/mirror-delay.js
* @enum {number}
*/
devtools.DebuggerAgent.ScopeType = {
Global: 0,
Local: 1,
With: 2,
Closure: 3,
Catch: 4
};
/**
* A copy of enum from include/v8.h
* @enum {number}
*/
devtools.DebuggerAgent.ProfilerModules = {
PROFILER_MODULE_NONE: 0,
PROFILER_MODULE_CPU: 1,
PROFILER_MODULE_HEAP_STATS: 1 << 1,
PROFILER_MODULE_JS_CONSTRUCTORS: 1 << 2,
PROFILER_MODULE_HEAP_SNAPSHOT: 1 << 16
};
/**
* Resets debugger agent to its initial state.
*/
devtools.DebuggerAgent.prototype.reset = function() {
this.contextId_ = null;
// No need to request scripts since they all will be pushed in AfterCompile
// events.
this.requestScriptsWhenContextIdSet_ = false;
this.waitingForInitialScriptsResponse_ = false;
this.parsedScripts_ = {};
this.requestNumberToBreakpointInfo_ = {};
this.callFrames_ = [];
this.requestSeqToCallback_ = {};
// Profiler isn't reset because it contains no data that is
// specific for a particular V8 instance. All such data is
// managed by an agent on the Render's side.
};
/**
* Initializes scripts UI. This method is called every time Scripts panel
* is shown. It will send request for context id if it's not set yet.
*/
devtools.DebuggerAgent.prototype.initUI = function() {
// Initialize scripts cache when Scripts panel is shown first time.
if (this.scriptsPanelInitialized_) {
return;
}
this.scriptsPanelInitialized_ = true;
if (this.contextId_) {
// We already have context id. This means that we are here from the
// very beginning of the page load cycle and hence will get all scripts
// via after-compile events. No need to request scripts for this session.
//
// There can be a number of scripts from after-compile events that are
// pending addition into the UI.
for (var scriptId in this.parsedScripts_) {
var script = this.parsedScripts_[scriptId];
WebInspector.parsedScriptSource(scriptId, script.getUrl(),
undefined /* script source */, script.getLineOffset());
}
return;
}
this.waitingForInitialScriptsResponse_ = true;
// Script list should be requested only when current context id is known.
RemoteDebuggerAgent.GetContextId();
this.requestScriptsWhenContextIdSet_ = true;
};
/**
* Asynchronously requests the debugger for the script source.
* @param {number} scriptId Id of the script whose source should be resolved.
* @param {function(source:?string):void} callback Function that will be called
* when the source resolution is completed. 'source' parameter will be null
* if the resolution fails.
*/
devtools.DebuggerAgent.prototype.resolveScriptSource = function(
scriptId, callback) {
var script = this.parsedScripts_[scriptId];
if (!script || script.isUnresolved()) {
callback(null);
return;
}
var cmd = new devtools.DebugCommand('scripts', {
'ids': [scriptId],
'includeSource': true
});
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
RemoteToolsAgent.ExecuteVoidJavaScript();
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
if (msg.isSuccess()) {
var scriptJson = msg.getBody()[0];
callback(scriptJson.source);
} else {
callback(null);
}
};
};
/**
* Tells the v8 debugger to stop on as soon as possible.
*/
devtools.DebuggerAgent.prototype.pauseExecution = function() {
RemoteDebuggerCommandExecutor.DebuggerPauseScript();
};
/**
* @param {number} sourceId Id of the script fot the breakpoint.
* @param {number} line Number of the line for the breakpoint.
* @param {?string} condition The breakpoint condition.
*/
devtools.DebuggerAgent.prototype.addBreakpoint = function(
sourceId, line, condition) {
var script = this.parsedScripts_[sourceId];
if (!script) {
return;
}
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var commandArguments;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
if (breakpoints && breakpoints[line]) {
return;
}
if (!breakpoints) {
breakpoints = {};
this.urlToBreakpoints_[script.getUrl()] = breakpoints;
}
var breakpointInfo = new devtools.BreakpointInfo(line);
breakpoints[line] = breakpointInfo;
commandArguments = {
'groupId': this.contextId_,
'type': 'script',
'target': script.getUrl(),
'line': line,
'condition': condition
};
} else {
var breakpointInfo = script.getBreakpointInfo(line);
if (breakpointInfo) {
return;
}
breakpointInfo = new devtools.BreakpointInfo(line);
script.addBreakpointInfo(breakpointInfo);
commandArguments = {
'groupId': this.contextId_,
'type': 'scriptId',
'target': sourceId,
'line': line,
'condition': condition
};
}
var cmd = new devtools.DebugCommand('setbreakpoint', commandArguments);
this.requestNumberToBreakpointInfo_[cmd.getSequenceNumber()] = breakpointInfo;
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
// It is necessary for being able to change a breakpoint just after it
// has been created (since we need an existing breakpoint id for that).
RemoteToolsAgent.ExecuteVoidJavaScript();
};
/**
* @param {number} sourceId Id of the script for the breakpoint.
* @param {number} line Number of the line for the breakpoint.
*/
devtools.DebuggerAgent.prototype.removeBreakpoint = function(sourceId, line) {
var script = this.parsedScripts_[sourceId];
if (!script) {
return;
}
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var breakpointInfo;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
breakpointInfo = breakpoints[line];
delete breakpoints[line];
} else {
breakpointInfo = script.getBreakpointInfo(line);
if (breakpointInfo) {
script.removeBreakpointInfo(breakpointInfo);
}
}
if (!breakpointInfo) {
return;
}
breakpointInfo.markAsRemoved();
var id = breakpointInfo.getV8Id();
// If we don't know id of this breakpoint in the v8 debugger we cannot send
// 'clearbreakpoint' request. In that case it will be removed in
// 'setbreakpoint' response handler when we learn the id.
if (id != -1) {
this.requestClearBreakpoint_(id);
}
};
/**
* @param {number} sourceId Id of the script for the breakpoint.
* @param {number} line Number of the line for the breakpoint.
* @param {?string} condition New breakpoint condition.
*/
devtools.DebuggerAgent.prototype.updateBreakpoint = function(
sourceId, line, condition) {
var script = this.parsedScripts_[sourceId];
if (!script) {
return;
}
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var breakpointInfo;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
breakpointInfo = breakpoints[line];
} else {
breakpointInfo = script.getBreakpointInfo(line);
}
var id = breakpointInfo.getV8Id();
// If we don't know id of this breakpoint in the v8 debugger we cannot send
// the 'changebreakpoint' request.
if (id != -1) {
// TODO(apavlov): make use of the real values for 'enabled' and
// 'ignoreCount' when appropriate.
this.requestChangeBreakpoint_(id, true, condition, null);
}
};
/**
* Tells the v8 debugger to step into the next statement.
*/
devtools.DebuggerAgent.prototype.stepIntoStatement = function() {
this.stepCommand_('in');
};
/**
* Tells the v8 debugger to step out of current function.
*/
devtools.DebuggerAgent.prototype.stepOutOfFunction = function() {
this.stepCommand_('out');
};
/**
* Tells the v8 debugger to step over the next statement.
*/
devtools.DebuggerAgent.prototype.stepOverStatement = function() {
this.stepCommand_('next');
};
/**
* Tells the v8 debugger to continue execution after it has been stopped on a
* breakpoint or an exception.
*/
devtools.DebuggerAgent.prototype.resumeExecution = function() {
this.clearExceptionMessage_();
var cmd = new devtools.DebugCommand('continue');
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Creates exception message and schedules it for addition to the resource upon
* backtrace availability.
* @param {string} url Resource url.
* @param {number} line Resource line number.
* @param {string} message Exception text.
*/
devtools.DebuggerAgent.prototype.createExceptionMessage_ = function(
url, line, message) {
this.currentExceptionMessage_ = new WebInspector.ConsoleMessage(
WebInspector.ConsoleMessage.MessageSource.JS,
WebInspector.ConsoleMessage.MessageType.Log,
WebInspector.ConsoleMessage.MessageLevel.Error,
line,
url,
0 /* group level */,
1 /* repeat count */,
'[Exception] ' + message);
};
/**
* Shows pending exception message that is created with createExceptionMessage_
* earlier.
*/
devtools.DebuggerAgent.prototype.showPendingExceptionMessage_ = function() {
if (!this.currentExceptionMessage_) {
return;
}
var msg = this.currentExceptionMessage_;
var resource = WebInspector.resourceURLMap[msg.url];
if (resource) {
msg.resource = resource;
WebInspector.panels.resources.addMessageToResource(resource, msg);
} else {
this.currentExceptionMessage_ = null;
}
};
/**
* Clears exception message from the resource.
*/
devtools.DebuggerAgent.prototype.clearExceptionMessage_ = function() {
if (this.currentExceptionMessage_) {
var messageElement =
this.currentExceptionMessage_._resourceMessageLineElement;
var bubble = messageElement.parentElement;
bubble.removeChild(messageElement);
if (!bubble.firstChild) {
// Last message in bubble removed.
bubble.parentElement.removeChild(bubble);
}
this.currentExceptionMessage_ = null;
}
};
/**
* @return {boolean} True iff the debugger will pause execution on the
* exceptions.
*/
devtools.DebuggerAgent.prototype.pauseOnExceptions = function() {
return this.pauseOnExceptions_;
};
/**
* Tells whether to pause in the debugger on the exceptions or not.
* @param {boolean} value True iff execution should be stopped in the debugger
* on the exceptions.
*/
devtools.DebuggerAgent.prototype.setPauseOnExceptions = function(value) {
this.pauseOnExceptions_ = value;
};
/**
* Sends 'evaluate' request to the debugger.
* @param {Object} arguments Request arguments map.
* @param {function(devtools.DebuggerMessage)} callback Callback to be called
* when response is received.
*/
devtools.DebuggerAgent.prototype.requestEvaluate = function(
arguments, callback) {
var cmd = new devtools.DebugCommand('evaluate', arguments);
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};
/**
* Sends 'lookup' request for each unresolved property of the object. When
* response is received the properties will be changed with their resolved
* values.
* @param {Object} object Object whose properties should be resolved.
* @param {function(devtools.DebuggerMessage)} Callback to be called when all
* children are resolved.
* @param {boolean} noIntrinsic Whether intrinsic properties should be included.
*/
devtools.DebuggerAgent.prototype.resolveChildren = function(object, callback,
noIntrinsic) {
if ('handle' in object) {
var result = [];
devtools.DebuggerAgent.formatObjectProperties_(object, result,
noIntrinsic);
callback(result);
} else {
this.requestLookup_([object.ref], function(msg) {
var result = [];
if (msg.isSuccess()) {
var handleToObject = msg.getBody();
var resolved = handleToObject[object.ref];
devtools.DebuggerAgent.formatObjectProperties_(resolved, result,
noIntrinsic);
callback(result);
} else {
callback([]);
}
});
}
};
/**
* Sends 'scope' request for the scope object to resolve its variables.
* @param {Object} scope Scope to be resolved.
* @param {function(Array.<WebInspector.ObjectPropertyProxy>)} callback
* Callback to be called when all scope variables are resolved.
*/
devtools.DebuggerAgent.prototype.resolveScope = function(scope, callback) {
var cmd = new devtools.DebugCommand('scope', {
'frameNumber': scope.frameNumber,
'number': scope.index,
'compactFormat': true
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
var result = [];
if (msg.isSuccess()) {
var scopeObjectJson = msg.getBody().object;
devtools.DebuggerAgent.formatObjectProperties_(scopeObjectJson, result,
true /* no intrinsic */);
}
callback(result);
};
};
/**
* Sends 'scopes' request for the frame object to resolve all variables
* available in the frame.
* @param {number} callFrameId Id of call frame whose variables need to
* be resolved.
* @param {function(Object)} callback Callback to be called when all frame
* variables are resolved.
*/
devtools.DebuggerAgent.prototype.resolveFrameVariables_ = function(
callFrameId, callback) {
var result = {};
var frame = this.callFrames_[callFrameId];
if (!frame) {
callback(result);
return;
}
var waitingResponses = 0;
function scopeResponseHandler(msg) {
waitingResponses--;
if (msg.isSuccess()) {
var properties = msg.getBody().object.properties;
for (var j = 0; j < properties.length; j++) {
result[properties[j].name] = true;
}
}
// When all scopes are resolved invoke the callback.
if (waitingResponses == 0) {
callback(result);
}
};
for (var i = 0; i < frame.scopeChain.length; i++) {
var scope = frame.scopeChain[i].objectId;
if (scope.type == devtools.DebuggerAgent.ScopeType.Global) {
// Do not resolve global scope since it takes for too long.
// TODO(yurys): allow to send only property names in the response.
continue;
}
var cmd = new devtools.DebugCommand('scope', {
'frameNumber': scope.frameNumber,
'number': scope.index,
'compactFormat': true
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] =
scopeResponseHandler;
waitingResponses++;
}
};
/**
* Evaluates the expressionString to an object in the call frame and reports
* all its properties.
* @param{string} expressionString Expression whose properties should be
* collected.
* @param{number} callFrameId The frame id.
* @param{function(Object result,bool isException)} reportCompletions Callback
* function.
*/
devtools.DebuggerAgent.prototype.resolveCompletionsOnFrame = function(
expressionString, callFrameId, reportCompletions) {
if (expressionString) {
expressionString = 'var obj = ' + expressionString +
'; var names = {}; for (var n in obj) { names[n] = true; };' +
'names;';
this.evaluateInCallFrame(
callFrameId,
expressionString,
function(result) {
var names = {};
if (!result.isException) {
var props = result.value.objectId.properties;
// Put all object properties into the map.
for (var i = 0; i < props.length; i++) {
names[props[i].name] = true;
}
}
reportCompletions(names, result.isException);
});
} else {
this.resolveFrameVariables_(callFrameId,
function(result) {
reportCompletions(result, false /* isException */);
});
}
};
/**
* Sets up callbacks that deal with profiles processing.
*/
devtools.DebuggerAgent.prototype.setupProfilerProcessorCallbacks = function() {
// A temporary icon indicating that the profile is being processed.
var processingIcon = new WebInspector.SidebarTreeElement(
'profile-sidebar-tree-item',
WebInspector.UIString('Processing...'),
'', null, false);
var profilesSidebar = WebInspector.panels.profiles.getProfileType(
WebInspector.CPUProfileType.TypeId).treeElement;
this.profilerProcessor_.setCallbacks(
function onProfileProcessingStarted() {
// Set visually empty string. Subtitle hiding is done via styles
// manipulation which doesn't play well with dynamic append / removal.
processingIcon.subtitle = ' ';
profilesSidebar.appendChild(processingIcon);
},
function onProfileProcessingStatus(ticksCount) {
processingIcon.subtitle =
WebInspector.UIString('%d ticks processed', ticksCount);
},
function onProfileProcessingFinished(profile) {
profilesSidebar.removeChild(processingIcon);
profile.typeId = WebInspector.CPUProfileType.TypeId;
InspectorBackend.addFullProfile(profile);
WebInspector.addProfileHeader(profile);
// If no profile is currently shown, show the new one.
var profilesPanel = WebInspector.panels.profiles;
if (!profilesPanel.visibleView) {
profilesPanel.showProfile(profile);
}
}
);
};
/**
* Initializes profiling state.
*/
devtools.DebuggerAgent.prototype.initializeProfiling = function() {
this.setupProfilerProcessorCallbacks();
this.forceGetLogLines_ = true;
this.getActiveProfilerModulesInterval_ = setInterval(
function() { RemoteDebuggerAgent.GetActiveProfilerModules(); }, 1000);
};
/**
* Starts profiling.
* @param {number} modules List of modules to enable.
*/
devtools.DebuggerAgent.prototype.startProfiling = function(modules) {
RemoteDebuggerAgent.StartProfiling(modules);
if (modules &
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
// Active modules will not change, instead, a snapshot will be logged.
RemoteDebuggerAgent.GetNextLogLines();
}
};
/**
* Stops profiling.
*/
devtools.DebuggerAgent.prototype.stopProfiling = function(modules) {
RemoteDebuggerAgent.StopProfiling(modules);
};
/**
* @param{number} scriptId
* @return {string} Type of the context of the script with specified id.
*/
devtools.DebuggerAgent.prototype.getScriptContextType = function(scriptId) {
return this.parsedScripts_[scriptId].getContextType();
};
/**
* Removes specified breakpoint from the v8 debugger.
* @param {number} breakpointId Id of the breakpoint in the v8 debugger.
*/
devtools.DebuggerAgent.prototype.requestClearBreakpoint_ = function(
breakpointId) {
var cmd = new devtools.DebugCommand('clearbreakpoint', {
'breakpoint': breakpointId
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Changes breakpoint parameters in the v8 debugger.
* @param {number} breakpointId Id of the breakpoint in the v8 debugger.
* @param {boolean} enabled Whether to enable the breakpoint.
* @param {?string} condition New breakpoint condition.
* @param {number} ignoreCount New ignore count for the breakpoint.
*/
devtools.DebuggerAgent.prototype.requestChangeBreakpoint_ = function(
breakpointId, enabled, condition, ignoreCount) {
var cmd = new devtools.DebugCommand('changebreakpoint', {
'breakpoint': breakpointId,
'enabled': enabled,
'condition': condition,
'ignoreCount': ignoreCount
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends 'backtrace' request to v8.
*/
devtools.DebuggerAgent.prototype.requestBacktrace_ = function() {
var cmd = new devtools.DebugCommand('backtrace', {
'compactFormat':true
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends command to v8 debugger.
* @param {devtools.DebugCommand} cmd Command to execute.
*/
devtools.DebuggerAgent.sendCommand_ = function(cmd) {
RemoteDebuggerCommandExecutor.DebuggerCommand(cmd.toJSONProtocol());
};
/**
* Tells the v8 debugger to make the next execution step.
* @param {string} action 'in', 'out' or 'next' action.
*/
devtools.DebuggerAgent.prototype.stepCommand_ = function(action) {
this.clearExceptionMessage_();
var cmd = new devtools.DebugCommand('continue', {
'stepaction': action,
'stepcount': 1
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends 'lookup' request to v8.
* @param {number} handle Handle to the object to lookup.
*/
devtools.DebuggerAgent.prototype.requestLookup_ = function(handles, callback) {
var cmd = new devtools.DebugCommand('lookup', {
'compactFormat':true,
'handles': handles
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};
/**
* Sets debugger context id for scripts filtering.
* @param {number} contextId Id of the inspected page global context.
*/
devtools.DebuggerAgent.prototype.setContextId_ = function(contextId) {
this.contextId_ = contextId;
// If it's the first time context id is set request scripts list.
if (this.requestScriptsWhenContextIdSet_) {
this.requestScriptsWhenContextIdSet_ = false;
var cmd = new devtools.DebugCommand('scripts', {
'includeSource': false
});
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
RemoteToolsAgent.ExecuteVoidJavaScript();
var debuggerAgent = this;
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
// Handle the response iff the context id hasn't changed since the request
// was issued. Otherwise if the context id did change all up-to-date
// scripts will be pushed in after compile events and there is no need to
// handle the response.
if (contextId == debuggerAgent.contextId_) {
debuggerAgent.handleScriptsResponse_(msg);
}
// We received initial scripts response so flush the flag and
// see if there is an unhandled backtrace response.
debuggerAgent.waitingForInitialScriptsResponse_ = false;
if (debuggerAgent.pendingBacktraceResponseHandler_) {
debuggerAgent.pendingBacktraceResponseHandler_();
debuggerAgent.pendingBacktraceResponseHandler_ = null;
}
};
}
};
/**
* Handles output sent by v8 debugger. The output is either asynchronous event
* or response to a previously sent request. See protocol definitioun for more
* details on the output format.
* @param {string} output
*/
devtools.DebuggerAgent.prototype.handleDebuggerOutput_ = function(output) {
var msg;
try {
msg = new devtools.DebuggerMessage(output);
} catch(e) {
debugPrint('Failed to handle debugger reponse:\n' + e);
throw e;
}
if (msg.getType() == 'event') {
if (msg.getEvent() == 'break') {
this.handleBreakEvent_(msg);
} else if (msg.getEvent() == 'exception') {
this.handleExceptionEvent_(msg);
} else if (msg.getEvent() == 'afterCompile') {
this.handleAfterCompileEvent_(msg);
}
} else if (msg.getType() == 'response') {
if (msg.getCommand() == 'scripts') {
this.invokeCallbackForResponse_(msg);
} else if (msg.getCommand() == 'setbreakpoint') {
this.handleSetBreakpointResponse_(msg);
} else if (msg.getCommand() == 'clearbreakpoint') {
this.handleClearBreakpointResponse_(msg);
} else if (msg.getCommand() == 'backtrace') {
this.handleBacktraceResponse_(msg);
} else if (msg.getCommand() == 'lookup') {
this.invokeCallbackForResponse_(msg);
} else if (msg.getCommand() == 'evaluate') {
this.invokeCallbackForResponse_(msg);
} else if (msg.getCommand() == 'scope') {
this.invokeCallbackForResponse_(msg);
}
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleBreakEvent_ = function(msg) {
// Force scrips panel to be shown first.
WebInspector.currentPanel = WebInspector.panels.scripts;
var body = msg.getBody();
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
this.requestBacktrace_();
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleExceptionEvent_ = function(msg) {
// Force scrips panel to be shown first.
WebInspector.currentPanel = WebInspector.panels.scripts;
var body = msg.getBody();
// No script field in the body means that v8 failed to parse the script. We
// resume execution on parser errors automatically.
if (this.pauseOnExceptions_ && body.script) {
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
this.createExceptionMessage_(body.script.name, line, body.exception.text);
this.requestBacktrace_();
} else {
this.resumeExecution();
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleScriptsResponse_ = function(msg) {
var scripts = msg.getBody();
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
// Skip scripts from other tabs.
if (!this.isScriptFromInspectedContext_(script, msg)) {
continue;
}
// We may already have received the info in an afterCompile event.
if (script.id in this.parsedScripts_) {
continue;
}
this.addScriptInfo_(script, msg);
}
};
/**
* @param {Object} script Json object representing script.
* @param {devtools.DebuggerMessage} msg Debugger response.
*/
devtools.DebuggerAgent.prototype.isScriptFromInspectedContext_ = function(
script, msg) {
if (!script.context) {
// Always ignore scripts from the utility context.
return false;
}
var context = msg.lookup(script.context.ref);
var scriptContextId = context.data;
if (!goog.isDef(scriptContextId)) {
return false; // Always ignore scripts from the utility context.
}
if (this.contextId_ === null) {
return true;
}
if (goog.isString(context.data)) {
// Find the id from context data. The context data has the format "type,id".
var comma = context.data.indexOf(',');
if (comma < 0) {
return false;
}
return (parseInt(context.data.substring(comma + 1)) == this.contextId_);
} else {
// TODO(sgjesse) remove this when patch for
// https://bugs.webkit.org/show_bug.cgi?id=31873 has landed in Chromium.
return (scriptContextId.value == this.contextId_);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleSetBreakpointResponse_ = function(msg) {
var requestSeq = msg.getRequestSeq();
var breakpointInfo = this.requestNumberToBreakpointInfo_[requestSeq];
if (!breakpointInfo) {
// TODO(yurys): handle this case
return;
}
delete this.requestNumberToBreakpointInfo_[requestSeq];
if (!msg.isSuccess()) {
// TODO(yurys): handle this case
return;
}
var idInV8 = msg.getBody().breakpoint;
breakpointInfo.setV8Id(idInV8);
if (breakpointInfo.isRemoved()) {
this.requestClearBreakpoint_(idInV8);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleAfterCompileEvent_ = function(msg) {
if (!this.contextId_) {
// Ignore scripts delta if main request has not been issued yet.
return;
}
var script = msg.getBody().script;
// Ignore scripts from other tabs.
if (!this.isScriptFromInspectedContext_(script, msg)) {
return;
}
this.addScriptInfo_(script, msg);
};
/**
* Handles current profiler status.
* @param {number} modules List of active (started) modules.
*/
devtools.DebuggerAgent.prototype.didGetActiveProfilerModules_ = function(
modules) {
var profModules = devtools.DebuggerAgent.ProfilerModules;
var profModuleNone = profModules.PROFILER_MODULE_NONE;
if (this.forceGetLogLines_ ||
(modules != profModuleNone &&
this.activeProfilerModules_ == profModuleNone)) {
this.forceGetLogLines_ = false;
// Start to query log data.
RemoteDebuggerAgent.GetNextLogLines();
}
this.activeProfilerModules_ = modules;
// Update buttons.
WebInspector.setRecordingProfile(modules & profModules.PROFILER_MODULE_CPU);
};
/**
* Handles a portion of a profiler log retrieved by GetNextLogLines call.
* @param {string} log A portion of profiler log.
*/
devtools.DebuggerAgent.prototype.didGetNextLogLines_ = function(log) {
if (log.length > 0) {
this.profilerProcessor_.processLogChunk(log);
} else if (this.activeProfilerModules_ ==
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE) {
// No new data and profiling is stopped---suspend log reading.
return;
}
setTimeout(function() { RemoteDebuggerAgent.GetNextLogLines(); }, 500);
};
/**
* Adds the script info to the local cache. This method assumes that the script
* is not in the cache yet.
* @param {Object} script Script json object from the debugger message.
* @param {devtools.DebuggerMessage} msg Debugger message containing the script
* data.
*/
devtools.DebuggerAgent.prototype.addScriptInfo_ = function(script, msg) {
var context = msg.lookup(script.context.ref);
var contextType;
if (goog.isString(context.data)) {
// Find the type from context data. The context data has the format
// "type,id".
var comma = context.data.indexOf(',');
if (comma < 0) {
return
}
contextType = context.data.substring(0, comma);
} else {
// TODO(sgjesse) remove this when patch for
// https://bugs.webkit.org/show_bug.cgi?id=31873 has landed in Chromium.
contextType = context.data.type;
}
this.parsedScripts_[script.id] = new devtools.ScriptInfo(
script.id, script.name, script.lineOffset, contextType);
if (this.scriptsPanelInitialized_) {
// Only report script as parsed after scripts panel has been shown.
WebInspector.parsedScriptSource(
script.id, script.name, script.source, script.lineOffset);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleClearBreakpointResponse_ = function(
msg) {
// Do nothing.
};
/**
* Handles response to 'backtrace' command.
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleBacktraceResponse_ = function(msg) {
if (this.waitingForInitialScriptsResponse_) {
this.pendingBacktraceResponseHandler_ =
this.doHandleBacktraceResponse_.bind(this, msg);
} else {
this.doHandleBacktraceResponse_(msg);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.doHandleBacktraceResponse_ = function(msg) {
var frames = msg.getBody().frames;
this.callFrames_ = [];
for (var i = 0; i < frames.length; ++i) {
this.callFrames_.push(this.formatCallFrame_(frames[i]));
}
WebInspector.pausedScript(this.callFrames_);
this.showPendingExceptionMessage_();
InspectorFrontendHost.activateWindow();
};
/**
* Evaluates code on given callframe.
*/
devtools.DebuggerAgent.prototype.evaluateInCallFrame = function(
callFrameId, code, callback) {
var callFrame = this.callFrames_[callFrameId];
callFrame.evaluate_(code, callback);
};
/**
* Handles response to a command by invoking its callback (if any).
* @param {devtools.DebuggerMessage} msg
* @return {boolean} Whether a callback for the given message was found and
* excuted.
*/
devtools.DebuggerAgent.prototype.invokeCallbackForResponse_ = function(msg) {
var callback = this.requestSeqToCallback_[msg.getRequestSeq()];
if (!callback) {
// It may happend if reset was called.
return false;
}
delete this.requestSeqToCallback_[msg.getRequestSeq()];
callback(msg);
return true;
};
/**
* @param {Object} stackFrame Frame json object from 'backtrace' response.
* @return {!devtools.CallFrame} Object containing information related to the
* call frame in the format expected by ScriptsPanel and its panes.
*/
devtools.DebuggerAgent.prototype.formatCallFrame_ = function(stackFrame) {
var func = stackFrame.func;
var sourceId = func.scriptId;
// Add service script if it does not exist.
var existingScript = this.parsedScripts_[sourceId];
if (!existingScript) {
this.parsedScripts_[sourceId] = new devtools.ScriptInfo(
sourceId, null /* name */, 0 /* line */, 'unknown' /* type */,
true /* unresolved */);
WebInspector.parsedScriptSource(sourceId, null, null, 0);
}
var funcName = func.name || func.inferredName || '(anonymous function)';
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(stackFrame.line);
// Add basic scope chain info with scope variables.
var scopeChain = [];
var ScopeType = devtools.DebuggerAgent.ScopeType;
for (var i = 0; i < stackFrame.scopes.length; i++) {
var scope = stackFrame.scopes[i];
scope.frameNumber = stackFrame.index;
var scopeObjectProxy = new WebInspector.ObjectProxy(scope, [], 0, '', true);
scopeObjectProxy.isScope = true;
switch(scope.type) {
case ScopeType.Global:
scopeObjectProxy.isDocument = true;
break;
case ScopeType.Local:
scopeObjectProxy.isLocal = true;
scopeObjectProxy.thisObject =
devtools.DebuggerAgent.formatObjectProxy_(stackFrame.receiver);
break;
case ScopeType.With:
// Catch scope is treated as a regular with scope by WebKit so we
// also treat it this way.
case ScopeType.Catch:
scopeObjectProxy.isWithBlock = true;
break;
case ScopeType.Closure:
scopeObjectProxy.isClosure = true;
break;
}
scopeChain.push(scopeObjectProxy);
}
return new devtools.CallFrame(stackFrame.index, 'function', funcName,
sourceId, line, scopeChain);
};
/**
* Collects properties for an object from the debugger response.
* @param {Object} object An object from the debugger protocol response.
* @param {Array.<WebInspector.ObjectPropertyProxy>} result An array to put the
* properties into.
* @param {boolean} noIntrinsic Whether intrinsic properties should be
* included.
*/
devtools.DebuggerAgent.formatObjectProperties_ = function(object, result,
noIntrinsic) {
devtools.DebuggerAgent.propertiesToProxies_(object.properties, result);
if (noIntrinsic) {
return;
}
result.push(new WebInspector.ObjectPropertyProxy('__proto__',
devtools.DebuggerAgent.formatObjectProxy_(object.protoObject)));
result.push(new WebInspector.ObjectPropertyProxy('constructor',
devtools.DebuggerAgent.formatObjectProxy_(object.constructorFunction)));
// Don't add 'prototype' property since it is one of the regualar properties.
};
/**
* For each property in 'properties' creates its proxy representative.
* @param {Array.<Object>} properties Receiver properties or locals array from
* 'backtrace' response.
* @param {Array.<WebInspector.ObjectPropertyProxy>} Results holder.
*/
devtools.DebuggerAgent.propertiesToProxies_ = function(properties, result) {
var map = {};
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
var name = String(property.name);
if (name in map) {
continue;
}
map[name] = true;
var value = devtools.DebuggerAgent.formatObjectProxy_(property.value);
var propertyProxy = new WebInspector.ObjectPropertyProxy(name, value);
result.push(propertyProxy);
}
};
/**
* @param {Object} v An object reference from the debugger response.
* @return {*} The value representation expected by ScriptsPanel.
*/
devtools.DebuggerAgent.formatObjectProxy_ = function(v) {
var description;
var hasChildren = false;
if (v.type == 'object') {
description = v.className;
hasChildren = true;
} else if (v.type == 'function') {
if (v.source) {
description = v.source;
} else {
description = 'function ' + v.name + '()';
}
hasChildren = true;
} else if (v.type == 'undefined') {
description = 'undefined';
} else if (v.type == 'null') {
description = 'null';
} else if (goog.isDef(v.value)) {
// Check for undefined and null types before checking the value, otherwise
// null/undefined may have blank value.
description = v.value;
} else {
description = '<unresolved ref: ' + v.ref + ', type: ' + v.type + '>';
}
var proxy = new WebInspector.ObjectProxy(v, [], 0, description, hasChildren);
proxy.type = v.type;
proxy.isV8Ref = true;
return proxy;
};
/**
* Converts line number from Web Inspector UI(1-based) to v8(0-based).
* @param {number} line Resource line number in Web Inspector UI.
* @return {number} The line number in v8.
*/
devtools.DebuggerAgent.webkitToV8LineNumber_ = function(line) {
return line - 1;
};
/**
* Converts line number from v8(0-based) to Web Inspector UI(1-based).
* @param {number} line Resource line number in v8.
* @return {number} The line number in Web Inspector.
*/
devtools.DebuggerAgent.v8ToWwebkitLineNumber_ = function(line) {
return line + 1;
};
/**
* @param {number} scriptId Id of the script.
* @param {?string} url Script resource URL if any.
* @param {number} lineOffset First line 0-based offset in the containing
* document.
* @param {string} contextType Type of the script's context:
* "page" - regular script from html page
* "injected" - extension content script
* @param {bool} opt_isUnresolved If true, script will not be resolved.
* @constructor
*/
devtools.ScriptInfo = function(
scriptId, url, lineOffset, contextType, opt_isUnresolved) {
this.scriptId_ = scriptId;
this.lineOffset_ = lineOffset;
this.contextType_ = contextType;
this.url_ = url;
this.isUnresolved_ = opt_isUnresolved;
this.lineToBreakpointInfo_ = {};
};
/**
* @return {number}
*/
devtools.ScriptInfo.prototype.getLineOffset = function() {
return this.lineOffset_;
};
/**
* @return {string}
*/
devtools.ScriptInfo.prototype.getContextType = function() {
return this.contextType_;
};
/**
* @return {?string}
*/
devtools.ScriptInfo.prototype.getUrl = function() {
return this.url_;
};
/**
* @return {?bool}
*/
devtools.ScriptInfo.prototype.isUnresolved = function() {
return this.isUnresolved_;
};
/**
* @param {number} line 0-based line number in the script.
* @return {?devtools.BreakpointInfo} Information on a breakpoint at the
* specified line in the script or undefined if there is no breakpoint at
* that line.
*/
devtools.ScriptInfo.prototype.getBreakpointInfo = function(line) {
return this.lineToBreakpointInfo_[line];
};
/**
* Adds breakpoint info to the script.
* @param {devtools.BreakpointInfo} breakpoint
*/
devtools.ScriptInfo.prototype.addBreakpointInfo = function(breakpoint) {
this.lineToBreakpointInfo_[breakpoint.getLine()] = breakpoint;
};
/**
* @param {devtools.BreakpointInfo} breakpoint Breakpoint info to be removed.
*/
devtools.ScriptInfo.prototype.removeBreakpointInfo = function(breakpoint) {
var line = breakpoint.getLine();
delete this.lineToBreakpointInfo_[line];
};
/**
* @param {number} line Breakpoint 0-based line number in the containing script.
* @constructor
*/
devtools.BreakpointInfo = function(line) {
this.line_ = line;
this.v8id_ = -1;
this.removed_ = false;
};
/**
* @return {number}
*/
devtools.BreakpointInfo.prototype.getLine = function(n) {
return this.line_;
};
/**
* @return {number} Unique identifier of this breakpoint in the v8 debugger.
*/
devtools.BreakpointInfo.prototype.getV8Id = function(n) {
return this.v8id_;
};
/**
* Sets id of this breakpoint in the v8 debugger.
* @param {number} id
*/
devtools.BreakpointInfo.prototype.setV8Id = function(id) {
this.v8id_ = id;
};
/**
* Marks this breakpoint as removed from the front-end.
*/
devtools.BreakpointInfo.prototype.markAsRemoved = function() {
this.removed_ = true;
};
/**
* @return {boolean} Whether this breakpoint has been removed from the
* front-end.
*/
devtools.BreakpointInfo.prototype.isRemoved = function() {
return this.removed_;
};
/**
* Call stack frame data.
* @param {string} id CallFrame id.
* @param {string} type CallFrame type.
* @param {string} functionName CallFrame type.
* @param {string} sourceID Source id.
* @param {number} line Source line.
* @param {Array.<Object>} scopeChain Array of scoped objects.
* @construnctor
*/
devtools.CallFrame = function(id, type, functionName, sourceID, line,
scopeChain) {
this.id = id;
this.type = type;
this.functionName = functionName;
this.sourceID = sourceID;
this.line = line;
this.scopeChain = scopeChain;
};
/**
* This method issues asynchronous evaluate request, reports result to the
* callback.
* @param {string} expression An expression to be evaluated in the context of
* this call frame.
* @param {function(Object):undefined} callback Callback to report result to.
*/
devtools.CallFrame.prototype.evaluate_ = function(expression, callback) {
devtools.tools.getDebuggerAgent().requestEvaluate({
'expression': expression,
'frame': this.id,
'global': false,
'disable_break': false,
'compactFormat': true
},
function(response) {
var result = {};
if (response.isSuccess()) {
result.value = devtools.DebuggerAgent.formatObjectProxy_(
response.getBody());
} else {
result.value = response.getMessage();
result.isException = true;
}
callback(result);
});
};
/**
* JSON based commands sent to v8 debugger.
* @param {string} command Name of the command to execute.
* @param {Object} opt_arguments Command-specific arguments map.
* @constructor
*/
devtools.DebugCommand = function(command, opt_arguments) {
this.command_ = command;
this.type_ = 'request';
this.seq_ = ++devtools.DebugCommand.nextSeq_;
if (opt_arguments) {
this.arguments_ = opt_arguments;
}
};
/**
* Next unique number to be used as debugger request sequence number.
* @type {number}
*/
devtools.DebugCommand.nextSeq_ = 1;
/**
* @return {number}
*/
devtools.DebugCommand.prototype.getSequenceNumber = function() {
return this.seq_;
};
/**
* @return {string}
*/
devtools.DebugCommand.prototype.toJSONProtocol = function() {
var json = {
'seq': this.seq_,
'type': this.type_,
'command': this.command_
}
if (this.arguments_) {
json.arguments = this.arguments_;
}
return JSON.stringify(json);
};
/**
* JSON messages sent from v8 debugger. See protocol definition for more
* details: http://code.google.com/p/v8/wiki/DebuggerProtocol
* @param {string} msg Raw protocol packet as JSON string.
* @constructor
*/
devtools.DebuggerMessage = function(msg) {
this.packet_ = JSON.parse(msg);
this.refs_ = [];
if (this.packet_.refs) {
for (var i = 0; i < this.packet_.refs.length; i++) {
this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
}
}
};
/**
* @return {string} The packet type.
*/
devtools.DebuggerMessage.prototype.getType = function() {
return this.packet_.type;
};
/**
* @return {?string} The packet event if the message is an event.
*/
devtools.DebuggerMessage.prototype.getEvent = function() {
return this.packet_.event;
};
/**
* @return {?string} The packet command if the message is a response to a
* command.
*/
devtools.DebuggerMessage.prototype.getCommand = function() {
return this.packet_.command;
};
/**
* @return {number} The packet request sequence.
*/
devtools.DebuggerMessage.prototype.getRequestSeq = function() {
return this.packet_.request_seq;
};
/**
* @return {number} Whether the v8 is running after processing the request.
*/
devtools.DebuggerMessage.prototype.isRunning = function() {
return this.packet_.running ? true : false;
};
/**
* @return {boolean} Whether the request succeeded.
*/
devtools.DebuggerMessage.prototype.isSuccess = function() {
return this.packet_.success ? true : false;
};
/**
* @return {string}
*/
devtools.DebuggerMessage.prototype.getMessage = function() {
return this.packet_.message;
};
/**
* @return {Object} Parsed message body json.
*/
devtools.DebuggerMessage.prototype.getBody = function() {
return this.packet_.body;
};
/**
* @param {number} handle Object handle.
* @return {?Object} Returns the object with the handle if it was sent in this
* message(some objects referenced by handles may be missing in the message).
*/
devtools.DebuggerMessage.prototype.lookup = function(handle) {
return this.refs_[handle];
};
/* codemap.js */
// Copyright 2009 the V8 project authors. 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.
// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};
/**
* Constructs a mapper that maps addresses into code entries.
*
* @constructor
*/
devtools.profiler.CodeMap = function() {
/**
* Dynamic code entries. Used for JIT compiled code.
*/
this.dynamics_ = new goog.structs.SplayTree();
/**
* Name generator for entries having duplicate names.
*/
this.dynamicsNameGen_ = new devtools.profiler.CodeMap.NameGenerator();
/**
* Static code entries. Used for statically compiled code.
*/
this.statics_ = new goog.structs.SplayTree();
/**
* Libraries entries. Used for the whole static code libraries.
*/
this.libraries_ = new goog.structs.SplayTree();
/**
* Map of memory pages occupied with static code.
*/
this.pages_ = [];
};
/**
* The number of alignment bits in a page address.
*/
devtools.profiler.CodeMap.PAGE_ALIGNMENT = 12;
/**
* Page size in bytes.
*/
devtools.profiler.CodeMap.PAGE_SIZE =
1 << devtools.profiler.CodeMap.PAGE_ALIGNMENT;
/**
* Adds a dynamic (i.e. moveable and discardable) code entry.
*
* @param {number} start The starting address.
* @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
*/
devtools.profiler.CodeMap.prototype.addCode = function(start, codeEntry) {
this.dynamics_.insert(start, codeEntry);
};
/**
* Moves a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} from The starting address of the entry being moved.
* @param {number} to The destination address.
*/
devtools.profiler.CodeMap.prototype.moveCode = function(from, to) {
var removedNode = this.dynamics_.remove(from);
this.dynamics_.insert(to, removedNode.value);
};
/**
* Discards a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} start The starting address of the entry being deleted.
*/
devtools.profiler.CodeMap.prototype.deleteCode = function(start) {
var removedNode = this.dynamics_.remove(start);
};
/**
* Adds a library entry.
*
* @param {number} start The starting address.
* @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
*/
devtools.profiler.CodeMap.prototype.addLibrary = function(
start, codeEntry) {
this.markPages_(start, start + codeEntry.size);
this.libraries_.insert(start, codeEntry);
};
/**
* Adds a static code entry.
*
* @param {number} start The starting address.
* @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
*/
devtools.profiler.CodeMap.prototype.addStaticCode = function(
start, codeEntry) {
this.statics_.insert(start, codeEntry);
};
/**
* @private
*/
devtools.profiler.CodeMap.prototype.markPages_ = function(start, end) {
for (var addr = start; addr <= end;
addr += devtools.profiler.CodeMap.PAGE_SIZE) {
this.pages_[addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT] = 1;
}
};
/**
* @private
*/
devtools.profiler.CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
return addr >= node.key && addr < (node.key + node.value.size);
};
/**
* @private
*/
devtools.profiler.CodeMap.prototype.findInTree_ = function(tree, addr) {
var node = tree.findGreatestLessThan(addr);
return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered.
*
* @param {number} addr Address.
*/
devtools.profiler.CodeMap.prototype.findEntry = function(addr) {
var pageAddr = addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT;
if (pageAddr in this.pages_) {
// Static code entries can contain "holes" of unnamed code.
// In this case, the whole library is assigned to this address.
return this.findInTree_(this.statics_, addr) ||
this.findInTree_(this.libraries_, addr);
}
var min = this.dynamics_.findMin();
var max = this.dynamics_.findMax();
if (max != null && addr < (max.key + max.value.size) && addr >= min.key) {
var dynaEntry = this.findInTree_(this.dynamics_, addr);
if (dynaEntry == null) return null;
// Dedupe entry name.
if (!dynaEntry.nameUpdated_) {
dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
dynaEntry.nameUpdated_ = true;
}
return dynaEntry;
}
return null;
};
/**
* Returns an array of all dynamic code entries.
*/
devtools.profiler.CodeMap.prototype.getAllDynamicEntries = function() {
return this.dynamics_.exportValues();
};
/**
* Returns an array of all static code entries.
*/
devtools.profiler.CodeMap.prototype.getAllStaticEntries = function() {
return this.statics_.exportValues();
};
/**
* Returns an array of all libraries entries.
*/
devtools.profiler.CodeMap.prototype.getAllLibrariesEntries = function() {
return this.libraries_.exportValues();
};
/**
* Creates a code entry object.
*
* @param {number} size Code entry size in bytes.
* @param {string} opt_name Code entry name.
* @constructor
*/
devtools.profiler.CodeMap.CodeEntry = function(size, opt_name) {
this.size = size;
this.name = opt_name || '';
this.nameUpdated_ = false;
};
devtools.profiler.CodeMap.CodeEntry.prototype.getName = function() {
return this.name;
};
devtools.profiler.CodeMap.CodeEntry.prototype.toString = function() {
return this.name + ': ' + this.size.toString(16);
};
devtools.profiler.CodeMap.NameGenerator = function() {
this.knownNames_ = {};
};
devtools.profiler.CodeMap.NameGenerator.prototype.getName = function(name) {
if (!(name in this.knownNames_)) {
this.knownNames_[name] = 0;
return name;
}
var count = ++this.knownNames_[name];
return name + ' {' + count + '}';
};
/* consarray.js */
// Copyright 2009 the V8 project authors. 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.
/**
* Constructs a ConsArray object. It is used mainly for tree traversal.
* In this use case we have lots of arrays that we need to iterate
* sequentally. The internal Array implementation is horribly slow
* when concatenating on large (10K items) arrays due to memory copying.
* That's why we avoid copying memory and insead build a linked list
* of arrays to iterate through.
*
* @constructor
*/
function ConsArray() {
this.tail_ = new ConsArray.Cell(null, null);
this.currCell_ = this.tail_;
this.currCellPos_ = 0;
};
/**
* Concatenates another array for iterating. Empty arrays are ignored.
* This operation can be safely performed during ongoing ConsArray
* iteration.
*
* @param {Array} arr Array to concatenate.
*/
ConsArray.prototype.concat = function(arr) {
if (arr.length > 0) {
this.tail_.data = arr;
this.tail_ = this.tail_.next = new ConsArray.Cell(null, null);
}
};
/**
* Whether the end of iteration is reached.
*/
ConsArray.prototype.atEnd = function() {
return this.currCell_ === null ||
this.currCell_.data === null ||
this.currCellPos_ >= this.currCell_.data.length;
};
/**
* Returns the current item, moves to the next one.
*/
ConsArray.prototype.next = function() {
var result = this.currCell_.data[this.currCellPos_++];
if (this.currCellPos_ >= this.currCell_.data.length) {
this.currCell_ = this.currCell_.next;
this.currCellPos_ = 0;
}
return result;
};
/**
* A cell object used for constructing a list in ConsArray.
*
* @constructor
*/
ConsArray.Cell = function(data, next) {
this.data = data;
this.next = next;
};
/* csvparser.js */
// Copyright 2009 the V8 project authors. 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.
// Initlialize namespaces.
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};
/**
* Creates a CSV lines parser.
*/
devtools.profiler.CsvParser = function() {
};
/**
* A regex for matching a trailing quote.
* @private
*/
devtools.profiler.CsvParser.TRAILING_QUOTE_RE_ = /\"$/;
/**
* A regex for matching a double quote.
* @private
*/
devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_ = /\"\"/g;
/**
* Parses a line of CSV-encoded values. Returns an array of fields.
*
* @param {string} line Input line.
*/
devtools.profiler.CsvParser.prototype.parseLine = function(line) {
var insideQuotes = false;
var fields = [];
var prevPos = 0;
for (var i = 0, n = line.length; i < n; ++i) {
switch (line.charAt(i)) {
case ',':
if (!insideQuotes) {
fields.push(line.substring(prevPos, i));
prevPos = i + 1;
}
break;
case '"':
if (!insideQuotes) {
insideQuotes = true;
// Skip the leading quote.
prevPos++;
} else {
if (i + 1 < n && line.charAt(i + 1) != '"') {
insideQuotes = false;
} else {
i++;
}
}
break;
}
}
if (n > 0) {
fields.push(line.substring(prevPos));
}
for (i = 0; i < fields.length; ++i) {
// Eliminate trailing quotes.
fields[i] = fields[i].replace(devtools.profiler.CsvParser.TRAILING_QUOTE_RE_, '');
// Convert quoted quotes into single ones.
fields[i] = fields[i].replace(devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_, '"');
}
return fields;
};
/* logreader.js */
// Copyright 2009 the V8 project authors. 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 Log Reader is used to process log file produced by V8.
*/
// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};
/**
* Base class for processing log files.
*
* @param {Array.<Object>} dispatchTable A table used for parsing and processing
* log records.
* @constructor
*/
devtools.profiler.LogReader = function(dispatchTable) {
/**
* @type {Array.<Object>}
*/
this.dispatchTable_ = dispatchTable;
this.dispatchTable_['alias'] =
{ parsers: [null, null], processor: this.processAlias_ };
this.dispatchTable_['repeat'] =
{ parsers: [parseInt, 'var-args'], processor: this.processRepeat_,
backrefs: true };
/**
* A key-value map for aliases. Translates short name -> full name.
* @type {Object}
*/
this.aliases_ = {};
/**
* A key-value map for previous address values.
* @type {Object}
*/
this.prevAddresses_ = {};
/**
* A key-value map for events than can be backreference-compressed.
* @type {Object}
*/
this.backRefsCommands_ = {};
this.initBackRefsCommands_();
/**
* Back references for decompression.
* @type {Array.<string>}
*/
this.backRefs_ = [];
};
/**
* Creates a parser for an address entry.
*
* @param {string} addressTag Address tag to perform offset decoding.
* @return {function(string):number} Address parser.
*/
devtools.profiler.LogReader.prototype.createAddressParser = function(
addressTag) {
var self = this;
return (function (str) {
var value = parseInt(str, 16);
var firstChar = str.charAt(0);
if (firstChar == '+' || firstChar == '-') {
var addr = self.prevAddresses_[addressTag];
addr += value;
self.prevAddresses_[addressTag] = addr;
return addr;
} else if (firstChar != '0' || str.charAt(1) != 'x') {
self.prevAddresses_[addressTag] = value;
}
return value;
});
};
/**
* Expands an alias symbol, if applicable.
*
* @param {string} symbol Symbol to expand.
* @return {string} Expanded symbol, or the input symbol itself.
*/
devtools.profiler.LogReader.prototype.expandAlias = function(symbol) {
return symbol in this.aliases_ ? this.aliases_[symbol] : symbol;
};
/**
* Used for printing error messages.
*
* @param {string} str Error message.
*/
devtools.profiler.LogReader.prototype.printError = function(str) {
// Do nothing.
};
/**
* Processes a portion of V8 profiler event log.
*
* @param {string} chunk A portion of log.
*/
devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) {
this.processLog_(chunk.split('\n'));
};
/**
* Processes stack record.
*
* @param {number} pc Program counter.
* @param {Array.<string>} stack String representation of a stack.
* @return {Array.<number>} Processed stack.
*/
devtools.profiler.LogReader.prototype.processStack = function(pc, stack) {
var fullStack = [pc];
var prevFrame = pc;
for (var i = 0, n = stack.length; i < n; ++i) {
var frame = stack[i];
var firstChar = frame.charAt(0);
if (firstChar == '+' || firstChar == '-') {
// An offset from the previous frame.
prevFrame += parseInt(frame, 16);
fullStack.push(prevFrame);
// Filter out possible 'overflow' string.
} else if (firstChar != 'o') {
fullStack.push(parseInt(frame, 16));
}
}
return fullStack;
};
/**
* Returns whether a particular dispatch must be skipped.
*
* @param {!Object} dispatch Dispatch record.
* @return {boolean} True if dispatch must be skipped.
*/
devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) {
return false;
};
/**
* Does a dispatch of a log record.
*
* @param {Array.<string>} fields Log record.
* @private
*/
devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) {
// Obtain the dispatch.
var command = fields[0];
if (!(command in this.dispatchTable_)) {
throw new Error('unknown command: ' + command);
}
var dispatch = this.dispatchTable_[command];
if (dispatch === null || this.skipDispatch(dispatch)) {
return;
}
// Parse fields.
var parsedFields = [];
for (var i = 0; i < dispatch.parsers.length; ++i) {
var parser = dispatch.parsers[i];
if (parser === null) {
parsedFields.push(fields[1 + i]);
} else if (typeof parser == 'function') {
parsedFields.push(parser(fields[1 + i]));
} else {
// var-args
parsedFields.push(fields.slice(1 + i));
break;
}
}
// Run the processor.
dispatch.processor.apply(this, parsedFields);
};
/**
* Decompresses a line if it was backreference-compressed.
*
* @param {string} line Possibly compressed line.
* @return {string} Decompressed line.
* @private
*/
devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) {
var backRefPos;
// Filter out case when a regexp is created containing '#'.
if (line.charAt(line.length - 1) != '"'
&& (backRefPos = line.lastIndexOf('#')) != -1) {
var backRef = line.substr(backRefPos + 1);
var backRefIdx = parseInt(backRef, 10) - 1;
var colonPos = backRef.indexOf(':');
var backRefStart =
colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0;
line = line.substr(0, backRefPos) +
this.backRefs_[backRefIdx].substr(backRefStart);
}
this.backRefs_.unshift(line);
if (this.backRefs_.length > 10) {
this.backRefs_.length = 10;
}
return line;
};
/**
* Initializes the map of backward reference compressible commands.
* @private
*/
devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() {
for (var event in this.dispatchTable_) {
var dispatch = this.dispatchTable_[event];
if (dispatch && dispatch.backrefs) {
this.backRefsCommands_[event] = true;
}
}
};
/**
* Processes alias log record. Adds an alias to a corresponding map.
*
* @param {string} symbol Short name.
* @param {string} expansion Long name.
* @private
*/
devtools.profiler.LogReader.prototype.processAlias_ = function(
symbol, expansion) {
if (expansion in this.dispatchTable_) {
this.dispatchTable_[symbol] = this.dispatchTable_[expansion];
if (expansion in this.backRefsCommands_) {
this.backRefsCommands_[symbol] = true;
}
} else {
this.aliases_[symbol] = expansion;
}
};
/**
* Processes log lines.
*
* @param {Array.<string>} lines Log lines.
* @private
*/
devtools.profiler.LogReader.prototype.processLog_ = function(lines) {
var csvParser = new devtools.profiler.CsvParser();
try {
for (var i = 0, n = lines.length; i < n; ++i) {
var line = lines[i];
if (!line) {
continue;
}
if (line.charAt(0) == '#' ||
line.substr(0, line.indexOf(',')) in this.backRefsCommands_) {
line = this.expandBackRef_(line);
}
var fields = csvParser.parseLine(line);
this.dispatchLogRow_(fields);
}
} catch (e) {
// An error on the last line is acceptable since log file can be truncated.
if (i < n - 1) {
this.printError('line ' + (i + 1) + ': ' + (e.message || e));
throw e;
}
}
};
/**
* Processes repeat log record. Expands it according to calls count and
* invokes processing.
*
* @param {number} count Count.
* @param {Array.<string>} cmd Parsed command.
* @private
*/
devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) {
// Replace the repeat-prefixed command from backrefs list with a non-prefixed.
this.backRefs_[0] = cmd.join(',');
for (var i = 0; i < count; ++i) {
this.dispatchLogRow_(cmd);
}
};
/* profile.js */
// Copyright 2009 the V8 project authors. 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.
// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};
/**
* Creates a profile object for processing profiling-related events
* and calculating function execution times.
*
* @constructor
*/
devtools.profiler.Profile = function() {
this.codeMap_ = new devtools.profiler.CodeMap();
this.topDownTree_ = new devtools.profiler.CallTree();
this.bottomUpTree_ = new devtools.profiler.CallTree();
};
/**
* Returns whether a function with the specified name must be skipped.
* Should be overriden by subclasses.
*
* @param {string} name Function name.
*/
devtools.profiler.Profile.prototype.skipThisFunction = function(name) {
return false;
};
/**
* Enum for profiler operations that involve looking up existing
* code entries.
*
* @enum {number}
*/
devtools.profiler.Profile.Operation = {
MOVE: 0,
DELETE: 1,
TICK: 2
};
/**
* Called whenever the specified operation has failed finding a function
* containing the specified address. Should be overriden by subclasses.
* See the devtools.profiler.Profile.Operation enum for the list of
* possible operations.
*
* @param {number} operation Operation.
* @param {number} addr Address of the unknown code.
* @param {number} opt_stackPos If an unknown address is encountered
* during stack strace processing, specifies a position of the frame
* containing the address.
*/
devtools.profiler.Profile.prototype.handleUnknownCode = function(
operation, addr, opt_stackPos) {
};
/**
* Registers a library.
*
* @param {string} name Code entry name.
* @param {number} startAddr Starting address.
* @param {number} endAddr Ending address.
*/
devtools.profiler.Profile.prototype.addLibrary = function(
name, startAddr, endAddr) {
var entry = new devtools.profiler.CodeMap.CodeEntry(
endAddr - startAddr, name);
this.codeMap_.addLibrary(startAddr, entry);
return entry;
};
/**
* Registers statically compiled code entry.
*
* @param {string} name Code entry name.
* @param {number} startAddr Starting address.
* @param {number} endAddr Ending address.
*/
devtools.profiler.Profile.prototype.addStaticCode = function(
name, startAddr, endAddr) {
var entry = new devtools.profiler.CodeMap.CodeEntry(
endAddr - startAddr, name);
this.codeMap_.addStaticCode(startAddr, entry);
return entry;
};
/**
* Registers dynamic (JIT-compiled) code entry.
*
* @param {string} type Code entry type.
* @param {string} name Code entry name.
* @param {number} start Starting address.
* @param {number} size Code entry size.
*/
devtools.profiler.Profile.prototype.addCode = function(
type, name, start, size) {
var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
this.codeMap_.addCode(start, entry);
return entry;
};
/**
* Reports about moving of a dynamic code entry.
*
* @param {number} from Current code entry address.
* @param {number} to New code entry address.
*/
devtools.profiler.Profile.prototype.moveCode = function(from, to) {
try {
this.codeMap_.moveCode(from, to);
} catch (e) {
this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
}
};
/**
* Reports about deletion of a dynamic code entry.
*
* @param {number} start Starting address.
*/
devtools.profiler.Profile.prototype.deleteCode = function(start) {
try {
this.codeMap_.deleteCode(start);
} catch (e) {
this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
}
};
/**
* Records a tick event. Stack must contain a sequence of
* addresses starting with the program counter value.
*
* @param {Array<number>} stack Stack sample.
*/
devtools.profiler.Profile.prototype.recordTick = function(stack) {
var processedStack = this.resolveAndFilterFuncs_(stack);
this.bottomUpTree_.addPath(processedStack);
processedStack.reverse();
this.topDownTree_.addPath(processedStack);
};
/**
* Translates addresses into function names and filters unneeded
* functions.
*
* @param {Array<number>} stack Stack sample.
*/
devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
var result = [];
for (var i = 0; i < stack.length; ++i) {
var entry = this.codeMap_.findEntry(stack[i]);
if (entry) {
var name = entry.getName();
if (!this.skipThisFunction(name)) {
result.push(name);
}
} else {
this.handleUnknownCode(
devtools.profiler.Profile.Operation.TICK, stack[i], i);
}
}
return result;
};
/**
* Performs a BF traversal of the top down call graph.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
this.topDownTree_.traverse(f);
};
/**
* Performs a BF traversal of the bottom up call graph.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
this.bottomUpTree_.traverse(f);
};
/**
* Calculates a top down profile for a node with the specified label.
* If no name specified, returns the whole top down calls tree.
*
* @param {string} opt_label Node label.
*/
devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
return this.getTreeProfile_(this.topDownTree_, opt_label);
};
/**
* Calculates a bottom up profile for a node with the specified label.
* If no name specified, returns the whole bottom up calls tree.
*
* @param {string} opt_label Node label.
*/
devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
return this.getTreeProfile_(this.bottomUpTree_, opt_label);
};
/**
* Helper function for calculating a tree profile.
*
* @param {devtools.profiler.Profile.CallTree} tree Call tree.
* @param {string} opt_label Node label.
*/
devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
if (!opt_label) {
tree.computeTotalWeights();
return tree;
} else {
var subTree = tree.cloneSubtree(opt_label);
subTree.computeTotalWeights();
return subTree;
}
};
/**
* Calculates a flat profile of callees starting from a node with
* the specified label. If no name specified, starts from the root.
*
* @param {string} opt_label Starting node label.
*/
devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
var counters = new devtools.profiler.CallTree();
var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
var precs = {};
precs[rootLabel] = 0;
var root = counters.findOrAddChild(rootLabel);
this.topDownTree_.computeTotalWeights();
this.topDownTree_.traverseInDepth(
function onEnter(node) {
if (!(node.label in precs)) {
precs[node.label] = 0;
}
var nodeLabelIsRootLabel = node.label == rootLabel;
if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
if (precs[rootLabel] == 0) {
root.selfWeight += node.selfWeight;
root.totalWeight += node.totalWeight;
} else {
var rec = root.findOrAddChild(node.label);
rec.selfWeight += node.selfWeight;
if (nodeLabelIsRootLabel || precs[node.label] == 0) {
rec.totalWeight += node.totalWeight;
}
}
precs[node.label]++;
}
},
function onExit(node) {
if (node.label == rootLabel || precs[rootLabel] > 0) {
precs[node.label]--;
}
},
null);
if (!opt_label) {
// If we have created a flat profile for the whole program, we don't
// need an explicit root in it. Thus, replace the counters tree
// root with the node corresponding to the whole program.
counters.root_ = root;
} else {
// Propagate weights so percents can be calculated correctly.
counters.getRoot().selfWeight = root.selfWeight;
counters.getRoot().totalWeight = root.totalWeight;
}
return counters;
};
/**
* Creates a dynamic code entry.
*
* @param {number} size Code size.
* @param {string} type Code type.
* @param {string} name Function name.
* @constructor
*/
devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
this.type = type;
};
/**
* Returns node name.
*/
devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
var name = this.name;
if (name.length == 0) {
name = '<anonymous>';
} else if (name.charAt(0) == ' ') {
// An anonymous function with location: " aaa.js:10".
name = '<anonymous>' + name;
}
return this.type + ': ' + name;
};
/**
* Constructs a call graph.
*
* @constructor
*/
devtools.profiler.CallTree = function() {
this.root_ = new devtools.profiler.CallTree.Node(
devtools.profiler.CallTree.ROOT_NODE_LABEL);
};
/**
* The label of the root node.
*/
devtools.profiler.CallTree.ROOT_NODE_LABEL = '';
/**
* @private
*/
devtools.profiler.CallTree.prototype.totalsComputed_ = false;
/**
* Returns the tree root.
*/
devtools.profiler.CallTree.prototype.getRoot = function() {
return this.root_;
};
/**
* Adds the specified call path, constructing nodes as necessary.
*
* @param {Array<string>} path Call path.
*/
devtools.profiler.CallTree.prototype.addPath = function(path) {
if (path.length == 0) {
return;
}
var curr = this.root_;
for (var i = 0; i < path.length; ++i) {
curr = curr.findOrAddChild(path[i]);
}
curr.selfWeight++;
this.totalsComputed_ = false;
};
/**
* Finds an immediate child of the specified parent with the specified
* label, creates a child node if necessary. If a parent node isn't
* specified, uses tree root.
*
* @param {string} label Child node label.
*/
devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
return this.root_.findOrAddChild(label);
};
/**
* Creates a subtree by cloning and merging all subtrees rooted at nodes
* with a given label. E.g. cloning the following call tree on label 'A'
* will give the following result:
*
* <A>--<B> <B>
* / /
* <root> == clone on 'A' ==> <root>--<A>
* \ \
* <C>--<A>--<D> <D>
*
* And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
* source call tree.
*
* @param {string} label The label of the new root node.
*/
devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
var subTree = new devtools.profiler.CallTree();
this.traverse(function(node, parent) {
if (!parent && node.label != label) {
return null;
}
var child = (parent ? parent : subTree).findOrAddChild(node.label);
child.selfWeight += node.selfWeight;
return child;
});
return subTree;
};
/**
* Computes total weights in the call graph.
*/
devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
if (this.totalsComputed_) {
return;
}
this.root_.computeTotalWeight();
this.totalsComputed_ = true;
};
/**
* Traverses the call graph in preorder. This function can be used for
* building optionally modified tree clones. This is the boilerplate code
* for this scenario:
*
* callTree.traverse(function(node, parentClone) {
* var nodeClone = cloneNode(node);
* if (parentClone)
* parentClone.addChild(nodeClone);
* return nodeClone;
* });
*
* @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
* The second parameter is the result of calling 'f' on the parent node.
*/
devtools.profiler.CallTree.prototype.traverse = function(f) {
var pairsToProcess = new ConsArray();
pairsToProcess.concat([{node: this.root_, param: null}]);
while (!pairsToProcess.atEnd()) {
var pair = pairsToProcess.next();
var node = pair.node;
var newParam = f(node, pair.param);
var morePairsToProcess = [];
node.forEachChild(function (child) {
morePairsToProcess.push({node: child, param: newParam}); });
pairsToProcess.concat(morePairsToProcess);
}
};
/**
* Performs an indepth call graph traversal.
*
* @param {function(devtools.profiler.CallTree.Node)} enter A function called
* prior to visiting node's children.
* @param {function(devtools.profiler.CallTree.Node)} exit A function called
* after visiting node's children.
*/
devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
function traverse(node) {
enter(node);
node.forEachChild(traverse);
exit(node);
}
traverse(this.root_);
};
/**
* Constructs a call graph node.
*
* @param {string} label Node label.
* @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
*/
devtools.profiler.CallTree.Node = function(label, opt_parent) {
this.label = label;
this.parent = opt_parent;
this.children = {};
};
/**
* Node self weight (how many times this node was the last node in
* a call path).
* @type {number}
*/
devtools.profiler.CallTree.Node.prototype.selfWeight = 0;
/**
* Node total weight (includes weights of all children).
* @type {number}
*/
devtools.profiler.CallTree.Node.prototype.totalWeight = 0;
/**
* Adds a child node.
*
* @param {string} label Child node label.
*/
devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
var child = new devtools.profiler.CallTree.Node(label, this);
this.children[label] = child;
return child;
};
/**
* Computes node's total weight.
*/
devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
function() {
var totalWeight = this.selfWeight;
this.forEachChild(function(child) {
totalWeight += child.computeTotalWeight(); });
return this.totalWeight = totalWeight;
};
/**
* Returns all node's children as an array.
*/
devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
var result = [];
this.forEachChild(function (node) { result.push(node); });
return result;
};
/**
* Finds an immediate child with the specified label.
*
* @param {string} label Child node label.
*/
devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
return this.children[label] || null;
};
/**
* Finds an immediate child with the specified label, creates a child
* node if necessary.
*
* @param {string} label Child node label.
*/
devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
return this.findChild(label) || this.addChild(label);
};
/**
* Calls the specified function for every child.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
for (var c in this.children) {
f(this.children[c]);
}
};
/**
* Walks up from the current node up to the call tree root.
*
* @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
*/
devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
for (var curr = this; curr != null; curr = curr.parent) {
f(curr);
}
};
/**
* Tries to find a node with the specified path.
*
* @param {Array<string>} labels The path.
* @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
*/
devtools.profiler.CallTree.Node.prototype.descendToChild = function(
labels, opt_f) {
for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
var child = curr.findChild(labels[pos]);
if (opt_f) {
opt_f(child, pos);
}
curr = child;
}
return curr;
};
/* profile_view.js */
// Copyright 2009 the V8 project authors. 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.
// Initlialize namespaces
var devtools = devtools || {};
devtools.profiler = devtools.profiler || {};
/**
* Creates a Profile View builder object.
*
* @param {number} samplingRate Number of ms between profiler ticks.
* @constructor
*/
devtools.profiler.ViewBuilder = function(samplingRate) {
this.samplingRate = samplingRate;
};
/**
* Builds a profile view for the specified call tree.
*
* @param {devtools.profiler.CallTree} callTree A call tree.
* @param {boolean} opt_bottomUpViewWeights Whether remapping
* of self weights for a bottom up view is needed.
*/
devtools.profiler.ViewBuilder.prototype.buildView = function(
callTree, opt_bottomUpViewWeights) {
var head;
var samplingRate = this.samplingRate;
var createViewNode = this.createViewNode;
callTree.traverse(function(node, viewParent) {
var totalWeight = node.totalWeight * samplingRate;
var selfWeight = node.selfWeight * samplingRate;
if (opt_bottomUpViewWeights === true) {
if (viewParent === head) {
selfWeight = totalWeight;
} else {
selfWeight = 0;
}
}
var viewNode = createViewNode(node.label, totalWeight, selfWeight, head);
if (viewParent) {
viewParent.addChild(viewNode);
} else {
head = viewNode;
}
return viewNode;
});
var view = this.createView(head);
return view;
};
/**
* Factory method for a profile view.
*
* @param {devtools.profiler.ProfileView.Node} head View head node.
* @return {devtools.profiler.ProfileView} Profile view.
*/
devtools.profiler.ViewBuilder.prototype.createView = function(head) {
return new devtools.profiler.ProfileView(head);
};
/**
* Factory method for a profile view node.
*
* @param {string} internalFuncName A fully qualified function name.
* @param {number} totalTime Amount of time that application spent in the
* corresponding function and its descendants (not that depending on
* profile they can be either callees or callers.)
* @param {number} selfTime Amount of time that application spent in the
* corresponding function only.
* @param {devtools.profiler.ProfileView.Node} head Profile view head.
* @return {devtools.profiler.ProfileView.Node} Profile view node.
*/
devtools.profiler.ViewBuilder.prototype.createViewNode = function(
funcName, totalTime, selfTime, head) {
return new devtools.profiler.ProfileView.Node(
funcName, totalTime, selfTime, head);
};
/**
* Creates a Profile View object. It allows to perform sorting
* and filtering actions on the profile.
*
* @param {devtools.profiler.ProfileView.Node} head Head (root) node.
* @constructor
*/
devtools.profiler.ProfileView = function(head) {
this.head = head;
};
/**
* Sorts the profile view using the specified sort function.
*
* @param {function(devtools.profiler.ProfileView.Node,
* devtools.profiler.ProfileView.Node):number} sortFunc A sorting
* functions. Must comply with Array.sort sorting function requirements.
*/
devtools.profiler.ProfileView.prototype.sort = function(sortFunc) {
this.traverse(function (node) {
node.sortChildren(sortFunc);
});
};
/**
* Traverses profile view nodes in preorder.
*
* @param {function(devtools.profiler.ProfileView.Node)} f Visitor function.
*/
devtools.profiler.ProfileView.prototype.traverse = function(f) {
var nodesToTraverse = new ConsArray();
nodesToTraverse.concat([this.head]);
while (!nodesToTraverse.atEnd()) {
var node = nodesToTraverse.next();
f(node);
nodesToTraverse.concat(node.children);
}
};
/**
* Constructs a Profile View node object. Each node object corresponds to
* a function call.
*
* @param {string} internalFuncName A fully qualified function name.
* @param {number} totalTime Amount of time that application spent in the
* corresponding function and its descendants (not that depending on
* profile they can be either callees or callers.)
* @param {number} selfTime Amount of time that application spent in the
* corresponding function only.
* @param {devtools.profiler.ProfileView.Node} head Profile view head.
* @constructor
*/
devtools.profiler.ProfileView.Node = function(
internalFuncName, totalTime, selfTime, head) {
this.internalFuncName = internalFuncName;
this.totalTime = totalTime;
this.selfTime = selfTime;
this.head = head;
this.parent = null;
this.children = [];
};
/**
* Returns a share of the function's total time in application's total time.
*/
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
'totalPercent',
function() { return this.totalTime /
(this.head ? this.head.totalTime : this.totalTime) * 100.0; });
/**
* Returns a share of the function's self time in application's total time.
*/
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
'selfPercent',
function() { return this.selfTime /
(this.head ? this.head.totalTime : this.totalTime) * 100.0; });
/**
* Returns a share of the function's total time in its parent's total time.
*/
devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
'parentTotalPercent',
function() { return this.totalTime /
(this.parent ? this.parent.totalTime : this.totalTime) * 100.0; });
/**
* Adds a child to the node.
*
* @param {devtools.profiler.ProfileView.Node} node Child node.
*/
devtools.profiler.ProfileView.Node.prototype.addChild = function(node) {
node.parent = this;
this.children.push(node);
};
/**
* Sorts all the node's children recursively.
*
* @param {function(devtools.profiler.ProfileView.Node,
* devtools.profiler.ProfileView.Node):number} sortFunc A sorting
* functions. Must comply with Array.sort sorting function requirements.
*/
devtools.profiler.ProfileView.Node.prototype.sortChildren = function(
sortFunc) {
this.children.sort(sortFunc);
};
/* splaytree.js */
// Copyright 2009 the V8 project authors. 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.
// A namespace stub. It will become more clear how to declare it properly
// during integration of this script into Dev Tools.
var goog = goog || {};
goog.structs = goog.structs || {};
/**
* Constructs a Splay tree. A splay tree is a self-balancing binary
* search tree with the additional property that recently accessed
* elements are quick to access again. It performs basic operations
* such as insertion, look-up and removal in O(log(n)) amortized time.
*
* @constructor
*/
goog.structs.SplayTree = function() {
};
/**
* Pointer to the root node of the tree.
*
* @type {goog.structs.SplayTree.Node}
* @private
*/
goog.structs.SplayTree.prototype.root_ = null;
/**
* @return {boolean} Whether the tree is empty.
*/
goog.structs.SplayTree.prototype.isEmpty = function() {
return !this.root_;
};
/**
* Inserts a node into the tree with the specified key and value if
* the tree does not already contain a node with the specified key. If
* the value is inserted, it becomes the root of the tree.
*
* @param {number} key Key to insert into the tree.
* @param {*} value Value to insert into the tree.
*/
goog.structs.SplayTree.prototype.insert = function(key, value) {
if (this.isEmpty()) {
this.root_ = new goog.structs.SplayTree.Node(key, value);
return;
}
// Splay on the key to move the last node on the search path for
// the key to the root of the tree.
this.splay_(key);
if (this.root_.key == key) {
return;
}
var node = new goog.structs.SplayTree.Node(key, value);
if (key > this.root_.key) {
node.left = this.root_;
node.right = this.root_.right;
this.root_.right = null;
} else {
node.right = this.root_;
node.left = this.root_.left;
this.root_.left = null;
}
this.root_ = node;
};
/**
* Removes a node with the specified key from the tree if the tree
* contains a node with this key. The removed node is returned. If the
* key is not found, an exception is thrown.
*
* @param {number} key Key to find and remove from the tree.
* @return {goog.structs.SplayTree.Node} The removed node.
*/
goog.structs.SplayTree.prototype.remove = function(key) {
if (this.isEmpty()) {
throw Error('Key not found: ' + key);
}
this.splay_(key);
if (this.root_.key != key) {
throw Error('Key not found: ' + key);
}
var removed = this.root_;
if (!this.root_.left) {
this.root_ = this.root_.right;
} else {
var right = this.root_.right;
this.root_ = this.root_.left;
// Splay to make sure that the new root has an empty right child.
this.splay_(key);
// Insert the original right child as the right child of the new
// root.
this.root_.right = right;
}
return removed;
};
/**
* Returns the node having the specified key or null if the tree doesn't contain
* a node with the specified key.
*
* @param {number} key Key to find in the tree.
* @return {goog.structs.SplayTree.Node} Node having the specified key.
*/
goog.structs.SplayTree.prototype.find = function(key) {
if (this.isEmpty()) {
return null;
}
this.splay_(key);
return this.root_.key == key ? this.root_ : null;
};
/**
* @return {goog.structs.SplayTree.Node} Node having the minimum key value.
*/
goog.structs.SplayTree.prototype.findMin = function() {
if (this.isEmpty()) {
return null;
}
var current = this.root_;
while (current.left) {
current = current.left;
}
return current;
};
/**
* @return {goog.structs.SplayTree.Node} Node having the maximum key value.
*/
goog.structs.SplayTree.prototype.findMax = function(opt_startNode) {
if (this.isEmpty()) {
return null;
}
var current = opt_startNode || this.root_;
while (current.right) {
current = current.right;
}
return current;
};
/**
* @return {goog.structs.SplayTree.Node} Node having the maximum key value that
* is less or equal to the specified key value.
*/
goog.structs.SplayTree.prototype.findGreatestLessThan = function(key) {
if (this.isEmpty()) {
return null;
}
// Splay on the key to move the node with the given key or the last
// node on the search path to the top of the tree.
this.splay_(key);
// Now the result is either the root node or the greatest node in
// the left subtree.
if (this.root_.key <= key) {
return this.root_;
} else if (this.root_.left) {
return this.findMax(this.root_.left);
} else {
return null;
}
};
/**
* @return {Array<*>} An array containing all the values of tree's nodes.
*/
goog.structs.SplayTree.prototype.exportValues = function() {
var result = [];
this.traverse_(function(node) { result.push(node.value); });
return result;
};
/**
* Perform the splay operation for the given key. Moves the node with
* the given key to the top of the tree. If no node has the given
* key, the last node on the search path is moved to the top of the
* tree. This is the simplified top-down splaying algorithm from:
* "Self-adjusting Binary Search Trees" by Sleator and Tarjan
*
* @param {number} key Key to splay the tree on.
* @private
*/
goog.structs.SplayTree.prototype.splay_ = function(key) {
if (this.isEmpty()) {
return;
}
// Create a dummy node. The use of the dummy node is a bit
// counter-intuitive: The right child of the dummy node will hold
// the L tree of the algorithm. The left child of the dummy node
// will hold the R tree of the algorithm. Using a dummy node, left
// and right will always be nodes and we avoid special cases.
var dummy, left, right;
dummy = left = right = new goog.structs.SplayTree.Node(null, null);
var current = this.root_;
while (true) {
if (key < current.key) {
if (!current.left) {
break;
}
if (key < current.left.key) {
// Rotate right.
var tmp = current.left;
current.left = tmp.right;
tmp.right = current;
current = tmp;
if (!current.left) {
break;
}
}
// Link right.
right.left = current;
right = current;
current = current.left;
} else if (key > current.key) {
if (!current.right) {
break;
}
if (key > current.right.key) {
// Rotate left.
var tmp = current.right;
current.right = tmp.left;
tmp.left = current;
current = tmp;
if (!current.right) {
break;
}
}
// Link left.
left.right = current;
left = current;
current = current.right;
} else {
break;
}
}
// Assemble.
left.right = current.left;
right.left = current.right;
current.left = dummy.right;
current.right = dummy.left;
this.root_ = current;
};
/**
* Performs a preorder traversal of the tree.
*
* @param {function(goog.structs.SplayTree.Node)} f Visitor function.
* @private
*/
goog.structs.SplayTree.prototype.traverse_ = function(f) {
var nodesToVisit = [this.root_];
while (nodesToVisit.length > 0) {
var node = nodesToVisit.shift();
if (node == null) {
continue;
}
f(node);
nodesToVisit.push(node.left);
nodesToVisit.push(node.right);
}
};
/**
* Constructs a Splay tree node.
*
* @param {number} key Key.
* @param {*} value Value.
*/
goog.structs.SplayTree.Node = function(key, value) {
this.key = key;
this.value = value;
};
/**
* @type {goog.structs.SplayTree.Node}
*/
goog.structs.SplayTree.Node.prototype.left = null;
/**
* @type {goog.structs.SplayTree.Node}
*/
goog.structs.SplayTree.Node.prototype.right = null;
/* profiler_processor.js */
// Copyright (c) 2009 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.
/**
* @fileoverview Profiler processor is used to process log file produced
* by V8 and produce an internal profile representation which is used
* for building profile views in 'Profiles' tab.
*/
goog.provide('devtools.profiler.Processor');
/**
* Creates a Profile View builder object compatible with WebKit Profiler UI.
*
* @param {number} samplingRate Number of ms between profiler ticks.
* @constructor
*/
devtools.profiler.WebKitViewBuilder = function(samplingRate) {
devtools.profiler.ViewBuilder.call(this, samplingRate);
};
goog.inherits(devtools.profiler.WebKitViewBuilder,
devtools.profiler.ViewBuilder);
/**
* @override
*/
devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(
funcName, totalTime, selfTime, head) {
return new devtools.profiler.WebKitViewNode(
funcName, totalTime, selfTime, head);
};
/**
* Constructs a Profile View node object for displaying in WebKit Profiler UI.
*
* @param {string} internalFuncName A fully qualified function name.
* @param {number} totalTime Amount of time that application spent in the
* corresponding function and its descendants (not that depending on
* profile they can be either callees or callers.)
* @param {number} selfTime Amount of time that application spent in the
* corresponding function only.
* @param {devtools.profiler.ProfileView.Node} head Profile view head.
* @constructor
*/
devtools.profiler.WebKitViewNode = function(
internalFuncName, totalTime, selfTime, head) {
devtools.profiler.ProfileView.Node.call(this,
internalFuncName, totalTime, selfTime, head);
this.initFuncInfo_();
this.callUID = internalFuncName;
};
goog.inherits(devtools.profiler.WebKitViewNode,
devtools.profiler.ProfileView.Node);
/**
* RegEx for stripping V8's prefixes of compiled functions.
*/
devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE =
/^(?:LazyCompile|Function|Callback): (.*)$/;
/**
* RegEx for extracting script source URL and line number.
*/
devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE =
/^((?:get | set )?[^ ]+) (.*):(\d+)( \{\d+\})?$/;
/**
* Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName'
* field.
* @private
*/
devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() {
var nodeAlias = devtools.profiler.WebKitViewNode;
this.functionName = this.internalFuncName;
var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
if (strippedName) {
this.functionName = strippedName[1];
}
var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
if (parsedName) {
this.functionName = parsedName[1];
if (parsedName[4]) {
this.functionName += parsedName[4];
}
this.url = parsedName[2];
this.lineNumber = parsedName[3];
} else {
this.url = '';
this.lineNumber = 0;
}
};
/**
* Ancestor of a profile object that leaves out only JS-related functions.
* @constructor
*/
devtools.profiler.JsProfile = function() {
devtools.profiler.Profile.call(this);
};
goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile);
/**
* RegExp that leaves only JS functions.
* @type {RegExp}
*/
devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script|Callback):/;
/**
* RegExp that filters out native code (ending with "native src.js:xxx").
* @type {RegExp}
*/
devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/;
/**
* RegExp that filters out native scripts.
* @type {RegExp}
*/
devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/;
/**
* @override
*/
devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) {
return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) ||
// To profile V8's natives comment out two lines below and '||' above.
devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) ||
devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name);
};
/**
* Profiler processor. Consumes profiler log and builds profile views.
*
* @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback
* that receives a new processed profile.
* @constructor
*/
devtools.profiler.Processor = function() {
devtools.profiler.LogReader.call(this, {
'code-creation': {
parsers: [null, this.createAddressParser('code'), parseInt, null],
processor: this.processCodeCreation_, backrefs: true,
needsProfile: true },
'code-move': { parsers: [this.createAddressParser('code'),
this.createAddressParser('code-move-to')],
processor: this.processCodeMove_, backrefs: true,
needsProfile: true },
'code-delete': { parsers: [this.createAddressParser('code')],
processor: this.processCodeDelete_, backrefs: true,
needsProfile: true },
'tick': { parsers: [this.createAddressParser('code'),
this.createAddressParser('stack'), parseInt, 'var-args'],
processor: this.processTick_, backrefs: true, needProfile: true },
'profiler': { parsers: [null, 'var-args'],
processor: this.processProfiler_, needsProfile: false },
'heap-sample-begin': { parsers: [null, null, parseInt],
processor: this.processHeapSampleBegin_ },
'heap-sample-stats': { parsers: [null, null, parseInt, parseInt],
processor: this.processHeapSampleStats_ },
'heap-sample-item': { parsers: [null, parseInt, parseInt],
processor: this.processHeapSampleItem_ },
'heap-js-cons-item': { parsers: [null, parseInt, parseInt],
processor: this.processHeapJsConsItem_ },
'heap-js-ret-item': { parsers: [null, 'var-args'],
processor: this.processHeapJsRetItem_ },
'heap-sample-end': { parsers: [null, null],
processor: this.processHeapSampleEnd_ },
// Not used in DevTools Profiler.
'shared-library': null,
// Obsolete row types.
'code-allocate': null,
'begin-code-region': null,
'end-code-region': null});
/**
* Callback that is called when a new profile is encountered in the log.
* @type {function()}
*/
this.startedProfileProcessing_ = null;
/**
* Callback that is called periodically to display processing status.
* @type {function()}
*/
this.profileProcessingStatus_ = null;
/**
* Callback that is called when a profile has been processed and is ready
* to be shown.
* @type {function(devtools.profiler.ProfileView)}
*/
this.finishedProfileProcessing_ = null;
/**
* The current profile.
* @type {devtools.profiler.JsProfile}
*/
this.currentProfile_ = null;
/**
* Builder of profile views. Created during "profiler,begin" event processing.
* @type {devtools.profiler.WebKitViewBuilder}
*/
this.viewBuilder_ = null;
/**
* Next profile id.
* @type {number}
*/
this.profileId_ = 1;
/**
* Counter for processed ticks.
* @type {number}
*/
this.ticksCount_ = 0;
/**
* Interval id for updating processing status.
* @type {number}
*/
this.processingInterval_ = null;
/**
* The current heap snapshot.
* @type {string}
*/
this.currentHeapSnapshot_ = null;
/**
* Next heap snapshot id.
* @type {number}
*/
this.heapSnapshotId_ = 1;
};
goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader);
/**
* @override
*/
devtools.profiler.Processor.prototype.printError = function(str) {
debugPrint(str);
};
/**
* @override
*/
devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) {
return dispatch.needsProfile && this.currentProfile_ == null;
};
/**
* Sets profile processing callbacks.
*
* @param {function()} started Started processing callback.
* @param {function(devtools.profiler.ProfileView)} finished Finished
* processing callback.
*/
devtools.profiler.Processor.prototype.setCallbacks = function(
started, processing, finished) {
this.startedProfileProcessing_ = started;
this.profileProcessingStatus_ = processing;
this.finishedProfileProcessing_ = finished;
};
/**
* An address for the fake "(program)" entry. WebKit's visualisation
* has assumptions on how the top of the call tree should look like,
* and we need to add a fake entry as the topmost function. This
* address is chosen because it's the end address of the first memory
* page, which is never used for code or data, but only as a guard
* page for catching AV errors.
*
* @type {number}
*/
devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff;
/**
* @type {string}
*/
devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff';
/**
* Sets new profile callback.
* @param {function(devtools.profiler.ProfileView)} callback Callback function.
*/
devtools.profiler.Processor.prototype.setNewProfileCallback = function(
callback) {
this.newProfileCallback_ = callback;
};
devtools.profiler.Processor.prototype.processProfiler_ = function(
state, params) {
switch (state) {
case 'resume':
if (this.currentProfile_ == null) {
this.currentProfile_ = new devtools.profiler.JsProfile();
// see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
this.currentProfile_.addCode(
'Function', '(program)',
devtools.profiler.Processor.PROGRAM_ENTRY, 1);
if (this.startedProfileProcessing_) {
this.startedProfileProcessing_();
}
this.ticksCount_ = 0;
var self = this;
if (this.profileProcessingStatus_) {
this.processingInterval_ = window.setInterval(
function() { self.profileProcessingStatus_(self.ticksCount_); },
1000);
}
}
break;
case 'pause':
if (this.currentProfile_ != null) {
window.clearInterval(this.processingInterval_);
this.processingInterval_ = null;
if (this.finishedProfileProcessing_) {
this.finishedProfileProcessing_(this.createProfileForView());
}
this.currentProfile_ = null;
}
break;
case 'begin':
var samplingRate = NaN;
if (params.length > 0) {
samplingRate = parseInt(params[0]);
}
if (isNaN(samplingRate)) {
samplingRate = 1;
}
this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate);
break;
// These events are valid but aren't used.
case 'compression':
case 'end': break;
default:
throw new Error('unknown profiler state: ' + state);
}
};
devtools.profiler.Processor.prototype.processCodeCreation_ = function(
type, start, size, name) {
this.currentProfile_.addCode(this.expandAlias(type), name, start, size);
};
devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) {
this.currentProfile_.moveCode(from, to);
};
devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) {
this.currentProfile_.deleteCode(start);
};
devtools.profiler.Processor.prototype.processTick_ = function(
pc, sp, vmState, stack) {
// see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
this.currentProfile_.recordTick(this.processStack(pc, stack));
this.ticksCount_++;
};
devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(
space, state, ticks) {
if (space != 'Heap') return;
this.currentHeapSnapshot_ = {
number: this.heapSnapshotId_++,
entries: {},
clusters: {},
lowlevels: {},
ticks: ticks
};
};
devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(
space, state, capacity, used) {
if (space != 'Heap') return;
this.currentHeapSnapshot_.capacity = capacity;
this.currentHeapSnapshot_.used = used;
};
devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(
item, number, size) {
if (!this.currentHeapSnapshot_) return;
this.currentHeapSnapshot_.lowlevels[item] = {
type: item, count: number, size: size
};
};
devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(
item, number, size) {
if (!this.currentHeapSnapshot_) return;
this.currentHeapSnapshot_.entries[item] = {
cons: item, count: number, size: size, retainers: {}
};
};
devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function(
item, retainersArray) {
if (!this.currentHeapSnapshot_) return;
var rawRetainers = {};
for (var i = 0, n = retainersArray.length; i < n; ++i) {
var entry = retainersArray[i].split(';');
rawRetainers[entry[0]] = parseInt(entry[1], 10);
}
function mergeRetainers(entry) {
for (var rawRetainer in rawRetainers) {
var consName = rawRetainer.indexOf(':') != -1 ?
rawRetainer.split(':')[0] : rawRetainer;
if (!(consName in entry.retainers))
entry.retainers[consName] = { cons: consName, count: 0, clusters: {} };
var retainer = entry.retainers[consName];
retainer.count += rawRetainers[rawRetainer];
if (consName != rawRetainer)
retainer.clusters[rawRetainer] = true;
}
}
if (item.indexOf(':') != -1) {
// Array, Function, or Object instances cluster case.
if (!(item in this.currentHeapSnapshot_.clusters)) {
this.currentHeapSnapshot_.clusters[item] = {
cons: item, retainers: {}
};
}
mergeRetainers(this.currentHeapSnapshot_.clusters[item]);
item = item.split(':')[0];
}
mergeRetainers(this.currentHeapSnapshot_.entries[item]);
};
devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(
space, state) {
if (space != 'Heap') return;
var snapshot = this.currentHeapSnapshot_;
this.currentHeapSnapshot_ = null;
// For some reason, 'used' from 'heap-sample-stats' sometimes differ from
// the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'.
snapshot.used = 0;
for (var item in snapshot.lowlevels) {
snapshot.used += snapshot.lowlevels[item].size;
}
WebInspector.panels.profiles.addSnapshot(snapshot);
};
/**
* Creates a profile for further displaying in ProfileView.
*/
devtools.profiler.Processor.prototype.createProfileForView = function() {
var profile = this.viewBuilder_.buildView(
this.currentProfile_.getTopDownProfile());
profile.uid = this.profileId_++;
profile.title = UserInitiatedProfileName + '.' + profile.uid;
return profile;
};
/* heap_profiler_panel.js */
// Copyright (c) 2009 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.
/**
* @fileoverview Heap profiler panel implementation.
*/
WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) {
snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number);
snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId;
var snapshots = WebInspector.HeapSnapshotProfileType.snapshots;
snapshots.push(snapshot);
snapshot.listIndex = snapshots.length - 1;
if (WebInspector.CPUProfile)
this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot);
else
this.addProfileHeader(snapshot);
this.dispatchEventToListeners("snapshot added");
}
WebInspector.HeapSnapshotView = function(parent, profile)
{
WebInspector.View.call(this);
this.element.addStyleClass("heap-snapshot-view");
this.parent = parent;
this.parent.addEventListener("snapshot 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 and Data"), "rgb(47,102,236)"),
other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
};
this.summaryBar = new WebInspector.SummaryBar(this.categories);
this.summaryBar.element.id = "heap-snapshot-summary";
this.summaryBar.calculator = new WebInspector.HeapSummaryCalculator(profile.used);
this.element.appendChild(this.summaryBar.element);
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 },
"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();
if (this.profile.listIndex > 0)
this.baseSelectElement.selectedIndex = this.profile.listIndex - 1;
else
this.baseSelectElement.selectedIndex = this.profile.listIndex;
this._resetDataGridList();
this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
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.trimWhitespace();
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.
var 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);
},
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 === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex])
return;
this._resetDataGridList();
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;
},
_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();
},
_resetDataGridList: function()
{
this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex];
var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
if (this.snapshotDataGridList)
lastComparator = this.snapshotDataGridList.lastComparator;
this.snapshotDataGridList = this._createSnapshotDataGridList();
this.snapshotDataGridList.sort(lastComparator, true);
},
_sortData: function()
{
var sortAscending = this.dataGrid.sortOrder === "ascending";
var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
var sortProperty = {
"cons": ["constructorName", 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 = WebInspector.HeapSnapshotProfileType.snapshots;
// 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");
baseOption.label = WebInspector.UIString("Compared to %s", list[i].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.summaryBar.calculator.showAsPercent = this._isShowingAsPercent;
this.summaryBar.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(total)
{
this.total = total;
}
WebInspector.HeapSummaryCalculator.prototype = {
computeSummaryValues: function(lowLevels)
{
function highFromLow(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 "other";
}
var highLevels = {data: 0, code: 0, other: 0};
for (var item in lowLevels) {
var highItem = highFromLow(item);
highLevels[highItem] += lowLevels[item].size;
}
return {categoryValues: highLevels};
},
formatValue: function(value)
{
if (this.showAsPercent)
return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
else
return Number.bytesToString(value);
},
get showAsPercent()
{
return this._showAsPercent;
},
set showAsPercent(x)
{
this._showAsPercent = x;
}
}
WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
{
this.profile = snapshot;
WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);
this.refreshTitles();
};
WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
get mainTitle()
{
if (this._mainTitle)
return this._mainTitle;
return this.profile.title;
},
set mainTitle(x)
{
this._mainTitle = x;
this.refreshTitles();
},
get subtitle()
{
if (this._subTitle)
return this._subTitle;
return WebInspector.UIString("Used %s of %s (%.0f%%)", Number.bytesToString(this.profile.used, null, false), Number.bytesToString(this.profile.capacity, null, false), this.profile.used / this.profile.capacity * 100.0);
},
set subtitle(x)
{
this._subTitle = x;
this.refreshTitles();
}
};
WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.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)
{
var self = this;
this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) {
self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree));
});
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
// Math minus sign, same width as plus.
return "\u2212";
},
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 = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
this.constructorName = snapshotEntry.cons;
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)
{
var self = this;
this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
});
},
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 = { cons: baseEntry.cons, count: 0, clusters: {} };
this.constructorName = snapshotEntry.cons;
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] = { cons: clusterRetainerEntry.cons, 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.snapshots = [];
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();
},
createSidebarTreeElementForProfile: function(profile)
{
var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile);
element.small = false;
return element;
},
createView: function(profile)
{
return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
}
}
WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
(function() {
var originalCreatePanels = WebInspector._createPanels;
WebInspector._createPanels = function() {
originalCreatePanels.apply(this, arguments);
if (WebInspector.panels.profiles)
WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
}
})();
/* devtools.js */
// Copyright (c) 2009 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.
/**
* @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.
*/
goog.provide('devtools.Tools');
goog.require('devtools.DebuggerAgent');
/**
* Dispatches raw message from the host.
* @param {string} remoteName
* @prama {string} methodName
* @param {string} param1, param2, param3 Arguments to dispatch.
*/
devtools$$dispatch = function(remoteName, methodName, param1, param2, param3) {
remoteName = 'Remote' + remoteName.substring(0, remoteName.length - 8);
var agent = window[remoteName];
if (!agent) {
debugPrint('No remote agent "' + remoteName + '" found.');
return;
}
var method = agent[methodName];
if (!method) {
debugPrint('No method "' + remoteName + '.' + methodName + '" found.');
return;
}
method.call(this, param1, param2, param3);
};
devtools.ToolsAgent = function() {
RemoteToolsAgent.DidDispatchOn =
WebInspector.Callback.processCallback;
RemoteToolsAgent.FrameNavigate =
goog.bind(this.frameNavigate_, this);
RemoteToolsAgent.DispatchOnClient =
goog.bind(this.dispatchOnClient_, this);
this.debuggerAgent_ = new devtools.DebuggerAgent();
};
/**
* Resets tools agent to its initial state.
*/
devtools.ToolsAgent.prototype.reset = function() {
InspectorFrontendHost.reset();
this.debuggerAgent_.reset();
};
/**
* @param {string} script Script exression to be evaluated in the context of the
* inspected page.
* @param {function(Object|string, boolean):undefined} opt_callback Function to
* call with the result.
*/
devtools.ToolsAgent.prototype.evaluateJavaScript = function(script,
opt_callback) {
InspectorBackend.evaluate(script, opt_callback || function() {});
};
/**
* @return {devtools.DebuggerAgent} Debugger agent instance.
*/
devtools.ToolsAgent.prototype.getDebuggerAgent = function() {
return this.debuggerAgent_;
};
/**
* @param {string} url Url frame navigated to.
* @see tools_agent.h
* @private
*/
devtools.ToolsAgent.prototype.frameNavigate_ = function(url) {
this.reset();
// Do not reset Profiles panel.
var profiles = null;
if ('profiles' in WebInspector.panels) {
profiles = WebInspector.panels['profiles'];
delete WebInspector.panels['profiles'];
}
WebInspector.reset();
if (profiles != null) {
WebInspector.panels['profiles'] = profiles;
}
};
/**
* @param {string} message Serialized call to be dispatched on WebInspector.
* @private
*/
devtools.ToolsAgent.prototype.dispatchOnClient_ = function(message) {
var args = JSON.parse(message);
var methodName = args[0];
var parameters = args.slice(1);
WebInspector[methodName].apply(WebInspector, parameters);
};
/**
* Evaluates js expression.
* @param {string} expr
*/
devtools.ToolsAgent.prototype.evaluate = function(expr) {
RemoteToolsAgent.evaluate(expr);
};
/**
* Enables / disables resources panel in the ui.
* @param {boolean} enabled New panel status.
*/
WebInspector.setResourcesPanelEnabled = function(enabled) {
InspectorBackend._resourceTrackingEnabled = enabled;
WebInspector.panels.resources.reset();
};
/**
* Prints string to the inspector console or shows alert if the console doesn't
* exist.
* @param {string} text
*/
function debugPrint(text) {
var console = WebInspector.console;
if (console) {
console.addMessage(new WebInspector.ConsoleMessage(
WebInspector.ConsoleMessage.MessageSource.JS,
WebInspector.ConsoleMessage.MessageType.Log,
WebInspector.ConsoleMessage.MessageLevel.Log,
1, 'chrome://devtools/<internal>', undefined, -1, text));
} else {
alert(text);
}
}
/**
* Global instance of the tools agent.
* @type {devtools.ToolsAgent}
*/
devtools.tools = null;
var context = {}; // Used by WebCore's inspector routines.
///////////////////////////////////////////////////////////////////////////////
// Here and below are overrides to existing WebInspector methods only.
// TODO(pfeldman): Patch WebCore and upstream changes.
var oldLoaded = WebInspector.loaded;
WebInspector.loaded = function() {
devtools.tools = new devtools.ToolsAgent();
devtools.tools.reset();
Preferences.ignoreWhitespace = false;
Preferences.samplingCPUProfiler = true;
Preferences.heapProfilerPresent = true;
oldLoaded.call(this);
// Hide dock button on Mac OS.
// TODO(pfeldman): remove once Mac OS docking is implemented.
if (InspectorFrontendHost.platform().indexOf('mac') == 0) {
document.getElementById('dock-status-bar-item').addStyleClass('hidden');
}
InspectorFrontendHost.loaded();
};
(function() {
/**
* Handles an F3 keydown event to focus the Inspector search box.
* @param {KeyboardEvent} event Event to optionally handle
* @return {boolean} whether the event has been handled
*/
function handleF3Keydown(event) {
if (event.keyIdentifier == 'F3' && !event.altKey && !event.ctrlKey &&
!event.shiftKey && !event.metaKey) {
var searchField = document.getElementById("search");
searchField.focus();
searchField.select();
event.preventDefault();
return true;
}
return false;
}
var oldKeyDown = WebInspector.documentKeyDown;
/**
* This override allows to intercept keydown events we want to handle in a
* custom way. Some nested documents (iframes) delegate keydown handling to
* WebInspector.documentKeyDown (e.g. SourceFrame).
* @param {KeyboardEvent} event
* @override
*/
WebInspector.documentKeyDown = function(event) {
var isHandled = handleF3Keydown(event);
if (!isHandled) {
// Mute refresh action.
if (event.keyIdentifier == 'F5') {
event.preventDefault();
} else if (event.keyIdentifier == 'U+0052' /* 'R' */ &&
(event.ctrlKey || event.metaKey)) {
event.preventDefault();
} else {
oldKeyDown.call(this, event);
}
}
};
})();
/**
* This override is necessary for adding script source asynchronously.
* @override
*/
WebInspector.ScriptView.prototype.setupSourceFrameIfNeeded = function() {
if (!this._frameNeedsSetup) {
return;
}
this.attach();
if (this.script.source) {
this.didResolveScriptSource_();
} else {
var self = this;
devtools.tools.getDebuggerAgent().resolveScriptSource(
this.script.sourceID,
function(source) {
self.script.source = source ||
WebInspector.UIString('<source is not available>');
self.didResolveScriptSource_();
});
}
};
/**
* Performs source frame setup when script source is aready resolved.
*/
WebInspector.ScriptView.prototype.didResolveScriptSource_ = function() {
if (!InspectorFrontendHost.addSourceToFrame(
"text/javascript", this.script.source, this.sourceFrame.element)) {
return;
}
delete this._frameNeedsSetup;
this.sourceFrame.addEventListener(
"syntax highlighting complete", this._syntaxHighlightingComplete, this);
this.sourceFrame.syntaxHighlightJavascript();
};
/**
* @param {string} type Type of the the property value('object' or 'function').
* @param {string} className Class name of the property value.
* @constructor
*/
WebInspector.UnresolvedPropertyValue = function(type, className) {
this.type = type;
this.className = className;
};
/**
* This function overrides standard searchableViews getters to perform search
* only in the current view (other views are loaded asynchronously, no way to
* search them yet).
*/
WebInspector.searchableViews_ = function() {
var views = [];
const visibleView = this.visibleView;
if (visibleView && visibleView.performSearch) {
views.push(visibleView);
}
return views;
};
/**
* @override
*/
WebInspector.ResourcesPanel.prototype.__defineGetter__(
'searchableViews',
WebInspector.searchableViews_);
/**
* @override
*/
WebInspector.ScriptsPanel.prototype.__defineGetter__(
'searchableViews',
WebInspector.searchableViews_);
(function() {
var oldShow = WebInspector.ScriptsPanel.prototype.show;
WebInspector.ScriptsPanel.prototype.show = function() {
devtools.tools.getDebuggerAgent().initUI();
this.enableToggleButton.visible = false;
oldShow.call(this);
};
})();
(function InterceptProfilesPanelEvents() {
var oldShow = WebInspector.ProfilesPanel.prototype.show;
WebInspector.ProfilesPanel.prototype.show = function() {
devtools.tools.getDebuggerAgent().initializeProfiling();
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));
};
// There is no clear way of setting frame title yet. So sniffing main resource
// load.
(function OverrideUpdateResource() {
var originalUpdateResource = WebInspector.updateResource;
WebInspector.updateResource = function(identifier, payload) {
originalUpdateResource.call(this, identifier, payload);
var resource = this.resources[identifier];
if (resource && resource.mainResource && resource.finished) {
document.title =
WebInspector.UIString('Developer Tools - %s', resource.url);
}
};
})();
// Highlight extension content scripts in the scripts list.
(function () {
var original = WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu;
WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu = function(script) {
var result = original.apply(this, arguments);
var debuggerAgent = devtools.tools.getDebuggerAgent();
var type = debuggerAgent.getScriptContextType(script.sourceID);
var option = script.filesSelectOption;
if (type == 'injected' && option) {
option.addStyleClass('injected');
}
return result;
};
})();
/** 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);
}
};
})();
(function () {
var orig = InjectedScriptAccess.getProperties;
InjectedScriptAccess.getProperties = function(
objectProxy, ignoreHasOwnProperty, callback) {
if (objectProxy.isScope) {
devtools.tools.getDebuggerAgent().resolveScope(objectProxy.objectId,
callback);
} else if (objectProxy.isV8Ref) {
devtools.tools.getDebuggerAgent().resolveChildren(objectProxy.objectId,
callback, false);
} else {
orig.apply(this, arguments);
}
};
})();
InjectedScriptAccess.evaluateInCallFrame = function(callFrameId, code,
objectGroup, callback)
{
//TODO(pfeldman): remove once 49084 is rolled.
if (!callback) {
callback = objectGroup;
}
devtools.tools.getDebuggerAgent().evaluateInCallFrame(
callFrameId, code, callback);
};
WebInspector.resourceTrackingWasEnabled = function()
{
InspectorBackend._resourceTrackingEnabled = true;
this.panels.resources.resourceTrackingWasEnabled();
};
WebInspector.resourceTrackingWasDisabled = function()
{
InspectorBackend._resourceTrackingEnabled = false;
this.panels.resources.resourceTrackingWasDisabled();
};
(function() {
var orig = WebInspector.ConsoleMessage.prototype.setMessageBody;
WebInspector.ConsoleMessage.prototype.setMessageBody = function(args) {
for (var i = 0; i < args.length; ++i) {
if (typeof args[i] == "string") {
args[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(args[i]);
}
}
orig.call(this, args);
};
})();
(function() {
var orig = InjectedScriptAccess.getCompletions;
InjectedScriptAccess.getCompletions = function(expressionString,
includeInspectorCommandLineAPI, callFrameId, reportCompletions) {
if (goog.isDef(callFrameId)) {
devtools.tools.getDebuggerAgent().resolveCompletionsOnFrame(
expressionString, callFrameId, reportCompletions);
} else {
return orig.apply(this, arguments);
}
};
})();
(function() {
WebInspector.ElementsPanel.prototype._nodeSearchButtonClicked = function(
event) {
InspectorBackend.toggleNodeSearch();
this.nodeSearchButton.toggled = !this.nodeSearchButton.toggled;
};
})();
(function() {
var originalAddToFrame = InspectorFrontendHost.addResourceSourceToFrame;
InspectorFrontendHost.addResourceSourceToFrame = function(identifier, element) {
var resource = WebInspector.resources[identifier];
if (!resource) {
return;
}
originalAddToFrame.call(this, identifier, resource.mimeType, element);
};
})();
/* devtools_host_stub.js */
// Copyright (c) 2009 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.
/**
* @fileoverview These stubs emulate backend functionality and allows
* DevTools frontend to function as a standalone web app.
*/
if (!window['RemoteDebuggerAgent']) {
/**
* @constructor
*/
RemoteDebuggerAgentStub = function() {
this.activeProfilerModules_ =
devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;
this.profileLogPos_ = 0;
this.heapProfSample_ = 0;
this.heapProfLog_ = '';
};
RemoteDebuggerAgentStub.prototype.GetContextId = function() {
RemoteDebuggerAgent.SetContextId(3);
};
RemoteDebuggerAgentStub.prototype.StopProfiling = function(modules) {
this.activeProfilerModules_ &= ~modules;
};
RemoteDebuggerAgentStub.prototype.StartProfiling = function(modules) {
var profModules = devtools.DebuggerAgent.ProfilerModules;
if (modules & profModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
if (modules & profModules.PROFILER_MODULE_HEAP_STATS) {
this.heapProfLog_ +=
'heap-sample-begin,"Heap","allocated",' +
(new Date()).getTime() + '\n' +
'heap-sample-stats,"Heap","allocated",10000,1000\n';
this.heapProfLog_ +=
'heap-sample-item,STRING_TYPE,100,1000\n' +
'heap-sample-item,CODE_TYPE,10,200\n' +
'heap-sample-item,MAP_TYPE,20,350\n';
this.heapProfLog_ += RemoteDebuggerAgentStub.HeapSamples[this.heapProfSample_++];
this.heapProfSample_ %= RemoteDebuggerAgentStub.HeapSamples.length;
this.heapProfLog_ +=
'heap-sample-end,"Heap","allocated"\n';
}
} else {
this.activeProfilerModules_ |= modules;
}
};
RemoteDebuggerAgentStub.prototype.GetActiveProfilerModules = function() {
var self = this;
setTimeout(function() {
RemoteDebuggerAgent.DidGetActiveProfilerModules(
self.activeProfilerModules_);
}, 100);
};
RemoteDebuggerAgentStub.prototype.GetNextLogLines = function() {
var profModules = devtools.DebuggerAgent.ProfilerModules;
var logLines = '';
if (this.activeProfilerModules_ & profModules.PROFILER_MODULE_CPU) {
if (this.profileLogPos_ < RemoteDebuggerAgentStub.ProfilerLogBuffer.length) {
this.profileLogPos_ += RemoteDebuggerAgentStub.ProfilerLogBuffer.length;
logLines += RemoteDebuggerAgentStub.ProfilerLogBuffer;
}
}
if (this.heapProfLog_) {
logLines += this.heapProfLog_;
this.heapProfLog_ = '';
}
setTimeout(function() {
RemoteDebuggerAgent.DidGetNextLogLines(logLines);
}, 100);
};
/**
* @constructor
*/
RemoteToolsAgentStub = function() {
};
RemoteToolsAgentStub.prototype.DispatchOnInjectedScript = function() {
};
RemoteToolsAgentStub.prototype.DispatchOnInspectorController = function() {
};
RemoteToolsAgentStub.prototype.ExecuteVoidJavaScript = function() {
};
RemoteDebuggerAgentStub.ProfilerLogBuffer =
'profiler,begin,1\n' +
'profiler,resume\n' +
'code-creation,LazyCompile,0x1000,256,"test1 http://aaa.js:1"\n' +
'code-creation,LazyCompile,0x2000,256,"test2 http://bbb.js:2"\n' +
'code-creation,LazyCompile,0x3000,256,"test3 http://ccc.js:3"\n' +
'tick,0x1010,0x0,3\n' +
'tick,0x2020,0x0,3,0x1010\n' +
'tick,0x2020,0x0,3,0x1010\n' +
'tick,0x3010,0x0,3,0x2020, 0x1010\n' +
'tick,0x2020,0x0,3,0x1010\n' +
'tick,0x2030,0x0,3,0x2020, 0x1010\n' +
'tick,0x2020,0x0,3,0x1010\n' +
'tick,0x1010,0x0,3\n' +
'profiler,pause\n';
RemoteDebuggerAgentStub.HeapSamples = [
'heap-js-cons-item,foo,1,100\n' +
'heap-js-cons-item,bar,20,2000\n' +
'heap-js-cons-item,Object,5,100\n' +
'heap-js-ret-item,foo,bar;3\n' +
'heap-js-ret-item,bar,foo;5\n' +
'heap-js-ret-item,Object:0x1234,(roots);1\n',
'heap-js-cons-item,foo,2000,200000\n' +
'heap-js-cons-item,bar,10,1000\n' +
'heap-js-cons-item,Object,6,120\n' +
'heap-js-ret-item,foo,bar;7,Object:0x1234;10\n' +
'heap-js-ret-item,bar,foo;10,Object:0x1234;10\n' +
'heap-js-ret-item,Object:0x1234,(roots);1\n',
'heap-js-cons-item,foo,15,1500\n' +
'heap-js-cons-item,bar,15,1500\n' +
'heap-js-cons-item,Object,5,100\n' +
'heap-js-cons-item,Array,3,1000\n' +
'heap-js-ret-item,foo,bar;3,Array:0x5678;1\n' +
'heap-js-ret-item,bar,foo;5,Object:0x1234;8,Object:0x5678;2\n' +
'heap-js-ret-item,Object:0x1234,(roots);1,Object:0x5678;2\n' +
'heap-js-ret-item,Object:0x5678,(global property);3,Object:0x1234;5\n' +
'heap-js-ret-item,Array:0x5678,(global property);3,Array:0x5678;2\n',
'heap-js-cons-item,bar,20,2000\n' +
'heap-js-cons-item,Object,6,120\n' +
'heap-js-ret-item,bar,foo;5,Object:0x1234;1,Object:0x1235;3\n' +
'heap-js-ret-item,Object:0x1234,(global property);3\n' +
'heap-js-ret-item,Object:0x1235,(global property);5\n',
'heap-js-cons-item,foo,15,1500\n' +
'heap-js-cons-item,bar,15,1500\n' +
'heap-js-cons-item,Array,10,1000\n' +
'heap-js-ret-item,foo,bar;1,Array:0x5678;1\n' +
'heap-js-ret-item,bar,foo;5\n' +
'heap-js-ret-item,Array:0x5678,(roots);3\n',
'heap-js-cons-item,bar,20,2000\n' +
'heap-js-cons-item,baz,15,1500\n' +
'heap-js-ret-item,bar,baz;3\n' +
'heap-js-ret-item,baz,bar;3\n'
];
/**
* @constructor
*/
RemoteDebuggerCommandExecutorStub = function() {
};
RemoteDebuggerCommandExecutorStub.prototype.DebuggerCommand = function(cmd) {
if ('{"seq":2,"type":"request","command":"scripts","arguments":{"' +
'includeSource":false}}' == cmd) {
var response1 =
'{"seq":5,"request_seq":2,"type":"response","command":"scripts","' +
'success":true,"body":[{"handle":61,"type":"script","name":"' +
'http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
'"lineCount":1,"sourceStart":"function fib(n) {","sourceLength":300,' +
'"scriptType":2,"compilationType":0,"context":{"ref":60}}],"refs":[{' +
'"handle":60,"type":"context","data":"page,3"}],"running":false}';
this.sendResponse_(response1);
} else if ('{"seq":3,"type":"request","command":"scripts","arguments":{' +
'"ids":[59],"includeSource":true}}' == cmd) {
this.sendResponse_(
'{"seq":8,"request_seq":3,"type":"response","command":"scripts",' +
'"success":true,"body":[{"handle":1,"type":"script","name":' +
'"http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
'"lineCount":1,"source":"function fib(n) {return n+1;}",' +
'"sourceLength":244,"scriptType":2,"compilationType":0,"context":{' +
'"ref":0}}],"refs":[{"handle":0,"type":"context","data":"page,3}],"' +
'"running":false}');
} else {
debugPrint('Unexpected command: ' + cmd);
}
};
RemoteDebuggerCommandExecutorStub.prototype.DebuggerPauseScript = function() {
};
RemoteDebuggerCommandExecutorStub.prototype.sendResponse_ = function(response) {
setTimeout(function() {
RemoteDebuggerAgent.DebuggerOutput(response);
}, 0);
};
DevToolsHostStub = function() {
this.isStub = true;
};
goog.inherits(DevToolsHostStub,
WebInspector.InspectorFrontendHostStub);
DevToolsHostStub.prototype.reset = function() {
};
DevToolsHostStub.prototype.setting = function() {
};
DevToolsHostStub.prototype.setSetting = function() {
};
window['RemoteDebuggerAgent'] = new RemoteDebuggerAgentStub();
window['RemoteDebuggerCommandExecutor'] =
new RemoteDebuggerCommandExecutorStub();
window['RemoteToolsAgent'] = new RemoteToolsAgentStub();
InspectorFrontendHost = new DevToolsHostStub();
}
/* tests.js */
// Copyright (c) 2009 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.
/**
* @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.
*/
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 the real injected host is present in the context.
*/
TestSuite.prototype.testHostIsPresent = function() {
this.assertTrue(typeof InspectorFrontendHost == 'object' &&
!InspectorFrontendHost.isStub);
};
/**
* Tests elements tree has an 'HTML' root.
*/
TestSuite.prototype.testElementsTreeRoot = function() {
var doc = WebInspector.domAgent.document;
this.assertEquals('HTML', doc.documentElement.nodeName);
this.assertTrue(doc.documentElement.hasChildNodes());
};
/**
* Tests that main resource is present in the system and that it is
* the only resource.
*/
TestSuite.prototype.testMainResource = function() {
var tokens = [];
var resources = WebInspector.resources;
for (var id in resources) {
tokens.push(resources[id].lastPathComponent);
}
this.assertEquals('simple_page.html', tokens.join(','));
};
/**
* Tests that resources tab is enabled when corresponding item is selected.
*/
TestSuite.prototype.testEnableResourcesTab = function() {
this.showPanel('resources');
var test = this;
this.addSniffer(WebInspector, 'addResource',
function(identifier, payload) {
test.assertEquals('simple_page.html', payload.lastPathComponent);
WebInspector.panels.resources.refresh();
WebInspector.panels.resources.revealAndSelectItem(
WebInspector.resources[identifier]);
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(identifier, payload) {
if (!payload.didLengthChange)
return;
var resource = WebInspector.resources[identifier];
if (!resource || !resource.url)
return;
if (resource.url.search('image.html$') != -1) {
var expectedLength = 87;
test.assertTrue(
resource.contentLength <= expectedLength,
'image.html content length is greater thatn expected.');
if (expectedLength == resource.contentLength) {
html = true;
}
} else if (resource.url.search('image.png') != -1) {
var expectedLength = 257796;
test.assertTrue(
resource.contentLength <= expectedLength,
'image.png content length is greater than expected.');
if (expectedLength == resource.contentLength) {
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 requestOk = false;
var responseOk = false;
var timingOk = false;
this.addSniffer(WebInspector, 'addResource',
function(identifier, payload) {
var resource = this.resources[identifier];
if (resource.mainResource) {
// We are only interested in secondary resources in this test.
return;
}
var requestHeaders = JSON.stringify(resource.requestHeaders);
test.assertContains(requestHeaders, 'Accept');
requestOk = true;
}, true);
this.addSniffer(WebInspector, 'updateResource',
function(identifier, payload) {
var resource = this.resources[identifier];
if (resource.mainResource) {
// We are only interested in secondary resources in this test.
return;
}
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(requestOk);
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(identifier, payload) {
var resource = this.resources[identifier];
if (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 test = this;
this.addSniffer(WebInspector.panels.profiles, 'addProfileHeader',
function(typeOrProfile, profile) {
if (!profile) {
profile = typeOrProfile;
}
var panel = WebInspector.panels.profiles;
panel.showProfile(profile);
var node = panel.visibleView.profileDataGridTree.children[0];
// Iterate over displayed functions and search for a function
// that is called 'fib' or 'eternal_fib'. If found, it 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();
});
var ticksCount = 0;
var tickRecord = '\nt,';
this.addSniffer(RemoteDebuggerAgent, 'DidGetNextLogLines',
function(log) {
var pos = 0;
while ((pos = log.indexOf(tickRecord, pos)) != -1) {
pos += tickRecord.length;
ticksCount++;
}
if (ticksCount > 100) {
InspectorBackend.stopProfiling();
}
}, true);
InspectorBackend.startProfiling();
this.takeControl();
};
/**
* Tests that scripts tab can be open and populated with inspected scripts.
*/
TestSuite.prototype.testShowScriptsTab = function() {
var parsedDebuggerTestPageHtml = false;
// Intercept parsedScriptSource calls to check that all expected scripts are
// added to the debugger.
var test = this;
var receivedConsoleApiSource = false;
this.addSniffer(WebInspector, 'parsedScriptSource',
function(sourceID, sourceURL, source, startingLine) {
if (sourceURL == undefined) {
if (receivedConsoleApiSource) {
test.fail('Unexpected script without URL');
} else {
receivedConsoleApiSource = true;
}
} else if (sourceURL.search(/debugger_test_page.html$/) != -1) {
if (parsedDebuggerTestPageHtml) {
test.fail('Unexpected parse event: ' + sourceURL);
}
parsedDebuggerTestPageHtml = true;
if (!WebInspector.panels.scripts.visibleView) {
test.fail('No visible script view: ' + sourceURL);
}
} else {
test.fail('Unexpected script URL: ' + sourceURL);
}
// There should be two scripts: one for the main page and another
// one which is source of console API(see
// InjectedScript._ensureCommandLineAPIInstalled).
if (parsedDebuggerTestPageHtml && receivedConsoleApiSource) {
test.releaseControl();
}
}, true /* sticky */);
this.showPanel('scripts');
// 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(devtools.DebuggerAgent.prototype, 'reset',
waitUntilScriptIsParsed);
// Reload inspected page. It will reset the debugger agent.
test.evaluateInConsole_(
'window.location.reload(true);',
function(resultText) {
test.assertEquals('undefined', resultText,
'Unexpected result of reload().');
});
function waitUntilScriptIsParsed() {
var parsed = devtools.tools.getDebuggerAgent().parsedScripts_;
for (var id in parsed) {
var url = parsed[id].getUrl();
if (url && url.search('debugger_test_page.html$') != -1) {
checkScriptsPanel();
return;
}
}
test.addSniffer(devtools.DebuggerAgent.prototype, 'addScriptInfo_',
waitUntilScriptIsParsed);
}
function checkScriptsPanel() {
test.showPanel('scripts');
test.assertTrue(test._scriptsAreParsed(['debugger_test_page.html$']),
'Inspected script not found in the scripts list');
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 a breakpoint can be set.
*/
TestSuite.prototype.testSetBreakpoint = function() {
var test = this;
this.showPanel('scripts');
var breakpointLine = 12;
this._waitUntilScriptsAreParsed(['debugger_test_page.html'],
function() {
test.showMainPageScriptSource_(
'debugger_test_page.html',
function(view, url) {
view._addBreakpoint(breakpointLine);
// Force v8 execution.
RemoteToolsAgent.ExecuteVoidJavaScript();
test.waitForSetBreakpointResponse_(url, breakpointLine,
function() {
test.releaseControl();
});
});
});
this.takeControl();
};
/**
* Tests that pause on exception works.
*/
TestSuite.prototype.testPauseOnException = function() {
this.showPanel('scripts');
var test = this;
// Make sure pause on exceptions is on.
if (!WebInspector.currentPanel.pauseOnExceptionButton.toggled) {
WebInspector.currentPanel.pauseOnExceptionButton.element.click();
}
this._executeCodeWhenScriptsAreParsed(
'handleClick()',
['pause_on_exception.html$']);
this._waitForScriptPause(
{
functionsOnStack: ['throwAnException', 'handleClick',
'(anonymous function)'],
lineNumber: 6,
lineText: ' return unknown_var;'
},
function() {
test.releaseControl();
});
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', '(anonymous function)'],
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._isContentLoaded()) {
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.console.visible = true;
WebInspector.console.prompt.text = code;
WebInspector.console.promptElement.handleKeyEvent(
new TestSuite.KeyEvent('Enter'));
this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
function(commandResult) {
callback(commandResult.toMessageElement().textContent);
});
};
/*
* Waits for 'setbreakpoint' response, checks that corresponding breakpoint
* was successfully set and invokes the callback if it was.
* @param {string} scriptUrl
* @param {number} breakpointLine
* @param {function()} callback
*/
TestSuite.prototype.waitForSetBreakpointResponse_ = function(scriptUrl,
breakpointLine,
callback) {
var test = this;
test.addSniffer(
devtools.DebuggerAgent.prototype,
'handleSetBreakpointResponse_',
function(msg) {
var bps = this.urlToBreakpoints_[scriptUrl];
test.assertTrue(!!bps, 'No breakpoints for line ' + breakpointLine);
var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine);
test.assertTrue(!!bps[line].getV8Id(),
'Breakpoint id was not assigned.');
callback();
});
};
/**
* Tests eval on call frame.
*/
TestSuite.prototype.testEvalOnCallFrame = function() {
this.showPanel('scripts');
var breakpointLine = 16;
var test = this;
this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_',
function(msg) {
test.showMainPageScriptSource_(
'debugger_test_page.html',
function(view, url) {
view._addBreakpoint(breakpointLine);
// Force v8 execution.
RemoteToolsAgent.ExecuteVoidJavaScript();
test.waitForSetBreakpointResponse_(url, breakpointLine,
setBreakpointCallback);
});
});
function setBreakpointCallback() {
// Since breakpoints are ignored in evals' calculate() function is
// execute after zero-timeout so that the breakpoint is hit.
test.evaluateInConsole_(
'setTimeout("calculate(123)" , 0)',
function(resultText) {
test.assertTrue(!isNaN(resultText),
'Failed to get timer id: ' + resultText);
waitForBreakpointHit();
});
}
function waitForBreakpointHit() {
test.addSniffer(
devtools.DebuggerAgent.prototype,
'handleBacktraceResponse_',
function(msg) {
test.assertEquals(2, this.callFrames_.length,
'Unexpected stack depth on the breakpoint. ' +
JSON.stringify(msg));
test.assertEquals('calculate', this.callFrames_[0].functionName,
'Unexpected top frame function.');
// Evaluate 'e+1' where 'e' is an argument of 'calculate' function.
test.evaluateInConsole_(
'e+1',
function(resultText) {
test.assertEquals('124', resultText, 'Unexpected "e+1" value.');
test.releaseControl();
});
});
}
this.takeControl();
};
/**
* 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',
'(anonymous function)'],
lineNumber: 9,
lineText: ' debugger;'
},
showConsole);
function showConsole() {
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();
};
/**
* Tests that inspected page doesn't hang on reload if it contains a syntax
* error and DevTools window is open.
*/
TestSuite.prototype.testAutoContinueOnSyntaxError = function() {
this.showPanel('scripts');
var test = this;
function checkScriptsList() {
var scriptSelect = document.getElementById('scripts-files');
var options = scriptSelect.options;
// There should be only console API source (see
// InjectedScript._ensureCommandLineAPIInstalled) since the page script
// contains a syntax error.
for (var i = 0 ; i < options.length; i++) {
if (options[i].text.search('script_syntax_error.html$') != -1) {
test.fail('Script with syntax error should not be in the list of ' +
'parsed scripts.');
}
}
}
this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_',
function(msg) {
checkScriptsList();
// Reload inspected page.
test.evaluateInConsole_(
'window.location.reload(true);',
function(resultText) {
test.assertEquals('undefined', resultText,
'Unexpected result of reload().');
waitForExceptionEvent();
});
});
function waitForExceptionEvent() {
var exceptionCount = 0;
test.addSniffer(
devtools.DebuggerAgent.prototype,
'handleExceptionEvent_',
function(msg) {
exceptionCount++;
test.assertEquals(1, exceptionCount, 'Too many exceptions.');
test.assertEquals(undefined, msg.getBody().script,
'Unexpected exception: ' + JSON.stringify(msg));
test.releaseControl();
});
// Check that the script is not paused on parse error.
test.addSniffer(
WebInspector,
'pausedScript',
function(callFrames) {
test.fail('Script execution should not pause on syntax error.');
});
}
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) {
var sourceRow = sourceFrame.sourceRow(lineNumber);
var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
this.assertEquals(lineNumber, sourceFrame.executionLine,
'Unexpected execution line number.');
this.assertEquals(lineContent, line.textContent,
'Unexpected execution line text.');
this.assertTrue(!!sourceRow, 'Execution line not found');
this.assertTrue(sourceRow.hasStyleClass('webkit-execution-line'),
'Execution line ' + lineNumber + ' is not highlighted. Class: ' +
sourceRow.className);
}
/**
* 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(callFrames) {
test.assertEquals(expectations.functionsOnStack.length,
callFrames.length,
'Unexpected stack depth');
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._isContentLoaded()) {
checkExecLine();
} else {
frame.addEventListener('content loaded', checkExecLine);
}
function checkExecLine() {
test._checkExecutionLine(frame, expectations.lineNumber,
expectations.lineText);
// Make sure we don't listen anymore.
frame.removeEventListener('content loaded', checkExecLine);
callback();
}
};
/**
* Performs sequence of steps.
* @param {Array.<Object|Function>} Array [expectations1,action1,expectations2,
* action2,...,actionN].
*/
TestSuite.prototype._performSteps = function(actions) {
var test = this;
var i = 0;
function doNextAction() {
if (i > 0) {
actions[i++]();
}
if (i < actions.length - 1) {
test._waitForScriptPause(actions[i++], doNextAction);
}
}
doNextAction();
};
/**
* 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);
});
}
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();
};
/**
* Waits until all debugger scripts are parsed and executes 'a()' in the
* inspected page.
*/
TestSuite.prototype._executeFunctionForStepTest = function() {
this._executeCodeWhenScriptsAreParsed(
'a()',
['debugger_step.html$', 'debugger_step.js$']);
};
/**
* Tests step over in the debugger.
*/
TestSuite.prototype.testStepOver = function() {
this.showPanel('scripts');
var test = this;
this._executeFunctionForStepTest();
this._performSteps([
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 3,
lineText: ' debugger;'
},
function() {
document.getElementById('scripts-step-over').click();
},
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 5,
lineText: ' var y = fact(10);'
},
function() {
document.getElementById('scripts-step-over').click();
},
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 6,
lineText: ' return y;'
},
function() {
test.releaseControl();
}
]);
test.takeControl();
};
/**
* Tests step out in the debugger.
*/
TestSuite.prototype.testStepOut = function() {
this.showPanel('scripts');
var test = this;
this._executeFunctionForStepTest();
this._performSteps([
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 3,
lineText: ' debugger;'
},
function() {
document.getElementById('scripts-step-out').click();
},
{
functionsOnStack: ['a','(anonymous function)'],
lineNumber: 8,
lineText: ' printResult(result);'
},
function() {
test.releaseControl();
}
]);
test.takeControl();
};
/**
* Tests step in in the debugger.
*/
TestSuite.prototype.testStepIn = function() {
this.showPanel('scripts');
var test = this;
this._executeFunctionForStepTest();
this._performSteps([
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 3,
lineText: ' debugger;'
},
function() {
document.getElementById('scripts-step-over').click();
},
{
functionsOnStack: ['d','a','(anonymous function)'],
lineNumber: 5,
lineText: ' var y = fact(10);'
},
function() {
document.getElementById('scripts-step-into').click();
},
{
functionsOnStack: ['fact','d','a','(anonymous function)'],
lineNumber: 15,
lineText: ' return r;'
},
function() {
test.releaseControl();
}
]);
test.takeControl();
};
/**
* Gets a XPathResult matching given xpath.
* @param {string} xpath
* @param {number} resultType
* @param {Node} opt_ancestor Context node. If not specified documentElement
* will be used
* @return {XPathResult} Type of returned value is determined by 'resultType' parameter
*/
TestSuite.prototype._evaluateXpath = function(
xpath, resultType, opt_ancestor) {
if (!opt_ancestor) {
opt_ancestor = document.documentElement;
}
try {
return document.evaluate(xpath, opt_ancestor, null, resultType, null);
} catch(e) {
this.fail('Error in expression: "' + xpath + '".' + e);
}
};
/**
* Gets first Node matching given xpath.
* @param {string} xpath
* @param {Node} opt_ancestor Context node. If not specified documentElement
* will be used
* @return {?Node}
*/
TestSuite.prototype._findNode = function(xpath, opt_ancestor) {
var result = this._evaluateXpath(xpath, XPathResult.FIRST_ORDERED_NODE_TYPE,
opt_ancestor).singleNodeValue;
this.assertTrue(!!result, 'Cannot find node on path: ' + xpath);
return result;
};
/**
* Gets a text matching given xpath.
* @param {string} xpath
* @param {Node} opt_ancestor Context node. If not specified documentElement
* will be used
* @return {?string}
*/
TestSuite.prototype._findText = function(xpath, opt_ancestor) {
var result = this._evaluateXpath(xpath, XPathResult.STRING_TYPE,
opt_ancestor).stringValue;
this.assertTrue(!!result, 'Cannot find text on path: ' + xpath);
return result;
};
/**
* Gets an iterator over nodes matching given xpath.
* @param {string} xpath
* @param {Node} opt_ancestor Context node. If not specified, documentElement
* will be used
* @return {XPathResult} Iterator over the nodes
*/
TestSuite.prototype._nodeIterator = function(xpath, opt_ancestor) {
return this._evaluateXpath(xpath, XPathResult.ORDERED_NODE_ITERATOR_TYPE,
opt_ancestor);
};
/**
* Checks the scopeSectionDiv against the expectations.
* @param {Node} scopeSectionDiv The section div
* @param {Object} expectations Expectations dictionary
*/
TestSuite.prototype._checkScopeSectionDiv = function(
scopeSectionDiv, expectations) {
var scopeTitle = this._findText(
'./div[@class="header"]/div[@class="title"]/text()', scopeSectionDiv);
this.assertEquals(expectations.title, scopeTitle,
'Unexpected scope section title.');
if (!expectations.properties) {
return;
}
this.assertTrue(scopeSectionDiv.hasStyleClass('expanded'), 'Section "' +
scopeTitle + '" is collapsed.');
var propertyIt = this._nodeIterator('./ol[@class="properties"]/li',
scopeSectionDiv);
var propertyLi;
var foundProps = [];
while (propertyLi = propertyIt.iterateNext()) {
var name = this._findText('./span[@class="name"]/text()', propertyLi);
var value = this._findText('./span[@class="value"]/text()', propertyLi);
this.assertTrue(!!name, 'Invalid variable name: "' + name + '"');
this.assertTrue(name in expectations.properties,
'Unexpected property: ' + name);
this.assertEquals(expectations.properties[name], value,
'Unexpected "' + name + '" property value.');
delete expectations.properties[name];
foundProps.push(name + ' = ' + value);
}
// Check that all expected properties were found.
for (var p in expectations.properties) {
this.fail('Property "' + p + '" was not found in scope "' + scopeTitle +
'". Found properties: "' + foundProps.join(',') + '"');
}
};
/**
* Expands scope sections matching the filter and invokes the callback on
* success.
* @param {function(WebInspector.ObjectPropertiesSection, number):boolean}
* filter
* @param {Function} callback
*/
TestSuite.prototype._expandScopeSections = function(filter, callback) {
var sections = WebInspector.currentPanel.sidebarPanes.scopechain.sections;
var toBeUpdatedCount = 0;
function updateListener() {
--toBeUpdatedCount;
if (toBeUpdatedCount == 0) {
// Report when all scopes are expanded and populated.
callback();
}
}
// Global scope is always the last one.
for (var i = 0; i < sections.length - 1; i++) {
var section = sections[i];
if (!filter(sections, i)) {
continue;
}
++toBeUpdatedCount;
var populated = section.populated;
this._hookGetPropertiesCallback(updateListener,
function() {
section.expand();
if (populated) {
// Make sure 'updateProperties' callback will be called at least once
// after it was overridden.
section.update();
}
});
}
};
/**
* Tests that scopes can be expanded and contain expected data.
*/
TestSuite.prototype.testExpandScope = function() {
this.showPanel('scripts');
var test = this;
this._executeCodeWhenScriptsAreParsed(
'handleClick()',
['debugger_closure.html$']);
this._waitForScriptPause(
{
functionsOnStack: ['innerFunction', 'handleClick',
'(anonymous function)'],
lineNumber: 8,
lineText: ' debugger;'
},
expandAllSectionsExceptGlobal);
// Expanding Global scope takes for too long so we skeep it.
function expandAllSectionsExceptGlobal() {
test._expandScopeSections(function(sections, i) {
return i < sections.length - 1;
},
examineScopes /* When all scopes are expanded and populated check
them. */);
}
// Check scope sections contents.
function examineScopes() {
var scopeVariablesSection = test._findNode('//div[@id="scripts-sidebar"]/' +
'div[div[@class="title"]/text()="Scope Variables"]');
var expectedScopes = [
{
title: 'Local',
properties: {
x:2009,
innerFunctionLocalVar:2011,
'this': 'global',
}
},
{
title: 'Closure',
properties: {
n:'TextParam',
makeClosureLocalVar:'local.TextParam',
}
},
{
title: 'Global',
},
];
var it = test._nodeIterator('./div[@class="body"]/div',
scopeVariablesSection);
var scopeIndex = 0;
var scopeDiv;
while (scopeDiv = it.iterateNext()) {
test.assertTrue(scopeIndex < expectedScopes.length,
'Too many scopes.');
test._checkScopeSectionDiv(scopeDiv, expectedScopes[scopeIndex]);
++scopeIndex;
}
test.assertEquals(expectedScopes.length, scopeIndex,
'Unexpected number of scopes.');
test.releaseControl();
}
test.takeControl();
};
/**
* Returns child tree element for a property with given name.
* @param {TreeElement} parent Parent tree element.
* @param {string} childName
* @param {string} objectPath Path to the object. Will be printed in the case
* of failure.
* @return {TreeElement}
*/
TestSuite.prototype._findChildProperty = function(
parent, childName, objectPath) {
var children = parent.children;
for (var i = 0; i < children.length; i++) {
var treeElement = children[i];
var property = treeElement.property;
if (property.name == childName) {
return treeElement;
}
}
this.fail('Cannot find property "' + childName + '" in ' + objectPath);
};
/**
* 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 orig = InjectedScriptAccess.getProperties;
InjectedScriptAccess.getProperties = function(objectProxy,
ignoreHasOwnProperty, callback) {
orig.call(InjectedScriptAccess, objectProxy, ignoreHasOwnProperty,
function() {
callback.apply(this, arguments);
hook();
});
};
try {
code();
} finally {
InjectedScriptAccess.getProperties = orig;
}
};
/**
* Tests that all elements in prototype chain of an object have expected
* intrinic proprties(__proto__, constructor, prototype).
*/
TestSuite.prototype.testDebugIntrinsicProperties = function() {
this.showPanel('scripts');
var test = this;
this._executeCodeWhenScriptsAreParsed(
'handleClick()',
['debugger_intrinsic_properties.html$']);
this._waitForScriptPause(
{
functionsOnStack: ['callDebugger', 'handleClick',
'(anonymous function)'],
lineNumber: 29,
lineText: ' debugger;'
},
expandLocalScope);
var localScopeSection = null;
function expandLocalScope() {
test._expandScopeSections(function(sections, i) {
if (i == 0) {
test.assertTrue(sections[i].object.isLocal, 'Scope #0 is not Local.');
localScopeSection = sections[i];
return true;
}
return false;
},
examineLocalScope);
}
function examineLocalScope() {
var scopeExpectations = [
'a', 'Object', [
'constructor', 'function Child()', [
'constructor', 'function Function()', null,
'name', 'Child', null,
'prototype', 'Object', [
'childProtoField', '21', null
]
],
'__proto__', 'Object', [
'__proto__', 'Object', [
'__proto__', 'Object', [
'__proto__', 'null', null,
'constructor', 'function Object()', null,
],
'constructor', 'function Parent()', [
'name', 'Parent', null,
'prototype', 'Object', [
'parentProtoField', '11', null,
]
],
'parentProtoField', '11', null,
],
'constructor', 'function Child()', null,
'childProtoField', '21', null,
],
'parentField', '10', null,
'childField', '20', null,
]
];
checkProperty(
localScopeSection.propertiesTreeOutline,
'<Local Scope>',
scopeExpectations);
}
var propQueue = [];
var index = 0;
var expectedFinalIndex = 8;
function expandAndCheckNextProperty() {
if (index == propQueue.length) {
test.assertEquals(expectedFinalIndex, index,
'Unexpected number of expanded objects.');
test.releaseControl();
return;
}
// Read next property data from the queue.
var treeElement = propQueue[index].treeElement;
var path = propQueue[index].path;
var expectations = propQueue[index].expectations;
index++;
// Expand the property.
test._hookGetPropertiesCallback(function() {
checkProperty(treeElement, path, expectations);
},
function() {
treeElement.expand();
});
}
function checkProperty(treeElement, path, expectations) {
for (var i = 0; i < expectations.length; i += 3) {
var name = expectations[i];
var description = expectations[i+1];
var value = expectations[i+2];
var propertyPath = path + '.' + name;
var propertyTreeElement = test._findChildProperty(
treeElement, name, path);
test.assertTrue(propertyTreeElement,
'Property "' + propertyPath + '" not found.');
test.assertEquals(description,
propertyTreeElement.property.value.description,
'Unexpected "' + propertyPath + '" description.');
if (value) {
// Schedule property content check.
propQueue.push({
treeElement: propertyTreeElement,
path: propertyPath,
expectations: value,
});
}
}
// Check next property in the queue.
expandAndCheckNextProperty();
}
test.takeControl();
};
/**
* 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.KeyEvent = function(key) {
this.keyIdentifier = key;
};
TestSuite.KeyEvent.prototype.preventDefault = function() {};
TestSuite.KeyEvent.prototype.stopPropagation = function() {};
/**
* Tests console eval.
*/
TestSuite.prototype.testConsoleEval = function() {
var test = this;
this.evaluateInConsole_('123',
function(resultText) {
test.assertEquals('123', resultText);
test.releaseControl();
});
this.takeControl();
};
/**
* Tests console log.
*/
TestSuite.prototype.testConsoleLog = function() {
WebInspector.console.visible = true;
var messages = WebInspector.console.messages;
var index = 0;
var test = this;
var assertNext = function(line, message, opt_class, opt_count, opt_substr) {
var elem = messages[index++].toMessageElement();
var clazz = elem.getAttribute('class');
var expectation = (opt_count || '') + 'console_test_page.html:' +
line + message;
if (opt_substr) {
test.assertContains(elem.textContent, expectation);
} else {
test.assertEquals(expectation, elem.textContent);
}
if (opt_class) {
test.assertContains(clazz, 'console-' + opt_class);
}
};
assertNext('5', 'log', 'log-level');
assertNext('7', 'debug', 'log-level');
assertNext('9', 'info', 'log-level');
assertNext('11', 'warn', 'warning-level');
assertNext('13', 'error', 'error-level');
assertNext('15', 'Message format number 1, 2 and 3.5');
assertNext('17', 'Message format for string');
assertNext('19', 'Object Object');
assertNext('22', 'repeated', 'log-level', 5);
assertNext('26', 'count: 1');
assertNext('26', 'count: 2');
assertNext('29', 'group', 'group-title');
index++;
assertNext('33', 'timer:', 'log-level', '', true);
assertNext('35', '1 2 3', 'log-level');
assertNext('37', 'HTMLDocument', 'log-level');
assertNext('39', '<html>', 'log-level', '', true);
};
/**
* Tests eval of global objects.
*/
TestSuite.prototype.testEvalGlobal = function() {
WebInspector.console.visible = true;
var inputs = ['foo', 'foobar'];
var expectations = ['foo', 'fooValue',
'foobar', 'ReferenceError: foobar is not defined'];
// Do not change code below - simply add inputs and expectations above.
var initEval = function(input) {
WebInspector.console.prompt.text = input;
WebInspector.console.promptElement.handleKeyEvent(
new TestSuite.KeyEvent('Enter'));
};
var test = this;
var messagesCount = 0;
var inputIndex = 0;
this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
function(commandResult) {
messagesCount++;
if (messagesCount == expectations.length) {
var messages = WebInspector.console.messages;
for (var i = 0; i < expectations; ++i) {
var elem = messages[i++].toMessageElement();
test.assertEquals(elem.textContent, expectations[i]);
}
test.releaseControl();
} else if (messagesCount % 2 == 0) {
initEval(inputs[inputIndex++]);
}
}, true);
initEval(inputs[inputIndex++]);
this.takeControl();
};
/**
* Tests that Storage panel can be open and that local DOM storage is added
* to the panel.
*/
TestSuite.prototype.testShowStoragePanel = function() {
var test = this;
this.addSniffer(WebInspector.panels.storage, 'addDOMStorage',
function(storage) {
var orig = storage.getEntries;
storage.getEntries = function(callback) {
orig.call(this, function(entries) {
callback(entries);
test.releaseControl();
});
};
try {
WebInspector.currentPanel.selectDOMStorage(storage.id);
storage.getEntries = orig;
} catch (e) {
test.fail('Exception in selectDOMStorage: ' + e);
}
});
this.showPanel('storage');
// Access localStorage so that it's pushed to the frontend.
this.evaluateInConsole_(
'setTimeout("localStorage.x = 10" , 0)',
function(resultText) {
test.assertTrue(!isNaN(resultText),
'Failed to get timer id: ' + resultText);
});
// Wait until DOM storage is added to the panel.
this.takeControl();
};
/**
* 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) {
new TestSuite().runTest(name);
};
}