diff options
author | Allan Sandfeld Jensen <allan.jensen@digia.com> | 2013-09-13 12:51:20 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-09-19 20:50:05 +0200 |
commit | d441d6f39bb846989d95bcf5caf387b42414718d (patch) | |
tree | e367e64a75991c554930278175d403c072de6bb8 /Source/WebInspectorUI/UserInterface/JavaScriptLogViewController.js | |
parent | 0060b2994c07842f4c59de64b5e3e430525c4b90 (diff) | |
download | qtwebkit-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.js | 515 |
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; |