1157 lines
44 KiB
JavaScript
1157 lines
44 KiB
JavaScript
/*
|
|
* 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.
|
|
*/
|
|
|
|
var 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.setAttribute("contenteditable", "true");
|
|
this.promptElement.className = "source-code";
|
|
this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
|
|
this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");
|
|
this.prompt.history = WebInspector.settings.consoleHistory;
|
|
|
|
this.topGroup = new WebInspector.ConsoleGroup(null);
|
|
this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
|
|
this.currentGroup = this.topGroup;
|
|
|
|
this.toggleConsoleButton = document.getElementById("console-status-bar-item");
|
|
this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
|
|
this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
|
|
|
|
// Will hold the list of filter elements
|
|
this.filterBarElement = document.getElementById("console-filter");
|
|
|
|
function createDividerElement() {
|
|
var dividerElement = document.createElement("div");
|
|
dividerElement.addStyleClass("scope-bar-divider");
|
|
this.filterBarElement.appendChild(dividerElement);
|
|
}
|
|
|
|
var updateFilterHandler = this._updateFilter.bind(this);
|
|
function createFilterElement(category, label) {
|
|
var categoryElement = document.createElement("li");
|
|
categoryElement.category = category;
|
|
categoryElement.className = category;
|
|
categoryElement.addEventListener("click", updateFilterHandler, false);
|
|
categoryElement.textContent = label;
|
|
|
|
this.filterBarElement.appendChild(categoryElement);
|
|
|
|
return categoryElement;
|
|
}
|
|
|
|
this.allElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));
|
|
createDividerElement.call(this);
|
|
this.errorElement = createFilterElement.call(this, "errors", WebInspector.UIString("Errors"));
|
|
this.warningElement = createFilterElement.call(this, "warnings", WebInspector.UIString("Warnings"));
|
|
this.logElement = createFilterElement.call(this, "logs", WebInspector.UIString("Logs"));
|
|
|
|
this.filter(this.allElement, false);
|
|
this._registerShortcuts();
|
|
|
|
this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
|
|
|
|
this._customFormatters = {
|
|
"object": this._formatobject,
|
|
"array": this._formatarray,
|
|
"node": this._formatnode,
|
|
"string": this._formatstring
|
|
};
|
|
|
|
this._registerConsoleDomainDispatcher();
|
|
}
|
|
|
|
WebInspector.ConsoleView.prototype = {
|
|
_registerConsoleDomainDispatcher: function() {
|
|
var console = this;
|
|
var dispatcher = {
|
|
addConsoleMessage: function(payload)
|
|
{
|
|
var consoleMessage = new WebInspector.ConsoleMessage(
|
|
payload.source,
|
|
payload.type,
|
|
payload.level,
|
|
payload.line,
|
|
payload.url,
|
|
payload.repeatCount,
|
|
payload.message,
|
|
payload.parameters,
|
|
payload.stackTrace,
|
|
payload.requestId);
|
|
console.addMessage(consoleMessage);
|
|
},
|
|
|
|
updateConsoleMessageExpiredCount: function(count)
|
|
{
|
|
var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count);
|
|
console.addMessage(WebInspector.ConsoleMessage.createTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning));
|
|
},
|
|
|
|
updateConsoleMessageRepeatCount: function(count)
|
|
{
|
|
var msg = console.previousMessage;
|
|
var prevRepeatCount = msg.totalRepeatCount;
|
|
|
|
if (!console.commandSincePreviousMessage) {
|
|
msg.repeatDelta = count - prevRepeatCount;
|
|
msg.repeatCount = msg.repeatCount + msg.repeatDelta;
|
|
msg.totalRepeatCount = count;
|
|
msg._updateRepeatCount();
|
|
console._incrementErrorWarningCount(msg);
|
|
} else {
|
|
var msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, count - prevRepeatCount, msg._messageText, msg._parameters, msg._stackTrace, msg._requestId);
|
|
msgCopy.totalRepeatCount = count;
|
|
msgCopy._formatMessage();
|
|
console.addMessage(msgCopy);
|
|
}
|
|
},
|
|
|
|
consoleMessagesCleared: function()
|
|
{
|
|
console.clearMessages();
|
|
},
|
|
}
|
|
InspectorBackend.registerDomainDispatcher("Console", dispatcher);
|
|
},
|
|
|
|
_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;
|
|
|
|
if (target.category === "all") {
|
|
if (target.hasStyleClass("selected")) {
|
|
// We can't unselect all, so we break early here
|
|
return;
|
|
}
|
|
|
|
unselectAll.call(this);
|
|
} else {
|
|
// Something other than all is being selected, so we want to unselect all
|
|
if (this.allElement.hasStyleClass("selected")) {
|
|
this.allElement.removeStyleClass("selected");
|
|
this.messagesElement.removeStyleClass("filter-all");
|
|
}
|
|
}
|
|
|
|
if (!selectMultiple) {
|
|
// If multiple selection is off, we want to unselect everything else
|
|
// and just select ourselves.
|
|
unselectAll.call(this);
|
|
|
|
target.addStyleClass("selected");
|
|
this.messagesElement.addStyleClass(targetFilterClass);
|
|
|
|
return;
|
|
}
|
|
|
|
if (target.hasStyleClass("selected")) {
|
|
// If selectMultiple is turned on, and we were selected, we just
|
|
// want to unselect ourselves.
|
|
target.removeStyleClass("selected");
|
|
this.messagesElement.removeStyleClass(targetFilterClass);
|
|
} else {
|
|
// If selectMultiple is turned on, and we weren't selected, we just
|
|
// want to select ourselves.
|
|
target.addStyleClass("selected");
|
|
this.messagesElement.addStyleClass(targetFilterClass);
|
|
}
|
|
},
|
|
|
|
_toggleConsoleButtonClicked: function()
|
|
{
|
|
this.drawer.visibleView = this;
|
|
},
|
|
|
|
attach: function(mainElement, statusBarElement)
|
|
{
|
|
mainElement.appendChild(this.element);
|
|
statusBarElement.appendChild(this.clearButton);
|
|
statusBarElement.appendChild(this.filterBarElement);
|
|
},
|
|
|
|
show: function()
|
|
{
|
|
this.toggleConsoleButton.addStyleClass("toggled-on");
|
|
this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
|
|
if (!this.prompt.isCaretInsidePrompt())
|
|
this.prompt.moveCaretToEndOfPrompt();
|
|
},
|
|
|
|
afterShow: function()
|
|
{
|
|
WebInspector.currentFocusElement = this.promptElement;
|
|
},
|
|
|
|
hide: function()
|
|
{
|
|
this.toggleConsoleButton.removeStyleClass("toggled-on");
|
|
this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
|
|
},
|
|
|
|
_scheduleScrollIntoView: function()
|
|
{
|
|
if (this._scrollIntoViewTimer)
|
|
return;
|
|
|
|
function scrollIntoView()
|
|
{
|
|
this.promptElement.scrollIntoView(true);
|
|
delete this._scrollIntoViewTimer;
|
|
}
|
|
this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
|
|
},
|
|
|
|
addMessage: function(msg)
|
|
{
|
|
var shouldScrollToLastMessage = this.messagesElement.isScrolledToBottom();
|
|
|
|
if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
|
|
this._incrementErrorWarningCount(msg);
|
|
WebInspector.resourceTreeModel.addConsoleMessage(msg);
|
|
WebInspector.panels.scripts.addConsoleMessage(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) {
|
|
var parentGroup = this.currentGroup.parentGroup
|
|
if (parentGroup)
|
|
this.currentGroup = parentGroup;
|
|
} else {
|
|
if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
|
|
var group = new WebInspector.ConsoleGroup(this.currentGroup);
|
|
this.currentGroup.messagesElement.appendChild(group.element);
|
|
this.currentGroup = group;
|
|
}
|
|
|
|
this.currentGroup.addMessage(msg);
|
|
}
|
|
|
|
if (shouldScrollToLastMessage)
|
|
this._scheduleScrollIntoView();
|
|
},
|
|
|
|
_incrementErrorWarningCount: function(msg)
|
|
{
|
|
switch (msg.level) {
|
|
case WebInspector.ConsoleMessage.MessageLevel.Warning:
|
|
WebInspector.warnings += msg.repeatDelta;
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Error:
|
|
WebInspector.errors += msg.repeatDelta;
|
|
break;
|
|
}
|
|
},
|
|
|
|
requestClearMessages: function()
|
|
{
|
|
InspectorBackend.clearConsoleMessages();
|
|
},
|
|
|
|
clearMessages: function()
|
|
{
|
|
WebInspector.resourceTreeModel.clearConsoleMessages();
|
|
WebInspector.panels.scripts.clearConsoleMessages();
|
|
|
|
this.messages = [];
|
|
|
|
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 includeCommandLineAPI = (!dotNotation && !bracketNotation);
|
|
var injectedScriptAccess;
|
|
if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused)
|
|
InspectorBackend.getCompletionsOnCallFrame(WebInspector.panels.scripts.selectedCallFrameId(), expressionString, includeCommandLineAPI, reportCompletions);
|
|
else
|
|
InspectorBackend.getCompletions(expressionString, includeCommandLineAPI, 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.keys(result).sort();
|
|
|
|
for (var i = 0; i < properties.length; ++i) {
|
|
var property = properties[i];
|
|
|
|
if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
|
|
continue;
|
|
|
|
if (bracketNotation) {
|
|
if (!/^[0-9]+$/.test(property))
|
|
property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
|
|
property += "]";
|
|
}
|
|
|
|
if (property.length < prefix.length)
|
|
continue;
|
|
if (property.indexOf(prefix) !== 0)
|
|
continue;
|
|
|
|
results.push(property);
|
|
if (bestMatchOnly)
|
|
break;
|
|
}
|
|
completionsReadyCallback(results);
|
|
},
|
|
|
|
_clearButtonClicked: function()
|
|
{
|
|
this.requestClearMessages();
|
|
},
|
|
|
|
_handleContextMenuEvent: function(event)
|
|
{
|
|
if (!window.getSelection().isCollapsed) {
|
|
// If there is a selection, we want to show our normal context menu
|
|
// (with Copy, etc.), and not Clear Console.
|
|
return;
|
|
}
|
|
|
|
var itemAction = function () {
|
|
WebInspector.settings.monitoringXHREnabled = !WebInspector.settings.monitoringXHREnabled;
|
|
InspectorBackend.setMonitoringXHREnabled(WebInspector.settings.monitoringXHREnabled);
|
|
}.bind(this);
|
|
var contextMenu = new WebInspector.ContextMenu();
|
|
contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.settings.monitoringXHREnabled)
|
|
contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this));
|
|
contextMenu.show(event);
|
|
},
|
|
|
|
_messagesSelectStart: function(event)
|
|
{
|
|
if (this._selectionTimeout)
|
|
clearTimeout(this._selectionTimeout);
|
|
|
|
this.prompt.clearAutoComplete();
|
|
|
|
function moveBackIfOutside()
|
|
{
|
|
delete this._selectionTimeout;
|
|
if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
|
|
this.prompt.moveCaretToEndOfPrompt();
|
|
this.prompt.autoCompleteSoon();
|
|
}
|
|
|
|
this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
|
|
},
|
|
|
|
_messagesClicked: function(event)
|
|
{
|
|
var link = event.target.enclosingNodeOrSelfWithNodeName("a");
|
|
if (!link || !link.representedNode)
|
|
return;
|
|
|
|
WebInspector.updateFocusedNode(link.representedNode.id);
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
},
|
|
|
|
_registerShortcuts: function()
|
|
{
|
|
this._shortcuts = {};
|
|
|
|
var shortcut = WebInspector.KeyboardShortcut;
|
|
var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
|
|
// This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers.
|
|
this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this);
|
|
this._shortcuts[shortcutK.key].isMacOnly = true;
|
|
|
|
var clearConsoleHandler = this.requestClearMessages.bind(this);
|
|
var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
|
|
this._shortcuts[shortcutL.key] = clearConsoleHandler;
|
|
|
|
var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
|
|
var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ];
|
|
section.addAlternateKeys(keys, WebInspector.UIString("Clear Console"));
|
|
|
|
keys = [
|
|
shortcut.shortcutToString(shortcut.Keys.Tab),
|
|
shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
|
|
];
|
|
section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion"));
|
|
section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
|
|
keys = [
|
|
shortcut.shortcutToString(shortcut.Keys.Down),
|
|
shortcut.shortcutToString(shortcut.Keys.Up)
|
|
];
|
|
section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
|
|
keys = [
|
|
shortcut.shortcutToString("N", shortcut.Modifiers.Alt),
|
|
shortcut.shortcutToString("P", shortcut.Modifiers.Alt)
|
|
];
|
|
if (WebInspector.isMac())
|
|
section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
|
|
section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
|
|
},
|
|
|
|
_promptKeyDown: function(event)
|
|
{
|
|
if (isEnterKey(event)) {
|
|
this._enterKeyPressed(event);
|
|
return;
|
|
}
|
|
|
|
var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
|
|
var handler = this._shortcuts[shortcut];
|
|
if (handler) {
|
|
if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
|
|
handler();
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
evalInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, callback)
|
|
{
|
|
if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
|
|
WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, includeCommandLineAPI, callback);
|
|
return;
|
|
}
|
|
|
|
if (!expression) {
|
|
// There is no expression, so the completion should happen against global properties.
|
|
expression = "this";
|
|
}
|
|
|
|
function evalCallback(result)
|
|
{
|
|
callback(WebInspector.RemoteObject.fromPayload(result));
|
|
}
|
|
InspectorBackend.evaluate(expression, objectGroup, includeCommandLineAPI, evalCallback);
|
|
},
|
|
|
|
_enterKeyPressed: function(event)
|
|
{
|
|
if (event.altKey || event.ctrlKey || event.shiftKey)
|
|
return;
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
this.prompt.clearAutoComplete(true);
|
|
|
|
var str = this.prompt.text;
|
|
if (!str.length)
|
|
return;
|
|
|
|
var commandMessage = new WebInspector.ConsoleCommand(str);
|
|
this.addMessage(commandMessage);
|
|
|
|
var self = this;
|
|
function printResult(result)
|
|
{
|
|
self.prompt.history.push(str);
|
|
self.prompt.historyOffset = 0;
|
|
self.prompt.text = "";
|
|
|
|
WebInspector.settings.consoleHistory = self.prompt.history.slice(-30);
|
|
|
|
self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
|
|
}
|
|
this.evalInInspectedWindow(str, "console", true, printResult);
|
|
},
|
|
|
|
_format: function(output, forceObjectFormat)
|
|
{
|
|
var isProxy = (output != null && typeof output === "object");
|
|
var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output));
|
|
|
|
var formatter = this._customFormatters[type];
|
|
if (!formatter || !isProxy) {
|
|
formatter = this._formatvalue;
|
|
output = output.description;
|
|
}
|
|
|
|
var span = document.createElement("span");
|
|
span.className = "console-formatted-" + type + " source-code";
|
|
formatter.call(this, output, span);
|
|
return span;
|
|
},
|
|
|
|
_formatvalue: function(val, elem)
|
|
{
|
|
elem.appendChild(document.createTextNode(val));
|
|
},
|
|
|
|
_formatobject: function(obj, elem)
|
|
{
|
|
elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
|
|
},
|
|
|
|
_formatnode: function(object, elem)
|
|
{
|
|
function printNode(nodeId)
|
|
{
|
|
if (!nodeId) {
|
|
// Sometimes DOM is loaded after the sync message is being formatted, so we get no
|
|
// nodeId here. So we fall back to object formatting here.
|
|
this._formatobject(object, elem);
|
|
return;
|
|
}
|
|
var treeOutline = new WebInspector.ElementsTreeOutline();
|
|
treeOutline.showInElementsPanelEnabled = true;
|
|
treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
|
|
treeOutline.element.addStyleClass("outline-disclosure");
|
|
if (!treeOutline.children[0].hasChildren)
|
|
treeOutline.element.addStyleClass("single-node");
|
|
elem.appendChild(treeOutline.element);
|
|
}
|
|
object.pushNodeToFrontend(printNode.bind(this));
|
|
},
|
|
|
|
_formatarray: function(arr, elem)
|
|
{
|
|
arr.getOwnProperties(false, this._printArray.bind(this, elem));
|
|
},
|
|
|
|
_formatstring: function(output, elem)
|
|
{
|
|
var span = document.createElement("span");
|
|
span.className = "console-formatted-string source-code";
|
|
span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
|
|
|
|
// Make black quotes.
|
|
elem.removeStyleClass("console-formatted-string");
|
|
elem.appendChild(document.createTextNode("\""));
|
|
elem.appendChild(span);
|
|
elem.appendChild(document.createTextNode("\""));
|
|
},
|
|
|
|
_printArray: function(elem, properties)
|
|
{
|
|
if (!properties)
|
|
return;
|
|
|
|
var elements = [];
|
|
for (var i = 0; i < properties.length; ++i) {
|
|
var name = properties[i].name;
|
|
if (name == parseInt(name))
|
|
elements[name] = this._formatAsArrayEntry(properties[i].value);
|
|
}
|
|
|
|
elem.appendChild(document.createTextNode("["));
|
|
for (var i = 0; i < elements.length; ++i) {
|
|
var element = elements[i];
|
|
if (element)
|
|
elem.appendChild(element);
|
|
else
|
|
elem.appendChild(document.createTextNode("undefined"))
|
|
if (i < elements.length - 1)
|
|
elem.appendChild(document.createTextNode(", "));
|
|
}
|
|
elem.appendChild(document.createTextNode("]"));
|
|
},
|
|
|
|
_formatAsArrayEntry: function(output)
|
|
{
|
|
// Prevent infinite expansion of cross-referencing arrays.
|
|
return this._format(output, WebInspector.RemoteObject.type(output) === "array");
|
|
}
|
|
}
|
|
|
|
WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
|
|
|
|
WebInspector.ConsoleMessage = function(source, type, level, line, url, repeatCount, message, parameters, stackTrace, requestId)
|
|
{
|
|
this.source = source;
|
|
this.type = type;
|
|
this.level = level;
|
|
this.line = line;
|
|
this.url = url;
|
|
this.repeatCount = repeatCount;
|
|
this.repeatDelta = repeatCount;
|
|
this.totalRepeatCount = repeatCount;
|
|
this._messageText = message;
|
|
this._parameters = parameters;
|
|
this._stackTrace = stackTrace;
|
|
this._requestId = requestId;
|
|
|
|
if (stackTrace && stackTrace.length) {
|
|
var topCallFrame = stackTrace[0];
|
|
if (!this.url)
|
|
this.url = topCallFrame.scriptName;
|
|
if (!this.line)
|
|
this.line = topCallFrame.lineNumber;
|
|
}
|
|
|
|
this._formatMessage();
|
|
}
|
|
|
|
WebInspector.ConsoleMessage.createTextMessage = function(text, level)
|
|
{
|
|
level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
|
|
return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, 1, null, [text], null);
|
|
}
|
|
|
|
WebInspector.ConsoleMessage.prototype = {
|
|
_formatMessage: function()
|
|
{
|
|
var stackTrace = this._stackTrace;
|
|
var messageText;
|
|
switch (this.type) {
|
|
case WebInspector.ConsoleMessage.MessageType.Trace:
|
|
messageText = document.createTextNode("console.trace()");
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.UncaughtException:
|
|
messageText = document.createTextNode(this._messageText);
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.NetworkError:
|
|
var resource = this._requestId && WebInspector.networkResourceById(this._requestId);
|
|
if (resource) {
|
|
stackTrace = resource.stackTrace;
|
|
|
|
messageText = document.createElement("span");
|
|
messageText.appendChild(document.createTextNode(resource.requestMethod + " "));
|
|
messageText.appendChild(WebInspector.linkifyURLAsNode(resource.url));
|
|
if (resource.failed)
|
|
messageText.appendChild(document.createTextNode(" " + resource.localizedFailDescription));
|
|
else
|
|
messageText.appendChild(document.createTextNode(" " + resource.statusCode + " (" + resource.statusText + ")"));
|
|
} else
|
|
messageText = this._format([this._messageText]);
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Assert:
|
|
var args = [WebInspector.UIString("Assertion failed:")];
|
|
if (this._parameters)
|
|
args = args.concat(this._parameters);
|
|
messageText = this._format(args);
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Object:
|
|
var obj = this._parameters ? this._parameters[0] : undefined;
|
|
var args = ["%O", obj];
|
|
messageText = this._format(args);
|
|
break;
|
|
default:
|
|
var args = this._parameters || [this._messageText];
|
|
messageText = this._format(args);
|
|
break;
|
|
}
|
|
|
|
this._formattedMessage = document.createElement("span");
|
|
this._formattedMessage.className = "console-message-text source-code";
|
|
|
|
if (this.url && this.url !== "undefined") {
|
|
var urlElement = WebInspector.linkifyResourceAsNode(this.url, "scripts", this.line, "console-message-url");
|
|
this._formattedMessage.appendChild(urlElement);
|
|
}
|
|
|
|
this._formattedMessage.appendChild(messageText);
|
|
|
|
if (this._stackTrace) {
|
|
switch (this.type) {
|
|
case WebInspector.ConsoleMessage.MessageType.Trace:
|
|
case WebInspector.ConsoleMessage.MessageType.UncaughtException:
|
|
case WebInspector.ConsoleMessage.MessageType.NetworkError:
|
|
case WebInspector.ConsoleMessage.MessageType.Assert: {
|
|
var ol = document.createElement("ol");
|
|
ol.className = "outline-disclosure";
|
|
var treeOutline = new TreeOutline(ol);
|
|
|
|
var content = this._formattedMessage;
|
|
var root = new TreeElement(content, null, true);
|
|
content.treeElementForTest = root;
|
|
treeOutline.appendChild(root);
|
|
if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
|
|
root.expand();
|
|
|
|
this._populateStackTraceTreeElement(root);
|
|
this._formattedMessage = ol;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is used for inline message bubbles in SourceFrames, or other plain-text representations.
|
|
this.message = this._formattedMessage.textContent;
|
|
},
|
|
|
|
isErrorOrWarning: function()
|
|
{
|
|
return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
|
|
},
|
|
|
|
_format: function(parameters)
|
|
{
|
|
// This node is used like a Builder. Values are continually appended onto it.
|
|
var formattedResult = document.createElement("span");
|
|
if (!parameters.length)
|
|
return formattedResult;
|
|
|
|
// Formatting code below assumes that parameters are all wrappers whereas frontend console
|
|
// API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
|
|
for (var i = 0; i < parameters.length; ++i) {
|
|
if (typeof parameters[i] === "object")
|
|
parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
|
|
else
|
|
parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
|
|
}
|
|
|
|
// There can be string log and string eval result. We distinguish between them based on message type.
|
|
var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
|
|
|
|
// Multiple parameters with the first being a format string. Save unused substitutions.
|
|
if (shouldFormatMessage) {
|
|
// Multiple parameters with the first being a format string. Save unused substitutions.
|
|
var result = this._formatWithSubstitutionString(parameters, formattedResult);
|
|
parameters = result.unusedSubstitutions;
|
|
if (parameters.length)
|
|
formattedResult.appendChild(document.createTextNode(" "));
|
|
}
|
|
|
|
// Single parameter, or unused substitutions from above.
|
|
for (var i = 0; i < parameters.length; ++i) {
|
|
// Inline strings when formatting.
|
|
if (shouldFormatMessage && parameters[i].type === "string")
|
|
formattedResult.appendChild(document.createTextNode(parameters[i].description));
|
|
else
|
|
formattedResult.appendChild(WebInspector.console._format(parameters[i]));
|
|
if (i < parameters.length - 1)
|
|
formattedResult.appendChild(document.createTextNode(" "));
|
|
}
|
|
return formattedResult;
|
|
},
|
|
|
|
_formatWithSubstitutionString: function(parameters, formattedResult)
|
|
{
|
|
var formatters = {}
|
|
for (var i in String.standardFormatters)
|
|
formatters[i] = String.standardFormatters[i];
|
|
|
|
function consoleFormatWrapper(force)
|
|
{
|
|
return function(obj) {
|
|
return WebInspector.console._format(obj, force);
|
|
};
|
|
}
|
|
|
|
// Firebug uses %o for formatting objects.
|
|
formatters.o = consoleFormatWrapper();
|
|
// Firebug allows both %i and %d for formatting integers.
|
|
formatters.i = formatters.d;
|
|
// Support %O to force object formatting, instead of the type-based %o formatting.
|
|
formatters.O = consoleFormatWrapper(true);
|
|
|
|
function append(a, b)
|
|
{
|
|
if (!(b instanceof Node))
|
|
a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
|
|
else
|
|
a.appendChild(b);
|
|
return a;
|
|
}
|
|
|
|
// String.format does treat formattedResult like a Builder, result is an object.
|
|
return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
|
|
},
|
|
|
|
toMessageElement: function()
|
|
{
|
|
if (this._element)
|
|
return this._element;
|
|
|
|
var element = document.createElement("div");
|
|
element.message = this;
|
|
element.className = "console-message";
|
|
|
|
this._element = element;
|
|
|
|
switch (this.level) {
|
|
case WebInspector.ConsoleMessage.MessageLevel.Tip:
|
|
element.addStyleClass("console-tip-level");
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Log:
|
|
element.addStyleClass("console-log-level");
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Debug:
|
|
element.addStyleClass("console-debug-level");
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Warning:
|
|
element.addStyleClass("console-warning-level");
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Error:
|
|
element.addStyleClass("console-error-level");
|
|
break;
|
|
}
|
|
|
|
if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
|
|
element.addStyleClass("console-group-title");
|
|
|
|
if (this.elementsTreeOutline) {
|
|
element.addStyleClass("outline-disclosure");
|
|
element.appendChild(this.elementsTreeOutline.element);
|
|
return element;
|
|
}
|
|
|
|
element.appendChild(this._formattedMessage);
|
|
|
|
if (this.repeatCount > 1)
|
|
this._updateRepeatCount();
|
|
|
|
return element;
|
|
},
|
|
|
|
_populateStackTraceTreeElement: function(parentTreeElement)
|
|
{
|
|
for (var i = 0; i < this._stackTrace.length; i++) {
|
|
var frame = this._stackTrace[i];
|
|
|
|
var content = document.createElement("div");
|
|
var messageTextElement = document.createElement("span");
|
|
messageTextElement.className = "console-message-text source-code";
|
|
var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
|
|
messageTextElement.appendChild(document.createTextNode(functionName));
|
|
content.appendChild(messageTextElement);
|
|
|
|
var urlElement = WebInspector.linkifyResourceAsNode(frame.scriptName, "scripts", frame.lineNumber, "console-message-url");
|
|
content.appendChild(urlElement);
|
|
|
|
var treeElement = new TreeElement(content);
|
|
parentTreeElement.appendChild(treeElement);
|
|
}
|
|
},
|
|
|
|
_updateRepeatCount: function() {
|
|
if (!this.repeatCountElement) {
|
|
this.repeatCountElement = document.createElement("span");
|
|
this.repeatCountElement.className = "bubble";
|
|
|
|
this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
|
|
this._element.addStyleClass("repeated-message");
|
|
}
|
|
this.repeatCountElement.textContent = this.repeatCount;
|
|
},
|
|
|
|
toString: function()
|
|
{
|
|
var sourceString;
|
|
switch (this.source) {
|
|
case WebInspector.ConsoleMessage.MessageSource.HTML:
|
|
sourceString = "HTML";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageSource.WML:
|
|
sourceString = "WML";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageSource.XML:
|
|
sourceString = "XML";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageSource.JS:
|
|
sourceString = "JS";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageSource.CSS:
|
|
sourceString = "CSS";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageSource.Other:
|
|
sourceString = "Other";
|
|
break;
|
|
}
|
|
|
|
var typeString;
|
|
switch (this.type) {
|
|
case WebInspector.ConsoleMessage.MessageType.Log:
|
|
case WebInspector.ConsoleMessage.MessageType.UncaughtException:
|
|
case WebInspector.ConsoleMessage.MessageType.NetworkError:
|
|
typeString = "Log";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Object:
|
|
typeString = "Object";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Trace:
|
|
typeString = "Trace";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
|
|
case WebInspector.ConsoleMessage.MessageType.StartGroup:
|
|
typeString = "Start Group";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.EndGroup:
|
|
typeString = "End Group";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Assert:
|
|
typeString = "Assert";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageType.Result:
|
|
typeString = "Result";
|
|
break;
|
|
}
|
|
|
|
var levelString;
|
|
switch (this.level) {
|
|
case WebInspector.ConsoleMessage.MessageLevel.Tip:
|
|
levelString = "Tip";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Log:
|
|
levelString = "Log";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Warning:
|
|
levelString = "Warning";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Debug:
|
|
levelString = "Debug";
|
|
break;
|
|
case WebInspector.ConsoleMessage.MessageLevel.Error:
|
|
levelString = "Error";
|
|
break;
|
|
}
|
|
|
|
return sourceString + " " + typeString + " " + levelString + ": " + this._formattedMessage.textContent + "\n" + this.url + " line " + this.line;
|
|
},
|
|
|
|
isEqual: function(msg)
|
|
{
|
|
if (!msg)
|
|
return false;
|
|
|
|
if (this._stackTrace) {
|
|
if (!msg._stackTrace)
|
|
return false;
|
|
var l = this._stackTrace;
|
|
var r = msg._stackTrace;
|
|
for (var i = 0; i < l.length; i++) {
|
|
if (l[i].scriptName !== r[i].scriptName ||
|
|
l[i].functionName !== r[i].functionName ||
|
|
l[i].lineNumber !== r[i].lineNumber ||
|
|
l[i].column !== r[i].column)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (this.source === msg.source)
|
|
&& (this.type === msg.type)
|
|
&& (this.level === msg.level)
|
|
&& (this.line === msg.line)
|
|
&& (this.url === msg.url)
|
|
&& (this.message === msg.message)
|
|
&& (this._requestId === msg._requestId);
|
|
}
|
|
}
|
|
|
|
// Note: Keep these constants in sync with the ones in Console.h
|
|
WebInspector.ConsoleMessage.MessageSource = {
|
|
HTML: 0,
|
|
WML: 1,
|
|
XML: 2,
|
|
JS: 3,
|
|
CSS: 4,
|
|
Other: 5
|
|
}
|
|
|
|
WebInspector.ConsoleMessage.MessageType = {
|
|
Log: 0,
|
|
Object: 1,
|
|
Trace: 2,
|
|
StartGroup: 3,
|
|
StartGroupCollapsed: 4,
|
|
EndGroup: 5,
|
|
Assert: 6,
|
|
UncaughtException: 7,
|
|
NetworkError:8,
|
|
Result: 9
|
|
}
|
|
|
|
WebInspector.ConsoleMessage.MessageLevel = {
|
|
Tip: 0,
|
|
Log: 1,
|
|
Warning: 2,
|
|
Error: 3,
|
|
Debug: 4
|
|
}
|
|
|
|
WebInspector.ConsoleCommand = function(command)
|
|
{
|
|
this.command = command;
|
|
}
|
|
|
|
WebInspector.ConsoleCommand.prototype = {
|
|
toMessageElement: function()
|
|
{
|
|
var element = document.createElement("div");
|
|
element.command = this;
|
|
element.className = "console-user-command";
|
|
|
|
var commandTextElement = document.createElement("span");
|
|
commandTextElement.className = "console-message-text source-code";
|
|
commandTextElement.textContent = this.command;
|
|
element.appendChild(commandTextElement);
|
|
|
|
return element;
|
|
}
|
|
}
|
|
|
|
WebInspector.ConsoleCommandResult = function(result, originatingCommand)
|
|
{
|
|
var level = (result.isError() ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
|
|
this.originatingCommand = originatingCommand;
|
|
WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, -1, null, 1, null, [result]);
|
|
}
|
|
|
|
WebInspector.ConsoleCommandResult.prototype = {
|
|
toMessageElement: function()
|
|
{
|
|
var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
|
|
element.addStyleClass("console-user-command-result");
|
|
return element;
|
|
}
|
|
}
|
|
|
|
WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
|
|
|
|
WebInspector.ConsoleGroup = function(parentGroup)
|
|
{
|
|
this.parentGroup = parentGroup;
|
|
|
|
var element = document.createElement("div");
|
|
element.className = "console-group";
|
|
element.group = this;
|
|
this.element = element;
|
|
|
|
var messagesElement = document.createElement("div");
|
|
messagesElement.className = "console-group-messages";
|
|
element.appendChild(messagesElement);
|
|
this.messagesElement = messagesElement;
|
|
}
|
|
|
|
WebInspector.ConsoleGroup.prototype = {
|
|
addMessage: function(msg)
|
|
{
|
|
var element = msg.toMessageElement();
|
|
|
|
if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
|
|
this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
|
|
element.addEventListener("click", this._titleClicked.bind(this), false);
|
|
var groupElement = element.enclosingNodeOrSelfWithClass("console-group");
|
|
if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
|
|
groupElement.addStyleClass("collapsed");
|
|
} else
|
|
this.messagesElement.appendChild(element);
|
|
|
|
if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
|
|
element.previousSibling.addStyleClass("console-adjacent-user-command-result");
|
|
},
|
|
|
|
_titleClicked: function(event)
|
|
{
|
|
var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
|
|
if (groupTitleElement) {
|
|
var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
|
|
if (groupElement)
|
|
if (groupElement.hasStyleClass("collapsed"))
|
|
groupElement.removeStyleClass("collapsed");
|
|
else
|
|
groupElement.addStyleClass("collapsed");
|
|
groupTitleElement.scrollIntoViewIfNeeded(true);
|
|
}
|
|
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|