summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@digia.com>2013-09-13 12:51:20 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-19 20:50:05 +0200
commitd441d6f39bb846989d95bcf5caf387b42414718d (patch)
treee367e64a75991c554930278175d403c072de6bb8 /Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js
parent0060b2994c07842f4c59de64b5e3e430525c4b90 (diff)
downloadqtwebkit-d441d6f39bb846989d95bcf5caf387b42414718d.tar.gz
Import Qt5x2 branch of QtWebkit for Qt 5.2
Importing a new snapshot of webkit. Change-Id: I2d01ad12cdc8af8cb015387641120a9d7ea5f10c Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js515
1 files changed, 515 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js b/Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js
new file mode 100644
index 000000000..edc4cf2dc
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.JavaScriptLogViewController = function(element, scrollElement, textPrompt, delegate, historySettingIdentifier)
+{
+ WebInspector.Object.call(this);
+
+ console.assert(textPrompt instanceof WebInspector.ConsolePrompt);
+ console.assert(historySettingIdentifier);
+
+ this._element = element;
+ this._scrollElement = scrollElement;
+
+ this._promptHistorySetting = new WebInspector.Setting(historySettingIdentifier, null);
+
+ this._prompt = textPrompt;
+ this._prompt.delegate = this;
+ this._prompt.history = this._promptHistorySetting.value;
+
+ this.delegate = delegate;
+
+ this._cleared = true;
+ this._previousMessage = null;
+ this._repeatCountWasInterrupted = false;
+
+ this._topConsoleGroups = [];
+
+ this.messagesClearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "K", this._handleClearShortcut.bind(this));
+ this.messagesAlternateClearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "L", this._handleClearShortcut.bind(this), this._element);
+
+ this._messagesFindKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "F", this._handleFindShortcut.bind(this), this._element);
+ this._messagesFindNextKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "G", this._handleFindNextShortcut.bind(this), this._element);
+ this._messagesFindPreviousKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command | WebInspector.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._element);
+
+ this._promptAlternateClearKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "L", this._handleClearShortcut.bind(this), this._prompt.element);
+ this._promptFindKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "F", this._handleFindShortcut.bind(this), this._prompt.element);
+ this._promptFindNextKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "G", this._handleFindNextShortcut.bind(this), this._prompt.element);
+ this._promptFindPreviousKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command | WebInspector.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._prompt.element);
+
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._clearLastProperties, this);
+
+ this.startNewSession();
+};
+
+WebInspector.JavaScriptLogViewController.CachedPropertiesDuration = 30000;
+
+WebInspector.JavaScriptLogViewController.prototype = {
+ constructor: WebInspector.JavaScriptLogViewController,
+
+ // Public
+
+ get prompt()
+ {
+ return this._prompt;
+ },
+
+ get topConsoleGroup()
+ {
+ return this._topConsoleGroup;
+ },
+
+ get currentConsoleGroup()
+ {
+ return this._currentConsoleGroup;
+ },
+
+ clear: function()
+ {
+ this._cleared = true;
+
+ this.startNewSession(true);
+
+ this.prompt.focus();
+
+ if (this.delegate && typeof this.delegate.didClearMessages === "function")
+ this.delegate.didClearMessages();
+ },
+
+ startNewSession: function(clearPreviousSessions)
+ {
+ if (this._topConsoleGroups.length && clearPreviousSessions) {
+ for (var i = 0; i < this._topConsoleGroups.length; ++i)
+ this._element.removeChild(this._topConsoleGroups[i].element);
+
+ this._topConsoleGroups = [];
+ this._topConsoleGroup = null;
+ this._currentConsoleGroup = null;
+ }
+
+ // Reuse the top group if it has no messages.
+ if (this._topConsoleGroup && !this._topConsoleGroup.hasMessages()) {
+ // Make sure the session is visible.
+ this._topConsoleGroup.element.scrollIntoView();
+ return;
+ }
+
+ var hasPreviousSession = !!this._topConsoleGroup;
+ var consoleGroup = new WebInspector.ConsoleGroup(null, hasPreviousSession);
+
+ this._previousMessage = null;
+ this._repeatCountWasInterrupted = false;
+
+ this._topConsoleGroups.push(consoleGroup);
+ this._topConsoleGroup = consoleGroup;
+ this._currentConsoleGroup = consoleGroup;
+
+ this._element.appendChild(consoleGroup.element);
+
+ // Make sure the new session is visible.
+ consoleGroup.element.scrollIntoView();
+ },
+
+ appendConsoleMessage: function(consoleMessage)
+ {
+ // Clone the message since there might be multiple clients using the message,
+ // and since the message has a DOM element it can't be two places at once.
+ var messageClone = consoleMessage.clone();
+
+ this._appendConsoleMessage(messageClone);
+
+ return messageClone;
+ },
+
+ updatePreviousMessageRepeatCount: function(count)
+ {
+ console.assert(this._previousMessage);
+ if (!this._previousMessage)
+ return;
+
+ if (!this._repeatCountWasInterrupted) {
+ this._previousMessage.repeatCount = count - (this._previousMessage.ignoredCount || 0);
+ this._previousMessage.updateRepeatCount();
+ } else {
+ var newMessage = this._previousMessage.clone();
+
+ // If this message is repeated after being cloned, messageRepeatCountUpdated will be called with a
+ // count that includes the count of this message before cloning. We should ignore instances of this
+ // log that occurred before we cloned, so set a property on the message with the number of previous
+ // instances of this log that we should ignore.
+ newMessage.ignoredCount = newMessage.repeatCount + (this._previousMessage.ignoredCount || 0);
+ newMessage.repeatCount = 1;
+
+ this._appendConsoleMessage(newMessage);
+ }
+ },
+
+ isScrolledToBottom: function()
+ {
+ // Lie about being scrolled to the bottom if we have a pending request to scroll to the bottom soon.
+ return this._scrollToBottomTimeout || this._scrollElement.isScrolledToBottom();
+ },
+
+ scrollToBottom: function()
+ {
+ if (this._scrollToBottomTimeout)
+ return;
+
+ function delayedWork()
+ {
+ this._scrollToBottomTimeout = null;
+ this._scrollElement.scrollTop = this._scrollElement.scrollHeight;
+ }
+
+ // Don't scroll immediately so we are not causing excessive layouts when there
+ // are many messages being added at once.
+ this._scrollToBottomTimeout = setTimeout(delayedWork.bind(this), 0);
+ },
+
+ // Protected
+
+ consolePromptHistoryDidChange: function(prompt)
+ {
+ this._promptHistorySetting.value = this.prompt.history;
+ },
+
+ consolePromptShouldCommitText: function(prompt, text, cursorIsAtLastPosition, handler)
+ {
+ // Always commit the text if we are not at the last position.
+ if (!cursorIsAtLastPosition) {
+ handler(true);
+ return;
+ }
+
+ // COMPATIBILITY (iOS 6): RuntimeAgent.parse did not exist in iOS 6. Always commit.
+ if (!RuntimeAgent.parse) {
+ handler(true);
+ return;
+ }
+
+ function parseFinished(error, result, message, range)
+ {
+ handler(result !== RuntimeAgent.SyntaxErrorType.Recoverable);
+ }
+
+ RuntimeAgent.parse(text, parseFinished.bind(this));
+ },
+
+ consolePromptTextCommitted: function(prompt, text)
+ {
+ console.assert(text);
+
+ var commandMessage = new WebInspector.ConsoleCommand(text);
+ this._appendConsoleMessage(commandMessage, true);
+
+ function printResult(result, wasThrown)
+ {
+ if (!result || this._cleared)
+ return;
+
+ this._appendConsoleMessage(new WebInspector.ConsoleCommandResult(result, wasThrown, commandMessage), true);
+ }
+
+ this._evaluateInInspectedWindow(text, "console", true, true, false, printResult.bind(this));
+ },
+
+ consolePromptCompletionsNeeded: function(prompt, defaultCompletions, base, prefix, suffix, forced)
+ {
+ // Don't allow non-forced empty prefix completions unless the base is that start of property access.
+ if (!forced && !prefix && !/[.[]$/.test(base)) {
+ prompt.updateCompletions(null);
+ return;
+ }
+
+ // If the base ends with an open parentheses or open curly bracket then treat it like there is
+ // no base so we get global object completions.
+ if (/[({]$/.test(base))
+ base = "";
+
+ var lastBaseIndex = base.length - 1;
+ var dotNotation = base[lastBaseIndex] === ".";
+ var bracketNotation = base[lastBaseIndex] === "[";
+
+ if (dotNotation || bracketNotation) {
+ base = base.substring(0, lastBaseIndex);
+
+ // Don't suggest anything for an empty base that is using dot notation.
+ // Bracket notation with an empty base will be treated as an array.
+ if (!base && dotNotation) {
+ prompt.updateCompletions(defaultCompletions);
+ return;
+ }
+
+ // Don't allow non-forced empty prefix completions if the user is entering a number, since it might be a float.
+ // But allow number completions if the base already has a decimal, so "10.0." will suggest Number properties.
+ if (!forced && !prefix && dotNotation && base.indexOf(".") === -1 && parseInt(base, 10) == base) {
+ prompt.updateCompletions(null);
+ return;
+ }
+
+ // An empty base with bracket notation is not property access, it is an array.
+ // Clear the bracketNotation flag so completions are not quoted.
+ if (!base && bracketNotation)
+ bracketNotation = false;
+ }
+
+ // If the base is the same as the last time, we can reuse the property names we have already gathered.
+ // Doing this eliminates delay caused by the async nature of the code below and it only calls getters
+ // and functions once instead of repetitively. Sure, there can be difference each time the base is evaluated,
+ // but this optimization gives us more of a win. We clear the cache after 30 seconds or when stepping in the
+ // debugger to make sure we don't use stale properties in most cases.
+ if (this._lastBase === base && this._lastPropertyNames) {
+ receivedPropertyNames.call(this, this._lastPropertyNames);
+ return;
+ }
+
+ this._lastBase = base;
+ this._lastPropertyNames = null;
+
+ var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+ if (!base && activeCallFrame)
+ activeCallFrame.collectScopeChainVariableNames(receivedPropertyNames.bind(this));
+ else
+ this._evaluateInInspectedWindow(base, "completion", true, true, false, evaluated.bind(this));
+
+ function updateLastPropertyNames(propertyNames)
+ {
+ if (this._clearLastPropertiesTimeout)
+ clearTimeout(this._clearLastPropertiesTimeout);
+ this._clearLastPropertiesTimeout = setTimeout(this._clearLastProperties.bind(this), WebInspector.JavaScriptLogViewController.CachedPropertiesDuration);
+
+ this._lastPropertyNames = propertyNames || {};
+ }
+
+ function evaluated(result, wasThrown)
+ {
+ if (wasThrown || !result || result.type === "undefined" || (result.type === "object" && result.subtype === "null")) {
+ RuntimeAgent.releaseObjectGroup("completion");
+
+ updateLastPropertyNames.call(this, {});
+ prompt.updateCompletions(defaultCompletions);
+
+ return;
+ }
+
+ function getCompletions(primitiveType)
+ {
+ var object;
+ if (primitiveType === "string")
+ object = new String("");
+ else if (primitiveType === "number")
+ object = new Number(0);
+ else if (primitiveType === "boolean")
+ object = new Boolean(false);
+ else
+ object = this;
+
+ var resultSet = {};
+ for (var o = object; o; o = o.__proto__) {
+ try {
+ var names = Object.getOwnPropertyNames(o);
+ for (var i = 0; i < names.length; ++i)
+ resultSet[names[i]] = true;
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ return resultSet;
+ }
+
+ if (result.type === "object" || result.type === "function")
+ result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
+ else if (result.type === "string" || result.type === "number" || result.type === "boolean")
+ this._evaluateInInspectedWindow("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, receivedPropertyNamesFromEvaluate.bind(this));
+ else
+ console.error("Unknown result type: " + result.type);
+ }
+
+ function receivedPropertyNamesFromEvaluate(object, wasThrown, result)
+ {
+ receivedPropertyNames.call(this, result && !wasThrown ? result.value : null);
+ }
+
+ function receivedPropertyNames(propertyNames)
+ {
+ propertyNames = propertyNames || {};
+
+ updateLastPropertyNames.call(this, propertyNames);
+
+ RuntimeAgent.releaseObjectGroup("completion");
+
+ if (!base) {
+ const commandLineAPI = ["$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", "$0", "$1", "$2", "$3", "$4", "$_"];
+ for (var i = 0; i < commandLineAPI.length; ++i)
+ propertyNames[commandLineAPI[i]] = true;
+ }
+
+ propertyNames = Object.keys(propertyNames);
+
+ var implicitSuffix = "";
+ if (bracketNotation) {
+ var quoteUsed = prefix[0] === "'" ? "'" : "\"";
+ if (suffix !== "]" && suffix !== quoteUsed)
+ implicitSuffix = "]";
+ }
+
+ var completions = defaultCompletions;
+ var knownCompletions = completions.keySet();
+
+ for (var i = 0; i < propertyNames.length; ++i) {
+ var property = propertyNames[i];
+
+ if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
+ continue;
+
+ if (bracketNotation) {
+ if (parseInt(property) != property)
+ property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : "");
+ }
+
+ if (!property.startsWith(prefix) || property in knownCompletions)
+ continue;
+
+ completions.push(property);
+ knownCompletions[property] = true;
+ }
+
+ function compare(a, b)
+ {
+ // Try to sort in numerical order first.
+ var numericCompareResult = a - b;
+ if (!isNaN(numericCompareResult))
+ return numericCompareResult;
+
+ // Not numbers, sort as strings.
+ return a.localeCompare(b);
+ }
+
+ completions.sort(compare);
+
+ prompt.updateCompletions(completions, implicitSuffix);
+ }
+ },
+
+ // Private
+
+ _clearLastProperties: function()
+ {
+ if (this._clearLastPropertiesTimeout) {
+ clearTimeout(this._clearLastPropertiesTimeout);
+ delete this._clearLastPropertiesTimeout;
+ }
+
+ // Clear the cache of property names so any changes while stepping or sitting idle get picked up if the same
+ // expression is evaluated again.
+ this._lastPropertyNames = null;
+ },
+
+ _evaluateInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, callback)
+ {
+ if (!expression) {
+ // There is no expression, so the completion should happen against global properties.
+ expression = "this";
+ }
+
+ function evalCallback(error, result, wasThrown)
+ {
+ if (error) {
+ console.error(error);
+ callback(null, false);
+ return;
+ }
+
+ if (returnByValue)
+ callback(null, wasThrown, wasThrown ? null : result);
+ else
+ callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
+ }
+
+ if (WebInspector.debuggerManager.activeCallFrame) {
+ DebuggerAgent.evaluateOnCallFrame(WebInspector.debuggerManager.activeCallFrame.id, expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, evalCallback);
+ return;
+ }
+
+ // COMPATIBILITY (iOS 6): Execution context identifiers (contextId) did not exist
+ // in iOS 6. Fallback to including the frame identifier (frameId).
+ var contextId = WebInspector.quickConsole.executionContextIdentifier;
+ RuntimeAgent.evaluate.invoke({expression: expression, objectGroup: objectGroup, includeCommandLineAPI: includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole: doNotPauseOnExceptionsAndMuteConsole, contextId: contextId, frameId: contextId, returnByValue: returnByValue}, evalCallback);
+ },
+
+ _handleClearShortcut: function()
+ {
+ this.clear();
+ },
+
+ _handleFindShortcut: function()
+ {
+ this.delegate.focusSearchBar();
+ },
+
+ _handleFindNextShortcut: function()
+ {
+ this.delegate.highlightNextSearchMatch();
+ },
+
+ _handleFindPreviousShortcut: function()
+ {
+ this.delegate.highlightPreviousSearchMatch();
+ },
+
+ _appendConsoleMessage: function(msg, repeatCountWasInterrupted)
+ {
+ var wasScrolledToBottom = this.isScrolledToBottom();
+
+ this._cleared = false;
+ this._repeatCountWasInterrupted = repeatCountWasInterrupted || false;
+
+ if (!repeatCountWasInterrupted)
+ this._previousMessage = msg;
+
+ if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
+ var parentGroup = this._currentConsoleGroup.parentGroup;
+ if (parentGroup)
+ this._currentConsoleGroup = parentGroup;
+ } else {
+ if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
+ var group = new WebInspector.ConsoleGroup(this._currentConsoleGroup);
+ this._currentConsoleGroup.messagesElement.appendChild(group.element);
+ this._currentConsoleGroup = group;
+ }
+
+ this._currentConsoleGroup.addMessage(msg);
+ }
+
+ if (msg.type === WebInspector.ConsoleMessage.MessageType.Result || wasScrolledToBottom)
+ this.scrollToBottom();
+
+ if (this.delegate && typeof this.delegate.didAppendConsoleMessage === "function")
+ this.delegate.didAppendConsoleMessage(msg);
+ }
+};
+
+WebInspector.JavaScriptLogViewController.prototype.__proto__ = WebInspector.Object.prototype;