Web Inspector: Create a UI for displaying JavaScript type information
authorsaambarati1@gmail.com <saambarati1@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 Sep 2014 18:25:32 +0000 (18:25 +0000)
committersaambarati1@gmail.com <saambarati1@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 2 Sep 2014 18:25:32 +0000 (18:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=135142

Reviewed by Joseph Pecoraro.

JavaScriptCore now has a type profiler. This patch provides a frontend
user interface for displaying this type information in the Web Inspector.
The user interface works by placing inline "type tokens" next to important
JavaScript expressions: function arguments, function return types, and
variable declarations. Type information can also be seen for almost every
JavaScipt expression just by hovering over it while viewing a JavaScript file.

Currently, turning on the type profiler is expensive, so the inspector
ensures to never turn on type profiling until the user specifically asks
for type information to be turned on.

* UserInterface/Base/Main.js:
(WebInspector.loaded):
* UserInterface/Controllers/CodeMirrorTokenTrackingController.js:
(WebInspector.CodeMirrorTokenTrackingController.prototype._startTracking):
(WebInspector.CodeMirrorTokenTrackingController.prototype._processNewHoveredToken):
Added a new type profiling mode.

* UserInterface/Controllers/FormatterSourceMap.js:
(WebInspector.FormatterSourceMap.prototype.originalToFormatted):
(WebInspector.FormatterSourceMap.prototype.originalPositionToFormatted):
(WebInspector.FormatterSourceMap.prototype.formattedToOriginal):
(WebInspector.FormatterSourceMap.prototype.formattedToOriginalOffset):
* UserInterface/Controllers/TypeTokenAnnotator.js: Added.
(WebInspector.TypeTokenAnnotator):
(WebInspector.TypeTokenAnnotator.prototype.get isActive):
(WebInspector.TypeTokenAnnotator.prototype.get sourceCodeTextEditor):
(WebInspector.TypeTokenAnnotator.prototype.pause):
(WebInspector.TypeTokenAnnotator.prototype.resume):
(WebInspector.TypeTokenAnnotator.prototype.refresh):
(WebInspector.TypeTokenAnnotator.prototype.reset):
(WebInspector.TypeTokenAnnotator.prototype.toggleTypeAnnotations):
(WebInspector.TypeTokenAnnotator.prototype._insertAnnotations.):
(WebInspector.TypeTokenAnnotator.prototype._insertTypeTokensForEachNode):
(WebInspector.TypeTokenAnnotator.prototype._insertToken):
(WebInspector.TypeTokenAnnotator.prototype.isLineTerminator):
(WebInspector.TypeTokenAnnotator.prototype._translateToOffsetAfterFunctionParameterList):
(WebInspector.TypeTokenAnnotator.prototype._clearTimeoutIfNeeded):
This class is responsible for producing the inline "type token" annotations.

* UserInterface/Images/NavigationItemTypes.svg: Added.
* UserInterface/Main.html:
* UserInterface/Models/Script.js:
(WebInspector.Script.prototype.get scriptSyntaxTree):
* UserInterface/Models/ScriptSyntaxTree.js:
(WebInspector.ScriptSyntaxTree.prototype.containsNonEmptyReturnStatement):
(WebInspector.ScriptSyntaxTree.prototype.):
(WebInspector.ScriptSyntaxTree.prototype.updateTypes):
(WebInspector.ScriptSyntaxTree.prototype._createInternalSyntaxTree):
* UserInterface/Views/ScriptContentView.js:
(WebInspector.ScriptContentView):
(WebInspector.ScriptContentView.prototype.get navigationItems):
(WebInspector.ScriptContentView.prototype._contentDidPopulate):
(WebInspector.ScriptContentView.prototype._toggleTypeAnnotations):
(WebInspector.ScriptContentView.prototype._showJavaScriptTypeInformationSettingChanged):
* UserInterface/Views/SourceCodeTextEditor.js:
(WebInspector.SourceCodeTextEditor):
(WebInspector.SourceCodeTextEditor.prototype.shown):
(WebInspector.SourceCodeTextEditor.prototype.hidden):
(WebInspector.SourceCodeTextEditor.prototype.canShowTypeAnnotations):
(WebInspector.SourceCodeTextEditor.prototype.toggleTypeAnnotations):
(WebInspector.SourceCodeTextEditor.prototype.showPopoverForTypes):
(WebInspector.SourceCodeTextEditor.prototype.prettyPrint):
(WebInspector.SourceCodeTextEditor.prototype._populateWithContent):
(WebInspector.SourceCodeTextEditor.prototype._debuggerDidPause):
(WebInspector.SourceCodeTextEditor.prototype._debuggerDidResume):
(WebInspector.SourceCodeTextEditor.prototype._updateTokenTrackingControllerState):
(WebInspector.SourceCodeTextEditor.prototype.tokenTrackingControllerNewHighlightCandidate):
(WebInspector.SourceCodeTextEditor.prototype._tokenTrackingControllerHighlightedJavaScriptTypeInformation.handler):
(WebInspector.SourceCodeTextEditor.prototype._tokenTrackingControllerHighlightedJavaScriptTypeInformation):
(WebInspector.SourceCodeTextEditor.prototype._showPopover):
(WebInspector.SourceCodeTextEditor.prototype.editingControllerDidFinishEditing):
(WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenAnnotator):
(WebInspector.SourceCodeTextEditor.prototype._enableScrollEventsForTypeTokenAnnotator):
(WebInspector.SourceCodeTextEditor.prototype._disableScrollEventsForTypeTokenAnnotator):
(WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenScrollEventHandler.scrollHandler):
(WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenScrollEventHandler):
* UserInterface/Views/TextContentView.js:
(WebInspector.TextContentView.prototype.get navigationItems):
* UserInterface/Views/TextEditor.js:
(WebInspector.TextEditor.prototype.set formatted):
(WebInspector.TextEditor.prototype.canShowTypeAnnotations):
(WebInspector.TextEditor.prototype.visibleRangeOffsets):
(WebInspector.TextEditor.prototype.originalOffsetToCurrentPosition):
(WebInspector.TextEditor.prototype.currentOffsetToCurrentPosition):
(WebInspector.TextEditor.prototype.currentPositionToOriginalOffset):
(WebInspector.TextEditor.prototype.currentPositionToCurrentOffset):
(WebInspector.TextEditor.prototype.setInlineWidget):
(WebInspector.TextEditor.prototype.addScrollHandler):
(WebInspector.TextEditor.prototype.removeScrollHandler):
(WebInspector.TextEditor.prototype._scrollIntoViewCentered):
* UserInterface/Views/TextResourceContentView.js:
(WebInspector.TextResourceContentView):
(WebInspector.TextResourceContentView.prototype.get navigationItems):
(WebInspector.TextResourceContentView.prototype._contentDidPopulate):
(WebInspector.TextResourceContentView.prototype._toggleTypeAnnotations):
(WebInspector.TextResourceContentView.prototype._showJavaScriptTypeInformationSettingChanged):
* UserInterface/Views/TypePropertiesSection.js: Added.
(WebInspector.TypePropertiesSection):
(WebInspector.TypePropertiesSection.prototype.onpopulate):
(WebInspector.TypePropertiesSection.PropertyComparator):
(WebInspector.TypePropertyTreeElement):
(WebInspector.TypePropertyTreeElement.prototype.onpopulate):
* UserInterface/Views/TypeTokenView.css: Added.
(.type-token):
(.type-token-left-spacing):
(.type-token-right-spacing):
(.type-token-function, .type-token-boolean):
(.type-token-number):
(.type-token-string):
(.type-token-default):
(.type-token-empty):
(.type-token-many):
* UserInterface/Views/TypeTokenView.js: Added.
(WebInspector.TypeTokenView):
(WebInspector.TypeTokenView.titleForPopover):
(WebInspector.TypeTokenView.prototype.update):
(WebInspector.TypeTokenView.prototype._setUpMouseoverHandlers):
(WebInspector.TypeTokenView.prototype._shouldShowPopover):
The inline "type token" view.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@173180 268f45cc-cd09-0410-ab3c-d52691b4dbfc

18 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorTokenTrackingController.js
Source/WebInspectorUI/UserInterface/Controllers/FormatterSourceMap.js
Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/NavigationItemTypes.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/Script.js
Source/WebInspectorUI/UserInterface/Models/ScriptSyntaxTree.js
Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
Source/WebInspectorUI/UserInterface/Views/TextContentView.js
Source/WebInspectorUI/UserInterface/Views/TextEditor.js
Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js
Source/WebInspectorUI/UserInterface/Views/TypePropertiesSection.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TypeTokenView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/TypeTokenView.js [new file with mode: 0644]

index 496c8e2..d3a2f94 100644 (file)
@@ -1,3 +1,131 @@
+2014-08-29  Saam Barati  <saambarati1@gmail.com>
+
+        Web Inspector: Create a UI for displaying JavaScript type information
+        https://bugs.webkit.org/show_bug.cgi?id=135142
+
+        Reviewed by Joseph Pecoraro.
+
+        JavaScriptCore now has a type profiler. This patch provides a frontend
+        user interface for displaying this type information in the Web Inspector.
+        The user interface works by placing inline "type tokens" next to important
+        JavaScript expressions: function arguments, function return types, and 
+        variable declarations. Type information can also be seen for almost every
+        JavaScipt expression just by hovering over it while viewing a JavaScript file.
+
+        Currently, turning on the type profiler is expensive, so the inspector
+        ensures to never turn on type profiling until the user specifically asks 
+        for type information to be turned on.
+
+        * UserInterface/Base/Main.js:
+        (WebInspector.loaded):
+        * UserInterface/Controllers/CodeMirrorTokenTrackingController.js:
+        (WebInspector.CodeMirrorTokenTrackingController.prototype._startTracking):
+        (WebInspector.CodeMirrorTokenTrackingController.prototype._processNewHoveredToken):
+        Added a new type profiling mode.
+
+        * UserInterface/Controllers/FormatterSourceMap.js:
+        (WebInspector.FormatterSourceMap.prototype.originalToFormatted):
+        (WebInspector.FormatterSourceMap.prototype.originalPositionToFormatted):
+        (WebInspector.FormatterSourceMap.prototype.formattedToOriginal):
+        (WebInspector.FormatterSourceMap.prototype.formattedToOriginalOffset):
+        * UserInterface/Controllers/TypeTokenAnnotator.js: Added.
+        (WebInspector.TypeTokenAnnotator):
+        (WebInspector.TypeTokenAnnotator.prototype.get isActive):
+        (WebInspector.TypeTokenAnnotator.prototype.get sourceCodeTextEditor):
+        (WebInspector.TypeTokenAnnotator.prototype.pause):
+        (WebInspector.TypeTokenAnnotator.prototype.resume):
+        (WebInspector.TypeTokenAnnotator.prototype.refresh):
+        (WebInspector.TypeTokenAnnotator.prototype.reset):
+        (WebInspector.TypeTokenAnnotator.prototype.toggleTypeAnnotations):
+        (WebInspector.TypeTokenAnnotator.prototype._insertAnnotations.):
+        (WebInspector.TypeTokenAnnotator.prototype._insertTypeTokensForEachNode):
+        (WebInspector.TypeTokenAnnotator.prototype._insertToken):
+        (WebInspector.TypeTokenAnnotator.prototype.isLineTerminator):
+        (WebInspector.TypeTokenAnnotator.prototype._translateToOffsetAfterFunctionParameterList):
+        (WebInspector.TypeTokenAnnotator.prototype._clearTimeoutIfNeeded):
+        This class is responsible for producing the inline "type token" annotations.
+
+        * UserInterface/Images/NavigationItemTypes.svg: Added.
+        * UserInterface/Main.html:
+        * UserInterface/Models/Script.js:
+        (WebInspector.Script.prototype.get scriptSyntaxTree):
+        * UserInterface/Models/ScriptSyntaxTree.js:
+        (WebInspector.ScriptSyntaxTree.prototype.containsNonEmptyReturnStatement):
+        (WebInspector.ScriptSyntaxTree.prototype.):
+        (WebInspector.ScriptSyntaxTree.prototype.updateTypes):
+        (WebInspector.ScriptSyntaxTree.prototype._createInternalSyntaxTree):
+        * UserInterface/Views/ScriptContentView.js:
+        (WebInspector.ScriptContentView):
+        (WebInspector.ScriptContentView.prototype.get navigationItems):
+        (WebInspector.ScriptContentView.prototype._contentDidPopulate):
+        (WebInspector.ScriptContentView.prototype._toggleTypeAnnotations):
+        (WebInspector.ScriptContentView.prototype._showJavaScriptTypeInformationSettingChanged):
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WebInspector.SourceCodeTextEditor):
+        (WebInspector.SourceCodeTextEditor.prototype.shown):
+        (WebInspector.SourceCodeTextEditor.prototype.hidden):
+        (WebInspector.SourceCodeTextEditor.prototype.canShowTypeAnnotations):
+        (WebInspector.SourceCodeTextEditor.prototype.toggleTypeAnnotations):
+        (WebInspector.SourceCodeTextEditor.prototype.showPopoverForTypes):
+        (WebInspector.SourceCodeTextEditor.prototype.prettyPrint):
+        (WebInspector.SourceCodeTextEditor.prototype._populateWithContent):
+        (WebInspector.SourceCodeTextEditor.prototype._debuggerDidPause):
+        (WebInspector.SourceCodeTextEditor.prototype._debuggerDidResume):
+        (WebInspector.SourceCodeTextEditor.prototype._updateTokenTrackingControllerState):
+        (WebInspector.SourceCodeTextEditor.prototype.tokenTrackingControllerNewHighlightCandidate):
+        (WebInspector.SourceCodeTextEditor.prototype._tokenTrackingControllerHighlightedJavaScriptTypeInformation.handler):
+        (WebInspector.SourceCodeTextEditor.prototype._tokenTrackingControllerHighlightedJavaScriptTypeInformation):
+        (WebInspector.SourceCodeTextEditor.prototype._showPopover):
+        (WebInspector.SourceCodeTextEditor.prototype.editingControllerDidFinishEditing):
+        (WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenAnnotator):
+        (WebInspector.SourceCodeTextEditor.prototype._enableScrollEventsForTypeTokenAnnotator):
+        (WebInspector.SourceCodeTextEditor.prototype._disableScrollEventsForTypeTokenAnnotator):
+        (WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenScrollEventHandler.scrollHandler):
+        (WebInspector.SourceCodeTextEditor.prototype._makeTypeTokenScrollEventHandler):
+        * UserInterface/Views/TextContentView.js:
+        (WebInspector.TextContentView.prototype.get navigationItems):
+        * UserInterface/Views/TextEditor.js:
+        (WebInspector.TextEditor.prototype.set formatted):
+        (WebInspector.TextEditor.prototype.canShowTypeAnnotations):
+        (WebInspector.TextEditor.prototype.visibleRangeOffsets):
+        (WebInspector.TextEditor.prototype.originalOffsetToCurrentPosition):
+        (WebInspector.TextEditor.prototype.currentOffsetToCurrentPosition):
+        (WebInspector.TextEditor.prototype.currentPositionToOriginalOffset):
+        (WebInspector.TextEditor.prototype.currentPositionToCurrentOffset):
+        (WebInspector.TextEditor.prototype.setInlineWidget):
+        (WebInspector.TextEditor.prototype.addScrollHandler):
+        (WebInspector.TextEditor.prototype.removeScrollHandler):
+        (WebInspector.TextEditor.prototype._scrollIntoViewCentered):
+        * UserInterface/Views/TextResourceContentView.js:
+        (WebInspector.TextResourceContentView):
+        (WebInspector.TextResourceContentView.prototype.get navigationItems):
+        (WebInspector.TextResourceContentView.prototype._contentDidPopulate):
+        (WebInspector.TextResourceContentView.prototype._toggleTypeAnnotations):
+        (WebInspector.TextResourceContentView.prototype._showJavaScriptTypeInformationSettingChanged):
+        * UserInterface/Views/TypePropertiesSection.js: Added.
+        (WebInspector.TypePropertiesSection):
+        (WebInspector.TypePropertiesSection.prototype.onpopulate):
+        (WebInspector.TypePropertiesSection.PropertyComparator):
+        (WebInspector.TypePropertyTreeElement):
+        (WebInspector.TypePropertyTreeElement.prototype.onpopulate):
+        * UserInterface/Views/TypeTokenView.css: Added.
+        (.type-token):
+        (.type-token-left-spacing):
+        (.type-token-right-spacing):
+        (.type-token-function, .type-token-boolean):
+        (.type-token-number):
+        (.type-token-string):
+        (.type-token-default):
+        (.type-token-empty):
+        (.type-token-many):
+        * UserInterface/Views/TypeTokenView.js: Added.
+        (WebInspector.TypeTokenView):
+        (WebInspector.TypeTokenView.titleForPopover):
+        (WebInspector.TypeTokenView.prototype.update):
+        (WebInspector.TypeTokenView.prototype._setUpMouseoverHandlers):
+        (WebInspector.TypeTokenView.prototype._shouldShowPopover):
+        The inline "type token" view.
+
 2014-08-29  Saam Barati  <sbarati@apple.com>
 
         Web Inspector: Fix how Popover animates
index 09cdb2e..648f5e2 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index b349a84..bc4ced2 100644 (file)
@@ -162,6 +162,10 @@ WebInspector.loaded = function()
     this.showShadowDOMSetting = new WebInspector.Setting("show-shadow-dom", false);
     this.showReplayInterfaceSetting = new WebInspector.Setting("show-web-replay", false);
 
+    this.showJavaScriptTypeInformationSetting = new WebInspector.Setting("show-javascript-type-information", false);
+    if (this.showJavaScriptTypeInformationSetting.value)
+        RuntimeAgent.enableTypeProfiler();
+
     this.mouseCoords = {
         x: 0,
         y: 0
index c502fd8..bd5f72f 100644 (file)
@@ -49,6 +49,7 @@ WebInspector.CodeMirrorTokenTrackingController.Mode = {
     None: "none",
     NonSymbolTokens: "non-symbol-tokens",
     JavaScriptExpression: "javascript-expression",
+    JavaScriptTypeInformation: "javascript-type-information",
     MarkedTokens: "marked-tokens"
 }
 
@@ -390,6 +391,7 @@ WebInspector.CodeMirrorTokenTrackingController.prototype = {
             this._candidate = this._processNonSymbolToken();
             break;
         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
+        case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
             this._candidate = this._processJavaScriptExpression();
             break;
         case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
index d2b611b..da72928 100644 (file)
@@ -45,15 +45,27 @@ WebInspector.FormatterSourceMap.prototype = {
     originalToFormatted: function(lineNumber, columnNumber)
     {
         var originalPosition = this._locationToPosition(this._originalLineEndings, lineNumber || 0, columnNumber || 0);
+        return this.originalPositionToFormatted(originalPosition);
+    },
+
+    originalPositionToFormatted: function(originalPosition)
+    {
         var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
         return this._positionToLocation(this._formattedLineEndings, formattedPosition);
     },
 
+
     formattedToOriginal: function(lineNumber, columnNumber)
     {
+        var originalPosition = this.formattedToOriginalOffset(lineNumber, columnNumber);
+        return this._positionToLocation(this._originalLineEndings, originalPosition);
+    },
+
+    formattedToOriginalOffset: function(lineNumber, columnNumber)
+    {
         var formattedPosition = this._locationToPosition(this._formattedLineEndings, lineNumber || 0, columnNumber || 0);
         var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
-        return this._positionToLocation(this._originalLineEndings, originalPosition);
+        return originalPosition;
     },
 
     // Private
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js b/Source/WebInspectorUI/UserInterface/Controllers/TypeTokenAnnotator.js
new file mode 100644 (file)
index 0000000..5ede3dc
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2014 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.TypeTokenAnnotator = function(sourceCodeTextEditor, script)
+{
+    WebInspector.Object.call(this);
+
+    console.assert(sourceCodeTextEditor && sourceCodeTextEditor instanceof WebInspector.SourceCodeTextEditor, sourceCodeTextEditor);
+    console.assert(script, script);
+
+    this._script = script;
+    this._sourceCodeTextEditor = sourceCodeTextEditor;
+    this._typeTokenNodes = [];
+    this._typeTokenBookmarks = [];
+    this._timeoutIdentifier = null;
+    this._isActive = false;
+};
+
+WebInspector.TypeTokenAnnotator.prototype = {
+    constructor: WebInspector.TypeTokenAnnotator,
+    __proto__: WebInspector.Object.prototype,
+
+    // Public
+
+    get isActive()
+    {
+        return this._isActive;
+    },
+
+    get sourceCodeTextEditor()
+    {
+        return this._sourceCodeTextEditor;
+    },
+
+    pause: function()
+    {
+        this._clearTimeoutIfNeeded();
+        this._isActive = false;
+    },
+
+    resume: function()
+    {
+        this._clearTimeoutIfNeeded();
+        this._isActive = true;
+        this._insertAnnotations();
+    },
+
+    refresh: function()
+    {
+        console.assert(this._isActive);
+        if (!this._isActive)
+            return;
+
+        this._clearTimeoutIfNeeded();
+        this._insertAnnotations();
+    },
+
+    reset: function()
+    {
+        this._clearTimeoutIfNeeded();
+        this._isActive = true;
+        this._clearTypeTokens();
+        this._insertAnnotations();
+    },
+
+    toggleTypeAnnotations: function()
+    {
+        if (this._isActive) {
+            this._isActive = false;
+            this._clearTypeTokens();
+        } else {
+            this._isActive = true;
+            this.reset();
+        }
+
+        return this._isActive;
+    },
+
+    // Private
+
+    _insertAnnotations: function()
+    {
+        if (!this._isActive)
+            return;
+
+        var scriptSyntaxTree = this._script.scriptSyntaxTree;
+
+        if (!scriptSyntaxTree) {
+            this._script.requestScriptSyntaxTree(function(syntaxTree) {
+                // After requesting the tree, we still might get a null tree from a parse error.
+                if (syntaxTree)
+                    this._insertAnnotations();
+            }.bind(this));
+
+            return;
+        }
+
+        if (!scriptSyntaxTree.parsedSuccessfully)
+            return;
+
+        var {startOffset, endOffset} = this._sourceCodeTextEditor.visibleRangeOffsets();
+
+        var startTime = Date.now();
+        var allNodesInRange = scriptSyntaxTree.filterByRange(startOffset, endOffset);
+        scriptSyntaxTree.updateTypes(allNodesInRange, function afterTypeUpdates() {
+            // Because this is an asynchronous call, we could have been deactivated before the callback function is called.
+            if (!this._isActive)
+                return;
+
+            allNodesInRange.forEach(this._insertTypeTokensForEachNode, this);
+            allNodesInRange.forEach(this._updateTypeTokensForEachNode, this);
+
+            var totalTime = Date.now() - startTime;
+            var timeoutTime = Math.min(Math.max(7500, totalTime), 8 * totalTime);
+
+            this._timeoutIdentifier = setTimeout(function timeoutUpdate() {
+                this._timeoutIdentifier = null;
+                this._insertAnnotations();
+            }.bind(this), timeoutTime);
+        }.bind(this));
+    },
+
+    _insertTypeTokensForEachNode: function(node)
+    {
+        var scriptSyntaxTree = this._script._scriptSyntaxTree;
+
+        switch (node.type) {
+        case WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration:
+        case WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression:
+            for (var param of node.params) {
+                if (!param.attachments.__typeToken && param.attachments.types && param.attachments.types.displayTypeName)
+                    this._insertToken(param.range[0], param, false, WebInspector.TypeTokenView.TitleType.Variable, param.name);
+            }
+
+            // If a function does not have an explicit return type, then don't show a return type unless we think it's a constructor.
+            var functionReturnType = node.attachments.returnTypes;
+            if (node.attachments.__typeToken || !functionReturnType || !functionReturnType.displayTypeName)
+                break;
+
+            if (scriptSyntaxTree.containsNonEmptyReturnStatement(node.body) || functionReturnType.displayTypeName !== "Undefined") {
+                var functionName = node.id ? node.id.name : null;
+                this._insertToken(node.isGetterOrSetter ? node.getterOrSetterRange[0] : node.range[0], node,
+                    true, WebInspector.TypeTokenView.TitleType.ReturnStatement, functionName);
+            }
+            break;
+        case WebInspector.ScriptSyntaxTree.NodeType.VariableDeclarator:
+            if (!node.attachments.__typeToken && node.attachments.types && node.attachments.types.displayTypeName)
+                this._insertToken(node.id.range[0], node, false, WebInspector.TypeTokenView.TitleType.Variable, node.id.name);
+
+            break;
+        }
+    },
+
+    _insertToken: function(originalOffset, node, shouldTranslateOffsetToAfterParameterList, typeTokenTitleType, functionOrVariableName)
+    {
+        var tokenPosition = this._sourceCodeTextEditor.originalOffsetToCurrentPosition(originalOffset);
+        var currentOffset = this._sourceCodeTextEditor.currentPositionToCurrentOffset(tokenPosition);
+        var sourceString = this._sourceCodeTextEditor.string;
+
+        if (shouldTranslateOffsetToAfterParameterList) {
+            // Translate the position to the closing parenthesis of the function arguments:
+            // translate from: [type-token] function foo() {} => to: function foo() [type-token] {}
+            currentOffset = this._translateToOffsetAfterFunctionParameterList(node, currentOffset, sourceString);
+            tokenPosition = this._sourceCodeTextEditor.currentOffsetToCurrentPosition(currentOffset);
+        }
+
+        // Note: bookmarks render to the left of the character they're being displayed next to.
+        // This is why right margin checks the current offset. And this is okay to do because JavaScript can't be written right-to-left.
+        var isSpaceRegexp = /\s/;
+        var shouldHaveLeftMargin = currentOffset !== 0 && !isSpaceRegexp.test(sourceString[currentOffset - 1]);
+        var shouldHaveRightMargin = !isSpaceRegexp.test(sourceString[currentOffset]);
+        var typeToken = new WebInspector.TypeTokenView(this, shouldHaveRightMargin, shouldHaveLeftMargin, typeTokenTitleType, functionOrVariableName);
+        var bookmark = this._sourceCodeTextEditor.setInlineWidget(tokenPosition, typeToken.element);
+        node.attachments.__typeToken = typeToken;
+        this._typeTokenNodes.push(node);
+        this._typeTokenBookmarks.push(bookmark);
+    },
+
+    _updateTypeTokensForEachNode: function(node)
+    {
+        switch (node.type) {
+        case WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration:
+        case WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression:
+            node.params.forEach(function(param) {
+                if (param.attachments.__typeToken)
+                    param.attachments.__typeToken.update(param.attachments.types);
+            });
+            if (node.attachments.__typeToken)
+                node.attachments.__typeToken.update(node.attachments.returnTypes);
+            break;
+        case WebInspector.ScriptSyntaxTree.NodeType.VariableDeclarator:
+            if (node.attachments.__typeToken)
+                node.attachments.__typeToken.update(node.attachments.types);
+            break;
+        }
+    },
+
+    _translateToOffsetAfterFunctionParameterList: function(node, offset, sourceString)
+    {
+        // The assumption here is that we get the offset starting at the function keyword (or after the get/set keywords).
+        // We will return the offset for the closing parenthesis in the function declaration.
+        // All this code is just a way to find this parenthesis while ignoring comments.
+
+        var isMultiLineComment = false;
+        var isSingleLineComment = false;
+        var shouldIgnore = false;
+
+        function isLineTerminator(char)
+        {
+            // Reference EcmaScript 5 grammar for single line comments and line terminators:
+            // http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
+            // http://www.ecma-international.org/ecma-262/5.1/#sec-7.4
+            return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
+        }
+
+        while ((sourceString[offset] !== ")" || shouldIgnore) && offset < sourceString.length) {
+            if (isSingleLineComment && isLineTerminator(sourceString[offset])) {
+                isSingleLineComment = false;
+                shouldIgnore = false;
+            } else if (isMultiLineComment && sourceString[offset] === "*" && sourceString[offset + 1] === "/") {
+                isMultiLineComment = false;
+                shouldIgnore = false;
+                offset++;
+            } else if (!shouldIgnore && sourceString[offset] === "/") {
+                offset++;
+                if (sourceString[offset] === "*")
+                    isMultiLineComment = true;
+                else if (sourceString[offset] === "/")
+                    isSingleLineComment = true;
+                else
+                    throw new Error("Bad parsing. Couldn't parse comment preamble.");
+                shouldIgnore = true;
+            }
+
+            offset++;
+        }
+
+        return offset + 1;
+    },
+
+    _clearTypeTokens: function()
+    {
+        this._typeTokenNodes.forEach(function(node) {
+            node.attachments.__typeToken = null;
+        });
+        this._typeTokenBookmarks.forEach(function(bookmark) {
+            bookmark.clear();
+        });
+
+        this._typeTokenNodes = [];
+        this._typeTokenBookmarks = [];
+    },
+
+    _clearTimeoutIfNeeded: function()
+    {
+        if (this._timeoutIdentifier) {
+            clearTimeout(this._timeoutIdentifier);
+            this._timeoutIdentifier = null;
+        }
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Images/NavigationItemTypes.svg b/Source/WebInspectorUI/UserInterface/Images/NavigationItemTypes.svg
new file mode 100644 (file)
index 0000000..a5f2196
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<!-- Copyright © 2014 Apple Inc. All rights reserved. -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
+    <rect class="stroked" fill="none" stroke="black" x="0.5" y="1.0" width="12" height="12" rx="2"/>
+    <path class="stroked" fill="none" stroke="black" stroke-linecap="square" d="M 6.5 5.5 L 6.5 9.5 M 4.0 4.5 L 9.0 4.5"/>
+</svg>
index 28ba47f..57af727 100644 (file)
     <link rel="stylesheet" href="Views/TimelineView.css">
     <link rel="stylesheet" href="Views/Toolbar.css">
     <link rel="stylesheet" href="Views/TreeElementStatusButton.css">
+    <link rel="stylesheet" href="Views/TypeTokenView.css">
 
     <link rel="stylesheet" href="Controllers/CodeMirrorCompletionController.css">
     <link rel="stylesheet" href="Controllers/CodeMirrorDragToAdjustNumberController.css">
     <script src="Views/Toolbar.js"></script>
     <script src="Views/TreeElementStatusButton.js"></script>
     <script src="Views/TreeOutlineDataGridSynchronizer.js"></script>
+    <script src="Views/TypePropertiesSection.js"></script>
+    <script src="Views/TypeTokenView.js"></script>
 
     <script src="Controllers/CodeMirrorEditingController.js"></script>
 
     <script src="Controllers/SourceMapManager.js"></script>
     <script src="Controllers/StorageManager.js"></script>
     <script src="Controllers/TimelineManager.js"></script>
+    <script src="Controllers/TypeTokenAnnotator.js"></script>
 
     <script src="Base/Main.js"></script>
 
index f0d0a71..14fcc8d 100644 (file)
@@ -105,6 +105,11 @@ WebInspector.Script.prototype = {
         return this._resource;
     },
 
+    get scriptSyntaxTree()
+    {
+        return this._scriptSyntaxTree;
+    },
+
     canRequestContentFromBackend: function()
     {
         // We can request content if we have an id.
index b53209c..53f26e4 100644 (file)
@@ -172,8 +172,8 @@ WebInspector.ScriptSyntaxTree.prototype = {
         if (!this._parsedSuccessfully)
             return false;
 
-        if (startNode._attachments._hasNonEmptyReturnStatement !== undefined)
-            return startNode._attachments._hasNonEmptyReturnStatement;
+        if (startNode.attachments._hasNonEmptyReturnStatement !== undefined)
+            return startNode.attachments._hasNonEmptyReturnStatement;
 
         function removeFunctionsFilter(node)
         {
@@ -191,19 +191,22 @@ WebInspector.ScriptSyntaxTree.prototype = {
             }
         }
          
-        startNode._attachments._hasNonEmptyReturnStatement = hasNonEmptyReturnStatement;
+        startNode.attachments._hasNonEmptyReturnStatement = hasNonEmptyReturnStatement;
 
         return hasNonEmptyReturnStatement;
     },
 
-    updateTypes: function(sourceID, nodesToUpdate, callback) 
+    updateTypes: function(nodesToUpdate, callback)
     {
+        console.assert(RuntimeAgent.getRuntimeTypesForVariablesAtOffsets);
         console.assert(Array.isArray(nodesToUpdate) && this._parsedSuccessfully);
+
         if (!this._parsedSuccessfully)
             return;
 
         var allRequests = [];
         var allRequestNodes = [];
+        var sourceID = this._script.id;
 
         for (var node of nodesToUpdate) {
             switch (node.type) {
@@ -249,9 +252,9 @@ WebInspector.ScriptSyntaxTree.prototype = {
                 var node = allRequestNodes[i];
                 var typeInformation = typeInformationArray[i];
                 if (allRequests[i].typeInformationDescriptor === WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.FunctionReturn)
-                    node._attachments._returnTypes = typeInformation;
+                    node.attachments.returnTypes = typeInformation;
                 else
-                    node._attachments._types = typeInformation;
+                    node.attachments.types = typeInformation;
             }
 
             callback();
@@ -777,7 +780,7 @@ WebInspector.ScriptSyntaxTree.prototype = {
         
         result.range = node.range;
         // This is an object for which you can add fields to an AST node without worrying about polluting the syntax-related fields of the node.
-        result._attachments = {}; 
+        result.attachments = {};
 
         return result;
     }
index 41a39ed..aaf6e34 100644 (file)
@@ -59,6 +59,15 @@ WebInspector.ScriptContentView = function(script)
     this._prettyPrintButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("pretty-print", toolTip, activatedToolTip, curleyBracesImage.src, curleyBracesImage.width, curleyBracesImage.height);
     this._prettyPrintButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._togglePrettyPrint, this);
     this._prettyPrintButtonNavigationItem.enabled = false; // Enabled when the text editor is populated with content.
+
+    var showTypesImageSize = WebInspector.Platform.isLegacyMacOS ? 15 : 16;
+    var toolTipTypes = WebInspector.UIString("Show type information");
+    var activatedToolTipTypes = WebInspector.UIString("Hide type information");
+    this._showTypesButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("show-types", toolTipTypes, activatedToolTipTypes, "Images/NavigationItemTypes.svg", showTypesImageSize, showTypesImageSize);
+    this._showTypesButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleTypeAnnotations, this);
+    this._showTypesButtonNavigationItem.enabled = false;
+
+    WebInspector.showJavaScriptTypeInformationSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showJavaScriptTypeInformationSettingChanged, this);
 };
 
 WebInspector.ScriptContentView.StyleClassName = "script";
@@ -70,7 +79,7 @@ WebInspector.ScriptContentView.prototype = {
 
     get navigationItems()
     {
-        return [this._prettyPrintButtonNavigationItem];
+        return [this._prettyPrintButtonNavigationItem, this._showTypesButtonNavigationItem];
     },
 
     get script()
@@ -204,6 +213,8 @@ WebInspector.ScriptContentView.prototype = {
     _contentDidPopulate: function(event)
     {
         this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
+        this._showTypesButtonNavigationItem.enabled = this._textEditor.canShowTypeAnnotations();
+        this._showTypesButtonNavigationItem.activated = WebInspector.showJavaScriptTypeInformationSetting.value;
     },
 
     _togglePrettyPrint: function(event)
@@ -212,6 +223,16 @@ WebInspector.ScriptContentView.prototype = {
         this._textEditor.formatted = activated;
     },
 
+    _toggleTypeAnnotations: function(event)
+    {
+        this._textEditor.toggleTypeAnnotations();
+    },
+
+    _showJavaScriptTypeInformationSettingChanged: function(event)
+    {
+        this._showTypesButtonNavigationItem.activated = WebInspector.showJavaScriptTypeInformationSetting.value;
+    },
+
     _textEditorFormattingDidChange: function(event)
     {
         this._prettyPrintButtonNavigationItem.activated = this._textEditor.formatted;
index 7f7ae08..16c98eb 100644 (file)
@@ -36,6 +36,9 @@ WebInspector.SourceCodeTextEditor = function(sourceCode)
 
     WebInspector.TextEditor.call(this, null, null, this);
 
+    this._typeTokenAnnotator = null;
+    this._typeTokenScrollHandler = null;
+
     // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
     this._updateTokenTrackingControllerState();
 
@@ -83,6 +86,7 @@ WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugg
 WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight";
 WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
 WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
+WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
 
 WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength = 500;
 
@@ -101,6 +105,14 @@ WebInspector.SourceCodeTextEditor.prototype = {
         return this._sourceCode;
     },
 
+    shown: function()
+    {
+        WebInspector.TextEditor.prototype.shown.call(this);
+
+        if (this._typeTokenAnnotator)
+            this._typeTokenAnnotator.resume();
+    },
+
     hidden: function()
     {
         WebInspector.TextEditor.prototype.hidden.call(this);
@@ -110,6 +122,9 @@ WebInspector.SourceCodeTextEditor.prototype = {
         this._dismissPopover();
         
         this._dismissEditingController(true);
+
+        if (this._typeTokenAnnotator)
+            this._typeTokenAnnotator.pause();
     },
 
     close: function()
@@ -149,6 +164,11 @@ WebInspector.SourceCodeTextEditor.prototype = {
         return WebInspector.TextEditor.prototype.canBeFormatted.call(this);
     },
 
+    canShowTypeAnnotations: function()
+    {
+        return !!this._typeTokenAnnotator;
+    },
+
     customPerformSearch: function(query)
     {
         function searchResultCallback(error, matches)
@@ -234,6 +254,55 @@ WebInspector.SourceCodeTextEditor.prototype = {
             this._updateEditableMarkers(range);
     },
 
+    toggleTypeAnnotations: function()
+    {
+        if (!this._typeTokenAnnotator)
+            return false;
+
+        var isActivated = this._typeTokenAnnotator.toggleTypeAnnotations();
+        if (isActivated) {
+            RuntimeAgent.enableTypeProfiler();
+            this._enableScrollEventsForTypeTokenAnnotator();
+        } else {
+            // We disable type profiling when exiting the inspector, so no need to call it here. If we were
+            // to call it here, we would have JavaScriptCore compile out all the necessary type profiling information,
+            // so if a user were to quickly press then unpress the button, we wouldn't be able to re-show type information.
+            this._disableScrollEventsForTypeTokenAnnotator();
+        }
+        WebInspector.showJavaScriptTypeInformationSetting.value = isActivated;
+
+        this._updateTokenTrackingControllerState();
+        return isActivated;
+    },
+
+    showPopoverForTypes: function(types, bounds, title)
+    {
+        var content = document.createElement("div");
+        content.className = "object expandable";
+
+        var titleElement = document.createElement("div");
+        titleElement.className = "title";
+        titleElement.textContent = title;
+        content.appendChild(titleElement);
+
+        var section = new WebInspector.TypePropertiesSection(types);
+        section.expanded = true;
+        section.element.classList.add("body");
+        content.appendChild(section.element);
+
+        this._showPopover(content, bounds);
+    },
+
+    // Protected
+
+    prettyPrint: function(pretty)
+    {
+        WebInspector.TextEditor.prototype.prettyPrint.call(this, pretty);
+
+        if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive)
+            this._typeTokenAnnotator.reset();
+    },
+
     // Private
 
     _unformattedLineInfoForEditorLineInfo: function(lineInfo)
@@ -358,6 +427,9 @@ WebInspector.SourceCodeTextEditor.prototype = {
 
         this._contentWillPopulate(content);
         this.string = content;
+
+        this._makeTypeTokenAnnotator();
+
         this._contentDidPopulate();
     },
 
@@ -967,12 +1039,16 @@ WebInspector.SourceCodeTextEditor.prototype = {
     _debuggerDidPause: function(event)
     {
         this._updateTokenTrackingControllerState();
+        if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive)
+            this._typeTokenAnnotator.refresh();
     },
 
     _debuggerDidResume: function(event)
     {
         this._updateTokenTrackingControllerState();
         this._dismissPopover();
+        if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive)
+            this._typeTokenAnnotator.refresh();
     },
 
     _sourceCodeSourceMapAdded: function(event)
@@ -988,6 +1064,8 @@ WebInspector.SourceCodeTextEditor.prototype = {
         var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
         if (WebInspector.debuggerManager.paused)
             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
+        else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive)
+            mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
         else if (this._hasColorMarkers())
             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
         else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey)
@@ -1010,6 +1088,7 @@ WebInspector.SourceCodeTextEditor.prototype = {
             this._dismissPopover();
             break;
         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
+        case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
             this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
             this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
@@ -1075,6 +1154,11 @@ WebInspector.SourceCodeTextEditor.prototype = {
             return;
         }
 
+        if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
+            this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
+            return;
+        }
+
         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
             var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
             if (markers.length > 0)
@@ -1130,25 +1214,67 @@ WebInspector.SourceCodeTextEditor.prototype = {
         DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
     },
 
-    _showPopover: function(content)
+    _tokenTrackingControllerHighlightedJavaScriptTypeInformation: function(candidate)
     {
-        console.assert(this.tokenTrackingController.candidate);
+        console.assert(candidate.expression);
+
+        var sourceCode = this._sourceCode;
+        var sourceID = sourceCode.scripts[0].id;
+        var range = candidate.hoveredTokenRange;
+        var offset = this.currentPositionToOriginalOffset({line: range.start.line, ch: range.start.ch});
+
+        var allRequests = [{
+            typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
+            sourceID: sourceID,
+            divot: offset
+        }];
 
+        function handler(error, allTypes) {
+            if (error)
+                return;
+
+            if (candidate !== this.tokenTrackingController.candidate)
+                return;
+
+            console.assert(allTypes.length === 1);
+            if (!allTypes.length)
+                return;
+            var types = allTypes[0];
+            if (types.displayTypeName) {
+                var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression);
+                this.showPopoverForTypes(types, null, popoverTitle);
+            }
+        }
+
+        RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
+    },
+
+    _showPopover: function(content, bounds)
+    {
+        console.assert(this.tokenTrackingController.candidate || bounds);
+
+        var shouldHighlightRange = false;
         var candidate = this.tokenTrackingController.candidate;
-        if (!candidate)
-            return;
+        // If bounds is falsey, this is a popover introduced from a hover event.
+        // Otherwise, this is called from TypeTokenAnnotator.
+        if (!bounds) {
+            if (!candidate)
+                return;
 
-        content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
+            var rects = this.rectsForRange(candidate.hoveredTokenRange);
+            bounds = WebInspector.Rect.unionOfRects(rects);
 
-        var rects = this.rectsForRange(candidate.hoveredTokenRange);
-        var bounds = WebInspector.Rect.unionOfRects(rects);
+            shouldHighlightRange = true;
+        }
+
+        content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
 
         this._popover = this._popover || new WebInspector.Popover(this);
         this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]);
+        if (shouldHighlightRange)
+            this.tokenTrackingController.highlightRange(candidate.expressionRange);
 
         this._trackPopoverEvents();
-
-        this.tokenTrackingController.highlightRange(candidate.expressionRange);
     },
 
     _showPopoverForFunction: function(data)
@@ -1384,6 +1510,63 @@ WebInspector.SourceCodeTextEditor.prototype = {
         this._ignoreContentDidChange--;
 
         delete this._editingController;
+    },
+
+    _makeTypeTokenAnnotator: function()
+    {
+        if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
+            return;
+
+        var script = null;
+        // FIXME: This needs to me modified to work with HTML files with inline script tags.
+        if (this._sourceCode instanceof WebInspector.Script)
+            script = this._sourceCode;
+        else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.type === WebInspector.Resource.Type.Script && this._sourceCode.scripts.length)
+            script = this._sourceCode.scripts[0];
+
+        if (!script)
+            return;
+
+        this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script);
+        if (WebInspector.showJavaScriptTypeInformationSetting.value) {
+            this._typeTokenAnnotator.reset();
+            this._enableScrollEventsForTypeTokenAnnotator();
+        }
+    },
+
+    _enableScrollEventsForTypeTokenAnnotator: function()
+    {
+        // Pause updating type tokens while scrolling to prevent frame loss.
+        console.assert(!this._typeTokenScrollHandler);
+        this._typeTokenScrollHandler = this._makeTypeTokenScrollEventHandler();
+        this.addScrollHandler(this._typeTokenScrollHandler);
+    },
+
+    _disableScrollEventsForTypeTokenAnnotator: function()
+    {
+        console.assert(this._typeTokenScrollHandler);
+        this.removeScrollHandler(this._typeTokenScrollHandler);
+        this._typeTokenScrollHandler = null;
+    },
+
+    _makeTypeTokenScrollEventHandler: function()
+    {
+        var typeTokenAnnotator = this._typeTokenAnnotator;
+        var timeoutIdentifier = null;
+        function scrollHandler()
+        {
+            if (timeoutIdentifier)
+                clearTimeout(timeoutIdentifier);
+            else
+                typeTokenAnnotator.pause();
+
+            timeoutIdentifier = setTimeout(function() {
+                timeoutIdentifier = null;
+                typeTokenAnnotator.resume();
+            }, WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
+        }
+
+        return scrollHandler;
     }
 };
 
index 261a228..b5e9a88 100644 (file)
@@ -50,6 +50,12 @@ WebInspector.TextContentView = function(string, mimeType)
     this._prettyPrintButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("pretty-print", toolTip, activatedToolTip, curleyBracesImage.src, curleyBracesImage.width, curleyBracesImage.height);
     this._prettyPrintButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._togglePrettyPrint, this);
     this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
+
+    var showTypesImageSize = WebInspector.Platform.isLegacyMacOS ? 15 : 16;
+    var toolTipTypes = WebInspector.UIString("Show type information");
+    var activatedToolTipTypes = WebInspector.UIString("Hide type information");
+    this._showTypesButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("show-types", toolTipTypes, activatedToolTipTypes, "Images/NavigationItemTypes.svg", showTypesImageSize, showTypesImageSize);
+    this._showTypesButtonNavigationItem.enabled = false;
 };
 
 WebInspector.TextContentView.StyleClassName = "text";
@@ -66,7 +72,7 @@ WebInspector.TextContentView.prototype = {
 
     get navigationItems()
     {
-        return [this._prettyPrintButtonNavigationItem];
+        return [this._prettyPrintButtonNavigationItem, this._showTypesButtonNavigationItem];
     },
 
     revealPosition: function(position, textRangeToSelect, forceUnformatted)
index 0ee57cb..50caec7 100644 (file)
@@ -167,7 +167,7 @@ WebInspector.TextEditor.prototype = {
             return;
 
         this._ignoreCodeMirrorContentDidChangeEvent++;
-        this._prettyPrint(formatted);
+        this.prettyPrint(formatted);
         this._ignoreCodeMirrorContentDidChangeEvent--;
         console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0);
 
@@ -198,6 +198,11 @@ WebInspector.TextEditor.prototype = {
         return this.hasFormatter();
     },
 
+    canShowTypeAnnotations: function()
+    {
+        return false;
+    },
+
     get selectedTextRange()
     {
         var start = this._codeMirror.getCursor(true);
@@ -639,6 +644,173 @@ WebInspector.TextEditor.prototype = {
         }
     },
 
+    visibleRangeOffsets: function()
+    {
+        var startOffset = null;
+        var endOffset = null;
+        var visibleRange = this._codeMirror.getViewport();
+
+        if (this._formatterSourceMap) {
+            startOffset = this._formatterSourceMap.formattedToOriginalOffset(Math.max(visibleRange.from - 1, 0), 0);
+            endOffset = this._formatterSourceMap.formattedToOriginalOffset(visibleRange.to - 1, 0);
+        } else {
+            startOffset = this._codeMirror.getDoc().indexFromPos({line: visibleRange.from, ch: 0});
+            endOffset = this._codeMirror.getDoc().indexFromPos({line: visibleRange.to, ch: 0});
+        }
+
+        return {startOffset: startOffset, endOffset: endOffset};
+    },
+
+    originalOffsetToCurrentPosition: function(offset)
+    {
+        var position = null;
+        if (this._formatterSourceMap) {
+            var location = this._formatterSourceMap.originalPositionToFormatted(offset);
+            position = {line: location.lineNumber, ch: location.columnNumber};
+        } else
+            position = this._codeMirror.getDoc().posFromIndex(offset);
+
+        return position;
+    },
+
+    currentOffsetToCurrentPosition: function(offset)
+    {
+        return this._codeMirror.getDoc().posFromIndex(offset);
+    },
+
+    currentPositionToOriginalOffset: function(position)
+    {
+        var offset = null;
+        if (this._formatterSourceMap)
+            offset = this._formatterSourceMap.formattedToOriginalOffset(position.line, position.ch);
+        else
+            offset = this.tokenTrackingController._codeMirror.getDoc().indexFromPos(position);
+
+        return offset;
+    },
+
+    currentPositionToCurrentOffset: function(position)
+    {
+        return this._codeMirror.getDoc().indexFromPos(position);
+    },
+
+    setInlineWidget: function(position, inlineElement)
+    {
+        return this._codeMirror.setUniqueBookmark(position, {widget: inlineElement});
+    },
+
+    addScrollHandler: function(handler)
+    {
+        this._codeMirror.on("scroll", handler);
+    },
+
+    removeScrollHandler: function(handler)
+    {
+        this._codeMirror.off("scroll", handler);
+    },
+
+    // Protected
+
+    prettyPrint: function(pretty)
+    {
+        function prettyPrintAndUpdateEditor()
+        {
+            const start = {line: 0, ch: 0};
+            const end = {line: this._codeMirror.lineCount() - 1};
+
+            var oldSelectionAnchor = this._codeMirror.getCursor("anchor");
+            var oldSelectionHead = this._codeMirror.getCursor("head");
+            var newSelectionAnchor, newSelectionHead;
+            var newExecutionLocation = null;
+
+            if (pretty) {
+                // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
+                const indentString = "    ";
+                var originalLineEndings = [];
+                var formattedLineEndings = [];
+                var mapping = {original: [0], formatted: [0]};
+                var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
+                var formatter = new Formatter(this._codeMirror, builder);
+                formatter.format(start, end);
+
+                this._formatterSourceMap = WebInspector.FormatterSourceMap.fromBuilder(builder);
+
+                this._codeMirror.setValue(builder.formattedContent);
+
+                if (this._positionToReveal) {
+                    var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
+                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
+                }
+
+                if (this._textRangeToSelect) {
+                    var mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
+                    var mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
+                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
+                }
+
+                if (!isNaN(this._executionLineNumber)) {
+                    console.assert(!isNaN(this._executionColumnNumber));
+                    newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
+                }
+
+                var mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch);
+                var mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch);
+                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
+                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
+            } else {
+                this._codeMirror.undo();
+
+                if (this._positionToReveal) {
+                    var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
+                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
+                }
+
+                if (this._textRangeToSelect) {
+                    var mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
+                    var mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
+                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
+                }
+
+                if (!isNaN(this._executionLineNumber)) {
+                    console.assert(!isNaN(this._executionColumnNumber));
+                    newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
+                }
+
+                var mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch);
+                var mappedHeadLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionHead.line, oldSelectionHead.ch);
+                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
+                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
+
+                this._formatterSourceMap = null;
+            }
+
+            this._scrollIntoViewCentered(newSelectionAnchor);
+            this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
+
+            if (newExecutionLocation) {
+                delete this._executionLineHandle;
+                this.executionColumnNumber = newExecutionLocation.columnNumber;
+                this.executionLineNumber = newExecutionLocation.lineNumber;
+            }
+
+            // FIXME: <rdar://problem/13129955> FindBanner: New searches should not lose search position (start from current selection/caret)
+            if (this.currentSearchQuery) {
+                var searchQuery = this.currentSearchQuery;
+                this.searchCleared();
+                // Set timeout so that this happens after the current CodeMirror operation.
+                // The editor has to update for the value and selection changes.
+                setTimeout(function(query) {
+                    this.performSearch(searchQuery);
+                }.bind(this), 0);
+            }
+
+            if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
+                this._delegate.textEditorUpdatedFormatting(this);
+        }
+
+        this._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this));
+    },
+
     // Private
 
     _contentChanged: function(codeMirror, change)
@@ -1235,106 +1407,6 @@ WebInspector.TextEditor.prototype = {
         var lineHeight = Math.ceil(this._codeMirror.defaultTextHeight());
         var margin = Math.floor((scrollInfo.clientHeight - lineHeight) / 2);
         this._codeMirror.scrollIntoView(position, margin);
-    },
-
-    _prettyPrint: function(pretty)
-    {
-        function prettyPrintAndUpdateEditor()
-        {
-            const start = {line: 0, ch: 0};
-            const end = {line: this._codeMirror.lineCount() - 1};
-
-            var oldSelectionAnchor = this._codeMirror.getCursor("anchor");
-            var oldSelectionHead = this._codeMirror.getCursor("head");
-            var newSelectionAnchor, newSelectionHead;
-            var newExecutionLocation = null;
-
-            if (pretty) {
-                // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
-                const indentString = "    ";
-                var originalLineEndings = [];
-                var formattedLineEndings = [];
-                var mapping = {original: [0], formatted: [0]};
-                var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
-                var formatter = new Formatter(this._codeMirror, builder);
-                formatter.format(start, end);
-
-                this._formatterSourceMap = WebInspector.FormatterSourceMap.fromBuilder(builder);
-
-                this._codeMirror.setValue(builder.formattedContent);
-
-                if (this._positionToReveal) {
-                    var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
-                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
-                }
-
-                if (this._textRangeToSelect) {
-                    var mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
-                    var mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
-                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
-                }
-
-                if (!isNaN(this._executionLineNumber)) {
-                    console.assert(!isNaN(this._executionColumnNumber));
-                    newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
-                }
-
-                var mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch);
-                var mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch);
-                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
-                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
-            } else {
-                this._codeMirror.undo();
-
-                if (this._positionToReveal) {
-                    var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
-                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
-                }
-
-                if (this._textRangeToSelect) {
-                    var mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
-                    var mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
-                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
-                }
-
-                if (!isNaN(this._executionLineNumber)) {
-                    console.assert(!isNaN(this._executionColumnNumber));
-                    newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
-                }
-
-                var mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch);
-                var mappedHeadLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionHead.line, oldSelectionHead.ch);
-                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
-                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
-
-                this._formatterSourceMap = null;
-            }
-
-            this._scrollIntoViewCentered(newSelectionAnchor);
-            this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
-
-            if (newExecutionLocation) {
-                delete this._executionLineHandle;
-                this.executionColumnNumber = newExecutionLocation.columnNumber;
-                this.executionLineNumber = newExecutionLocation.lineNumber;
-            }
-
-            // FIXME: <rdar://problem/13129955> FindBanner: New searches should not lose search position (start from current selection/caret)
-            if (this.currentSearchQuery) {
-                var searchQuery = this.currentSearchQuery;
-                this.searchCleared();
-                // Set timeout so that this happens after the current CodeMirror operation.
-                // The editor has to update for the value and selection changes.
-                setTimeout(function(query) {
-                    this.performSearch(searchQuery);
-                }.bind(this), 0);
-            }
-
-            if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
-                this._delegate.textEditorUpdatedFormatting(this);
-        }
-
-        this._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this));
     }
 };
 
index fdc65a7..5c9c5aa 100644 (file)
@@ -51,6 +51,15 @@ WebInspector.TextResourceContentView = function(resource)
     this._prettyPrintButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("pretty-print", toolTip, activatedToolTip, curleyBracesImage.src, curleyBracesImage.width, curleyBracesImage.height);
     this._prettyPrintButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._togglePrettyPrint, this);
     this._prettyPrintButtonNavigationItem.enabled = false; // Enabled when the text editor is populated with content.
+
+    var showTypesImageSize = WebInspector.Platform.isLegacyMacOS ? 15 : 16;
+    var toolTipTypes = WebInspector.UIString("Show type information");
+    var activatedToolTipTypes = WebInspector.UIString("Hide type information");
+    this._showTypesButtonNavigationItem = new WebInspector.ActivateButtonNavigationItem("show-types", toolTipTypes, activatedToolTipTypes, "Images/NavigationItemTypes.svg", showTypesImageSize, showTypesImageSize);
+    this._showTypesButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleTypeAnnotations, this);
+    this._showTypesButtonNavigationItem.enabled = false;
+
+    WebInspector.showJavaScriptTypeInformationSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showJavaScriptTypeInformationSettingChanged, this);
 };
 
 WebInspector.TextResourceContentView.StyleClassName = "text";
@@ -62,7 +71,7 @@ WebInspector.TextResourceContentView.prototype = {
 
     get navigationItems()
     {
-        return [this._prettyPrintButtonNavigationItem];
+        return [this._prettyPrintButtonNavigationItem, this._showTypesButtonNavigationItem];
     },
 
     get managesOwnIssues()
@@ -200,6 +209,8 @@ WebInspector.TextResourceContentView.prototype = {
     _contentDidPopulate: function(event)
     {
         this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
+        this._showTypesButtonNavigationItem.enabled = this._textEditor.canShowTypeAnnotations();
+        this._showTypesButtonNavigationItem.activated = WebInspector.showJavaScriptTypeInformationSetting.value;
     },
 
     _togglePrettyPrint: function(event)
@@ -208,6 +219,16 @@ WebInspector.TextResourceContentView.prototype = {
         this._textEditor.formatted = activated;
     },
 
+    _toggleTypeAnnotations: function(event)
+    {
+        this._textEditor.toggleTypeAnnotations();
+    },
+
+    _showJavaScriptTypeInformationSettingChanged: function(event)
+    {
+        this._showTypesButtonNavigationItem.activated = WebInspector.showJavaScriptTypeInformationSetting.value;
+    },
+
     _textEditorFormattingDidChange: function(event)
     {
         this._prettyPrintButtonNavigationItem.activated = this._textEditor.formatted;
diff --git a/Source/WebInspectorUI/UserInterface/Views/TypePropertiesSection.js b/Source/WebInspectorUI/UserInterface/Views/TypePropertiesSection.js
new file mode 100644 (file)
index 0000000..a4880cd
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 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.TypePropertiesSection = function(types, title, subtitle)
+{
+    this.emptyPlaceholder = WebInspector.UIString("No Properties");
+    this.types = types;
+
+    WebInspector.PropertiesSection.call(this, title, subtitle);
+};
+
+WebInspector.TypePropertiesSection.prototype = {
+    constructor: WebInspector.TypePropertiesSection,
+    __proto__: WebInspector.PropertiesSection.prototype,
+
+    onpopulate: function()
+    {
+        this.propertiesTreeOutline.removeChildren();
+
+        var primitiveTypeNames = this.types.globalPrimitiveTypeNames || this.types.localPrimitiveTypeNames;
+        var structures = this.types.globalStructures || this.types.localStructures;
+        var properties = [];
+        for (var struct of structures) {
+            properties.push({
+                name: struct.constructorName,
+                structure: struct
+            });
+        }
+        for (var primitiveName of primitiveTypeNames) {
+            properties.push({
+                name: primitiveName,
+                structure: null
+            });
+        }
+
+        properties.sort(WebInspector.TypePropertiesSection.PropertyComparator);
+        for (var property of properties)
+            this.propertiesTreeOutline.appendChild(new WebInspector.TypePropertyTreeElement(property));
+
+        if (!this.propertiesTreeOutline.children.length) {
+            var title = document.createElement("div");
+            title.className = "info";
+            title.textContent = this.emptyPlaceholder;
+            var infoElement = new TreeElement(title, null, false);
+            this.propertiesTreeOutline.appendChild(infoElement);
+        }
+
+        this.dispatchEventToListeners(WebInspector.Section.Event.VisibleContentDidChange);
+
+        if (properties.length === 1)
+            this.propertiesTreeOutline.children[0].expandRecursively();
+    }
+};
+
+// This is mostly identical to ObjectPropertiesSection.compareProperties.
+// But this checks for equality because we can have two objects named the same thing.
+WebInspector.TypePropertiesSection.PropertyComparator = function(propertyA, propertyB)
+{
+    var a = propertyA.name;
+    var b = propertyB.name;
+    if (a.indexOf("__proto__") !== -1)
+        return 1;
+    if (b.indexOf("__proto__") !== -1)
+        return -1;
+    if (a === b)
+        return 1;
+
+    var diff = 0;
+    var chunk = /^\d+|^\D+/;
+    var chunka, chunkb, anum, bnum;
+    while (diff === 0) {
+        if (!a && b)
+            return -1;
+        if (!b && a)
+            return 1;
+        chunka = a.match(chunk)[0];
+        chunkb = b.match(chunk)[0];
+        anum = !isNaN(chunka);
+        bnum = !isNaN(chunkb);
+        if (anum && !bnum)
+            return -1;
+        if (bnum && !anum)
+            return 1;
+        if (anum && bnum) {
+            diff = chunka - chunkb;
+            if (diff === 0 && chunka.length !== chunkb.length) {
+                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
+                    return chunka.length - chunkb.length;
+                else
+                    return chunkb.length - chunka.length;
+            }
+        } else if (chunka !== chunkb)
+            return (chunka < chunkb) ? -1 : 1;
+        a = a.substring(chunka.length);
+        b = b.substring(chunkb.length);
+    }
+
+    return diff;
+};
+
+WebInspector.TypePropertyTreeElement = function(property)
+{
+    this.property = property;
+
+    this.nameElement = document.createElement("span");
+    this.nameElement.className = "name";
+    this.nameElement.textContent = this.property.name;
+
+    TreeElement.call(this, this.nameElement, null, false);
+
+    this.toggleOnClick = true;
+    this.hasChildren = !!this.property.structure;
+};
+
+WebInspector.TypePropertyTreeElement.prototype = {
+    constructor: WebInspector.TypePropertyTreeElement,
+    __proto__: TreeElement.prototype,
+
+    onpopulate: function()
+    {
+        this.removeChildren();
+
+        var properties = [];
+        for (var fieldName of this.property.structure.fields) {
+            properties.push({
+                name: fieldName,
+                structure: null
+            });
+        }
+
+        properties.sort(WebInspector.TypePropertiesSection.PropertyComparator);
+
+        if (this.property.structure.prototypeStructure) {
+            properties.push({
+                name: this.property.structure.prototypeStructure.constructorName + " (" + WebInspector.UIString("Prototype") + ")",
+                structure: this.property.structure.prototypeStructure
+            });
+        }
+
+        for (var property of properties)
+            this.appendChild(new WebInspector.TypePropertyTreeElement(property));
+    }
+};
+
diff --git a/Source/WebInspectorUI/UserInterface/Views/TypeTokenView.css b/Source/WebInspectorUI/UserInterface/Views/TypeTokenView.css
new file mode 100644 (file)
index 0000000..b896a8f
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+.type-token {
+    display: inline;
+    border-radius: 10px;
+    border-color: transparent;
+    padding-left: 8px;
+    padding-right: 8px;
+    padding-top: 1px;
+    color: white;
+    font-size: 10px;
+    opacity: 0.85;
+}
+
+.type-token-left-spacing {
+    margin-left: 5px;
+}
+
+.type-token-right-spacing {
+    margin-right: 5px;
+}
+
+.type-token-function, .type-token-boolean {
+    background-color: rgb(204, 59, 191);
+}
+
+.type-token-number {
+    background-color: rgb(59, 112, 226);
+}
+
+.type-token-string {
+    background-color: rgb(223, 53, 46);
+}
+
+.type-token-default {
+    background-color: rgb(118, 174, 29);
+}
+
+.type-token-empty {
+    background-color: rgb(126, 126, 126);
+}
+
+.type-token-many {
+    background-color: rgb(237, 132, 0);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/TypeTokenView.js b/Source/WebInspectorUI/UserInterface/Views/TypeTokenView.js
new file mode 100644 (file)
index 0000000..3496e46
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 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.TypeTokenView = function(tokenAnnotator, shouldHaveRightMargin, shouldHaveLeftMargin, titleType, functionOrVariableName)
+{
+    console.assert(titleType === WebInspector.TypeTokenView.TitleType.Variable  || titleType === WebInspector.TypeTokenView.TitleType.ReturnStatement);
+
+    WebInspector.Object.call(this);
+
+    var span = document.createElement("span");
+    span.classList.add("type-token");
+    if (shouldHaveRightMargin)
+        span.classList.add("type-token-right-spacing");
+    if (shouldHaveLeftMargin)
+        span.classList.add("type-token-left-spacing");
+
+    this.element = span;
+    this._tokenAnnotator = tokenAnnotator;
+    this._types = null;
+    this._colorClass = null;
+
+    this._popoverTitle = WebInspector.TypeTokenView.titleForPopover(titleType, functionOrVariableName);
+
+    this._setUpMouseoverHandlers();
+};
+
+WebInspector.TypeTokenView.titleForPopover = function(titleType, functionOrVariableName)
+{
+    var titleString = null;
+    if (titleType === WebInspector.TypeTokenView.TitleType.Variable)
+        titleString = WebInspector.UIString("Type information for variable: %s").format(functionOrVariableName);
+    else {
+        if (functionOrVariableName)
+            titleString = WebInspector.UIString("Return type for function: %s").format(functionOrVariableName);
+        else
+            titleString = WebInspector.UIString("Return type for anonymous function");
+    }
+
+    return titleString;
+};
+
+WebInspector.TypeTokenView.TitleType = {
+    Variable: "title-type-variable",
+    ReturnStatement: "title-type-return-statement"
+};
+
+// These type strings should be kept in sync with type information in JavaScriptCore/runtime/TypeSet.cpp
+WebInspector.TypeTokenView.ColorClassForType = {
+    "String": "type-token-string",
+    "Function": "type-token-function",
+    "Number": "type-token-number",
+    "Integer": "type-token-number",
+    "Undefined": "type-token-empty",
+    "Null": "type-token-empty",
+    "(?)": "type-token-empty",
+    "Boolean": "type-token-boolean",
+    "(many)": "type-token-many"
+};
+
+WebInspector.TypeTokenView.DelayHoverTime = 350;
+
+WebInspector.TypeTokenView.prototype = {
+    constructor: WebInspector.TypeTokenView,
+    __proto__: WebInspector.Object.prototype,
+
+    // Public
+
+    update: function(types)
+    {
+        var title = types.displayTypeName;
+        this.element.textContent = title;
+        this._types = types;
+        var hashString = title[title.length - 1] === "?" ? title.slice(0, title.length - 1) : title;
+
+        if (this._colorClass)
+            this.element.classList.remove(this._colorClass);
+
+        this._colorClass = WebInspector.TypeTokenView.ColorClassForType[hashString] || "type-token-default";
+        this.element.classList.add(this._colorClass);
+    },
+
+    // Private
+
+    _setUpMouseoverHandlers: function()
+    {
+        var timeoutID = null;
+
+        this.element.addEventListener("mouseover", function() {
+            function showPopoverAfterDelay()
+            {
+                timeoutID = null;
+
+                var domRect = this.element.getBoundingClientRect();
+                var bounds = new WebInspector.Rect(domRect.left, domRect.top, domRect.width, domRect.height);
+                this._tokenAnnotator.sourceCodeTextEditor.showPopoverForTypes(this._types, bounds, this._popoverTitle);
+            }
+
+            if (this._shouldShowPopover())
+                timeoutID = setTimeout(showPopoverAfterDelay.bind(this), WebInspector.TypeTokenView.DelayHoverTime);
+        }.bind(this));
+
+        this.element.addEventListener("mouseout", function() {
+            if (timeoutID)
+                clearTimeout(timeoutID);
+        }.bind(this));
+    },
+
+    _shouldShowPopover: function()
+    {
+        if ((this._types.globalPrimitiveTypeNames && this._types.globalPrimitiveTypeNames.length > 1) || (this._types.localPrimitiveTypeNames && this._types.localPrimitiveTypeNames.length > 1))
+            return true;
+
+        if ((this._types.globalStructures && this._types.globalStructures.length) || (this._types.localStructures && this._types.localStructures.length))
+            return true;
+
+        return false;
+    }
+};