diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js new file mode 100644 index 000000000..7da0685e7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/ScopeChainDetailsSidebarPanel.js @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2013, 2015 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.ScopeChainDetailsSidebarPanel = class ScopeChainDetailsSidebarPanel extends WebInspector.DetailsSidebarPanel +{ + constructor() + { + super("scope-chain", WebInspector.UIString("Scope Chain"), WebInspector.UIString("Scope Chain")); + + this._callFrame = null; + + this._watchExpressionsSetting = new WebInspector.Setting("watch-expressions", []); + this._watchExpressionsSetting.addEventListener(WebInspector.Setting.Event.Changed, this._updateWatchExpressionsNavigationBar, this); + + this._watchExpressionOptionsElement = document.createElement("div"); + this._watchExpressionOptionsElement.classList.add("options"); + + this._navigationBar = new WebInspector.NavigationBar; + this._watchExpressionOptionsElement.appendChild(this._navigationBar.element); + + let addWatchExpressionButton = new WebInspector.ButtonNavigationItem("add-watch-expression", WebInspector.UIString("Add watch expression"), "Images/Plus13.svg", 13, 13); + addWatchExpressionButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._addWatchExpressionButtonClicked, this); + this._navigationBar.addNavigationItem(addWatchExpressionButton); + + this._clearAllWatchExpressionButton = new WebInspector.ButtonNavigationItem("clear-watch-expressions", WebInspector.UIString("Clear watch expressions"), "Images/NavigationItemTrash.svg", 14, 14); + this._clearAllWatchExpressionButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearAllWatchExpressionsButtonClicked, this); + this._navigationBar.addNavigationItem(this._clearAllWatchExpressionButton); + + this._refreshAllWatchExpressionButton = new WebInspector.ButtonNavigationItem("refresh-watch-expressions", WebInspector.UIString("Refresh watch expressions"), "Images/ReloadFull.svg", 13, 13); + this._refreshAllWatchExpressionButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._refreshAllWatchExpressionsButtonClicked, this); + this._navigationBar.addNavigationItem(this._refreshAllWatchExpressionButton); + + this._watchExpressionsSectionGroup = new WebInspector.DetailsSectionGroup; + this._watchExpressionsSection = new WebInspector.DetailsSection("watch-expressions", WebInspector.UIString("Watch Expressions"), [this._watchExpressionsSectionGroup], this._watchExpressionOptionsElement); + this.contentView.element.appendChild(this._watchExpressionsSection.element); + + this._updateWatchExpressionsNavigationBar(); + + this.needsLayout(); + + // Update on console prompt eval as objects in the scope chain may have changed. + WebInspector.runtimeManager.addEventListener(WebInspector.RuntimeManager.Event.DidEvaluate, this._didEvaluateExpression, this); + + // Update watch expressions when console execution context changes. + WebInspector.runtimeManager.addEventListener(WebInspector.RuntimeManager.Event.ActiveExecutionContextChanged, this._activeExecutionContextChanged, this); + + // Update watch expressions on navigations. + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); + + // Update watch expressions on active call frame changes. + WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this); + } + + // Public + + inspect(objects) + { + // Convert to a single item array if needed. + if (!(objects instanceof Array)) + objects = [objects]; + + var callFrameToInspect = null; + + // Iterate over the objects to find a WebInspector.CallFrame to inspect. + for (var i = 0; i < objects.length; ++i) { + if (!(objects[i] instanceof WebInspector.CallFrame)) + continue; + callFrameToInspect = objects[i]; + break; + } + + this.callFrame = callFrameToInspect; + + return true; + } + + get callFrame() + { + return this._callFrame; + } + + set callFrame(callFrame) + { + if (callFrame === this._callFrame) + return; + + this._callFrame = callFrame; + + this.needsLayout(); + } + + // Protected + + layout() + { + let callFrame = this._callFrame; + + Promise.all([this._generateWatchExpressionsSection(), this._generateCallFramesSection()]).then(function(sections) { + let [watchExpressionsSection, callFrameSections] = sections; + + function delayedWork() + { + // Clear the timeout so we don't update the interface twice. + clearTimeout(timeout); + + if (watchExpressionsSection) + this._watchExpressionsSectionGroup.rows = [watchExpressionsSection]; + else { + let emptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Watch Expressions")); + this._watchExpressionsSectionGroup.rows = [emptyRow]; + emptyRow.showEmptyMessage(); + } + + this.contentView.element.removeChildren(); + this.contentView.element.appendChild(this._watchExpressionsSection.element); + + // Bail if the call frame changed while we were waiting for the async response. + if (this._callFrame !== callFrame) + return; + + if (!callFrameSections) + return; + + for (let callFrameSection of callFrameSections) + this.contentView.element.appendChild(callFrameSection.element); + } + + // We need a timeout in place in case there are long running, pending backend dispatches. This can happen + // if the debugger is paused in code that was executed from the console. The console will be waiting for + // the result of the execution and without a timeout we would never update the scope variables. + let delay = WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties.size === 0 ? 50 : 250; + let timeout = setTimeout(delayedWork.bind(this), delay); + + // Since ObjectTreeView populates asynchronously, we want to wait to replace the existing content + // until after all the pending asynchronous requests are completed. This prevents severe flashing while stepping. + InspectorBackend.runAfterPendingDispatches(delayedWork.bind(this)); + }.bind(this)).catch(function(e) { console.error(e); }); + } + + _generateCallFramesSection() + { + let callFrame = this._callFrame; + if (!callFrame) + return Promise.resolve(null); + + let detailsSections = []; + let foundLocalScope = false; + + let sectionCountByType = new Map; + for (let type in WebInspector.ScopeChainNode.Type) + sectionCountByType.set(WebInspector.ScopeChainNode.Type[type], 0); + + let scopeChain = callFrame.mergedScopeChain(); + for (let scope of scopeChain) { + // Don't show sections for empty scopes unless it is the local scope, since it has "this". + if (scope.empty && scope.type !== WebInspector.ScopeChainNode.Type.Local) + continue; + + let title = null; + let extraPropertyDescriptor = null; + let collapsedByDefault = false; + + let count = sectionCountByType.get(scope.type); + sectionCountByType.set(scope.type, ++count); + + switch (scope.type) { + case WebInspector.ScopeChainNode.Type.Local: + foundLocalScope = true; + collapsedByDefault = false; + title = WebInspector.UIString("Local Variables"); + if (callFrame.thisObject) + extraPropertyDescriptor = new WebInspector.PropertyDescriptor({name: "this", value: callFrame.thisObject}); + break; + + case WebInspector.ScopeChainNode.Type.Closure: + if (scope.__baseClosureScope && scope.name) + title = WebInspector.UIString("Closure Variables (%s)").format(scope.name); + else + title = WebInspector.UIString("Closure Variables"); + collapsedByDefault = false; + break; + + case WebInspector.ScopeChainNode.Type.Block: + title = WebInspector.UIString("Block Variables"); + collapsedByDefault = false; + break; + + case WebInspector.ScopeChainNode.Type.Catch: + title = WebInspector.UIString("Catch Variables"); + collapsedByDefault = false; + break; + + case WebInspector.ScopeChainNode.Type.FunctionName: + title = WebInspector.UIString("Function Name Variable"); + collapsedByDefault = true; + break; + + case WebInspector.ScopeChainNode.Type.With: + title = WebInspector.UIString("With Object Properties"); + collapsedByDefault = foundLocalScope; + break; + + case WebInspector.ScopeChainNode.Type.Global: + title = WebInspector.UIString("Global Variables"); + collapsedByDefault = true; + break; + + case WebInspector.ScopeChainNode.Type.GlobalLexicalEnvironment: + title = WebInspector.UIString("Global Lexical Environment"); + collapsedByDefault = true; + break; + } + + let detailsSectionIdentifier = scope.type + "-" + sectionCountByType.get(scope.type); + let detailsSection = new WebInspector.DetailsSection(detailsSectionIdentifier, title, null, null, collapsedByDefault); + + // FIXME: This just puts two ObjectTreeViews next to each other, but that means + // that properties are not nicely sorted between the two separate lists. + + let rows = []; + for (let object of scope.objects) { + let scopePropertyPath = WebInspector.PropertyPath.emptyPropertyPathForScope(object); + let objectTree = new WebInspector.ObjectTreeView(object, WebInspector.ObjectTreeView.Mode.Properties, scopePropertyPath); + + objectTree.showOnlyProperties(); + + if (extraPropertyDescriptor) { + objectTree.appendExtraPropertyDescriptor(extraPropertyDescriptor); + extraPropertyDescriptor = null; + } + + let treeOutline = objectTree.treeOutline; + treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementAdded, this._treeElementAdded.bind(this, detailsSectionIdentifier), this); + treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange.bind(this, detailsSectionIdentifier), this); + + rows.push(new WebInspector.ObjectPropertiesDetailSectionRow(objectTree, detailsSection)); + } + + detailsSection.groups[0].rows = rows; + detailsSections.push(detailsSection); + } + + return Promise.resolve(detailsSections); + } + + _generateWatchExpressionsSection() + { + let watchExpressions = this._watchExpressionsSetting.value; + if (!watchExpressions.length) { + if (this._usedWatchExpressionsObjectGroup) { + this._usedWatchExpressionsObjectGroup = false; + for (let target of WebInspector.targets) + target.RuntimeAgent.releaseObjectGroup(WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName); + } + return Promise.resolve(null); + } + + for (let target of WebInspector.targets) + target.RuntimeAgent.releaseObjectGroup(WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName); + this._usedWatchExpressionsObjectGroup = true; + + let watchExpressionsRemoteObject = WebInspector.RemoteObject.createFakeRemoteObject(); + let fakePropertyPath = WebInspector.PropertyPath.emptyPropertyPathForScope(watchExpressionsRemoteObject); + let objectTree = new WebInspector.ObjectTreeView(watchExpressionsRemoteObject, WebInspector.ObjectTreeView.Mode.Properties, fakePropertyPath); + objectTree.showOnlyProperties(); + + let treeOutline = objectTree.treeOutline; + const watchExpressionSectionIdentifier = "watch-expressions"; + treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementAdded, this._treeElementAdded.bind(this, watchExpressionSectionIdentifier), this); + treeOutline.addEventListener(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange.bind(this, watchExpressionSectionIdentifier), this); + treeOutline.objectTreeElementAddContextMenuItems = this._objectTreeElementAddContextMenuItems.bind(this); + + let promises = []; + for (let expression of watchExpressions) { + promises.push(new Promise(function(resolve, reject) { + let options = {objectGroup: WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName, includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: false, generatePreview: true, saveResult: false}; + WebInspector.runtimeManager.evaluateInInspectedWindow(expression, options, function(object, wasThrown) { + if (!object) + return; + let propertyDescriptor = new WebInspector.PropertyDescriptor({name: expression, value: object}, undefined, undefined, wasThrown); + objectTree.appendExtraPropertyDescriptor(propertyDescriptor); + resolve(propertyDescriptor); + }); + })); + } + + return Promise.all(promises).then(function() { + return Promise.resolve(new WebInspector.ObjectPropertiesDetailSectionRow(objectTree)); + }); + } + + _addWatchExpression(expression) + { + let watchExpressions = this._watchExpressionsSetting.value.slice(0); + watchExpressions.push(expression); + this._watchExpressionsSetting.value = watchExpressions; + + this.needsLayout(); + } + + _removeWatchExpression(expression) + { + let watchExpressions = this._watchExpressionsSetting.value.slice(0); + watchExpressions.remove(expression, true); + this._watchExpressionsSetting.value = watchExpressions; + + this.needsLayout(); + } + + _clearAllWatchExpressions() + { + this._watchExpressionsSetting.value = []; + + this.needsLayout(); + } + + _addWatchExpressionButtonClicked(event) + { + function presentPopoverOverTargetElement() + { + let target = WebInspector.Rect.rectFromClientRect(event.target.element.getBoundingClientRect()); + popover.present(target, [WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_X]); + } + + let popover = new WebInspector.Popover(this); + let content = document.createElement("div"); + content.classList.add("watch-expression"); + content.appendChild(document.createElement("div")).textContent = WebInspector.UIString("Add New Watch Expression"); + + let editorElement = content.appendChild(document.createElement("div")); + editorElement.classList.add("watch-expression-editor", WebInspector.SyntaxHighlightedStyleClassName); + + this._codeMirror = WebInspector.CodeMirrorEditor.create(editorElement, { + lineWrapping: true, + mode: "text/javascript", + indentWithTabs: true, + indentUnit: 4, + matchBrackets: true, + value: "", + }); + + this._popoverCommitted = false; + + this._codeMirror.addKeyMap({ + "Enter": () => { this._popoverCommitted = true; popover.dismiss(); }, + }); + + let completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror); + completionController.addExtendedCompletionProvider("javascript", WebInspector.javaScriptRuntimeCompletionProvider); + + // Resize the popover as best we can when the CodeMirror editor changes size. + let previousHeight = 0; + this._codeMirror.on("changes", function(cm, event) { + let height = cm.getScrollInfo().height; + if (previousHeight !== height) { + previousHeight = height; + popover.update(false); + } + }); + + popover.content = content; + + popover.windowResizeHandler = presentPopoverOverTargetElement; + presentPopoverOverTargetElement(); + + // CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear. + setTimeout(() => { + this._codeMirror.refresh(); + this._codeMirror.focus(); + popover.update(); + }, 0); + } + + willDismissPopover(popover) + { + if (this._popoverCommitted) { + let expression = this._codeMirror.getValue().trim(); + if (expression) + this._addWatchExpression(expression); + } + + this._codeMirror = null; + } + + _refreshAllWatchExpressionsButtonClicked(event) + { + this.needsLayout(); + } + + _clearAllWatchExpressionsButtonClicked(event) + { + this._clearAllWatchExpressions(); + } + + _didEvaluateExpression(event) + { + if (event.data.objectGroup === WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName) + return; + + this.needsLayout(); + } + + _activeExecutionContextChanged() + { + this.needsLayout(); + } + + _activeCallFrameDidChange() + { + this.needsLayout(); + } + + _mainResourceDidChange(event) + { + if (!event.target.isMainFrame()) + return; + + this.needsLayout(); + } + + _objectTreeElementAddContextMenuItems(objectTreeElement, contextMenu) + { + // Only add our watch expression context menus to the top level ObjectTree elements. + if (objectTreeElement.parent !== objectTreeElement.treeOutline) + return; + + contextMenu.appendItem(WebInspector.UIString("Remove Watch Expression"), () => { + let expression = objectTreeElement.property.name; + this._removeWatchExpression(expression); + }); + } + + _propertyPathIdentifierForTreeElement(identifier, objectPropertyTreeElement) + { + if (!objectPropertyTreeElement.property) + return null; + + let propertyPath = objectPropertyTreeElement.thisPropertyPath(); + if (propertyPath.isFullPathImpossible()) + return null; + + return identifier + "-" + propertyPath.fullPath; + } + + _treeElementAdded(identifier, event) + { + let treeElement = event.data.element; + let propertyPathIdentifier = this._propertyPathIdentifierForTreeElement(identifier, treeElement); + if (!propertyPathIdentifier) + return; + + if (WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties.has(propertyPathIdentifier)) + treeElement.expand(); + } + + _treeElementDisclosureDidChange(identifier, event) + { + let treeElement = event.data.element; + let propertyPathIdentifier = this._propertyPathIdentifierForTreeElement(identifier, treeElement); + if (!propertyPathIdentifier) + return; + + if (treeElement.expanded) + WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties.add(propertyPathIdentifier); + else + WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties.delete(propertyPathIdentifier); + } + + _updateWatchExpressionsNavigationBar() + { + let enabled = this._watchExpressionsSetting.value.length; + this._refreshAllWatchExpressionButton.enabled = enabled; + this._clearAllWatchExpressionButton.enabled = enabled; + } +}; + +WebInspector.ScopeChainDetailsSidebarPanel._autoExpandProperties = new Set; +WebInspector.ScopeChainDetailsSidebarPanel.WatchExpressionsObjectGroupName = "watch-expressions"; |