/* 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, "&").replace(/</g, "<").replace(/>/g, ">"); | |
} | |
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\"></" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "></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\"><" + 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>=​\""; | |
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​"); | |
info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>"; | |
} | |
info.title += "\"</span>"; | |
} | |
} | |
info.title += "></span>​"; | |
// 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>​<span class=\"webkit-html-tag\"></" + node.nodeName.toLowerCase().escapeHTML() + "></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\"><!--" + node.nodeValue.escapeHTML() + "--></span>"; | |
break; | |
case Node.DOCUMENT_TYPE_NODE: | |
info.title = "<span class=\"webkit-html-doctype\"><!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 += "></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\"> </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); | |
}; | |
} | |