Title: [199169] trunk/Source/WebInspectorUI
Revision
199169
Author
[email protected]
Date
2016-04-07 12:29:38 -0700 (Thu, 07 Apr 2016)

Log Message

Web Inspector: Inspector hangs when trying to view a large XHR resource
https://bugs.webkit.org/show_bug.cgi?id=144107
<rdar://problem/20669463>

Reviewed by NOBODY (OOPS!).

Previously auto formatting (initially pretty print source code) in TextEditor
was done synchronously in this order:

  (1) revealing the Editor as soon as we have content
  (2) set the CodeMirror value
  (3) pretty print with the CodeMirror editor
  (4) set the CodeMirror value
      => Layout

At the end, CodeMirror would layout once with the new content. This approach
performs very poorly when step (3) is an asynchronous action, because it would
mean CodeMirror would layout for both (2) and at the end (4) and the layout
itself can be very costly if the content is minified and so has very long
lines at the top of the file that need to be syntax highlighted and visible
since we do not wrap.

This patch changes the order of operations to benefit asynchronous formatting.
When SourceCodeTextEditor determines that it can autoformat it:

  (1) set the CodeMirror value
  (2) pretty print to source text
  (3) reveal the Editor when pretty printing is done
  (4) set the CodeMirror value
      => Layout

This maintains the fact that to undo pretty printing we can just "undo" the
editor to get the original text. This also means we only do a single
CodeMirror layout, with the pretty printed and therefore more manageable
source text for highlighting. It also means we continue to show a loading
indicator in the editor while we are pretty printing. If this is truely
done asynchronously, which is the case for _javascript_ with FormatterWorker,
then the loading indicator will animate smoothly.

This sequence also works with the traditional synchronous formatters,
which we still have for CSS.

* UserInterface/Views/ContentView.js:
(WebInspector.ContentView.contentViewForRepresentedObject):
* UserInterface/Views/NavigationSidebarPanel.js:
(WebInspector.NavigationSidebarPanel.prototype.showDefaultContentViewForTreeElement):
(WebInspector.NavigationSidebarPanel.prototype._checkElementsForPendingViewStateCookie):
BreakpointTreeElements can now be restored and reselected when its
source code is null. Avoid deleting the pending cookie data if a
ContentView was not shown for the resource. When the Breakpoint
and SourceCode get hooked up, this code will run again and work.

* UserInterface/Views/ScriptContentView.js:
(WebInspector.ScriptContentView.prototype._togglePrettyPrint):
* UserInterface/Views/TextResourceContentView.js:
(WebInspector.TextResourceContentView.prototype._togglePrettyPrint):
* UserInterface/Views/TextContentView.js:
(WebInspector.TextContentView.prototype._togglePrettyPrint):
New API for toggling formatting, now that it is an async operation.

* UserInterface/Views/SourceCodeTextEditor.js:
(WebInspector.SourceCodeTextEditor):
(WebInspector.SourceCodeTextEditor.prototype.toggleTypeAnnotations):
(WebInspector.SourceCodeTextEditor.prototype.prettyPrint):
(WebInspector.SourceCodeTextEditor.prototype._populateWithContent):
(WebInspector.SourceCodeTextEditor.prototype._proceedPopulateWithContent):
(WebInspector.SourceCodeTextEditor.prototype._prepareEditorForInitialContent):
(WebInspector.SourceCodeTextEditor.prototype._populateWithInlineScriptContent.scriptContentAvailable):
(WebInspector.SourceCodeTextEditor.prototype._populateWithInlineScriptContent):
(WebInspector.SourceCodeTextEditor.prototype._populateWithScriptContent):
(WebInspector.SourceCodeTextEditor.prototype.textEditorUpdatedFormatting):
(WebInspector.SourceCodeTextEditor.prototype._contentWillPopulate): Deleted.
Move auto formatting logic into SourceCodeTextEditor, because it
determines if content should be auto formatted, and it loads the
initial content so it can determine when to show the editor for
the first time.

When we get the initial content and determine we have to autoformat,
setup the TextEditor, but don't proceed with WillPopulate/DidPopulate
until after we have formatted text.

* UserInterface/Views/TextEditor.js:
(WebInspector.TextEditor):
(WebInspector.TextEditor.set string.update):
(WebInspector.TextEditor.prototype.set string):
(WebInspector.TextEditor.prototype.setFormatted):
(WebInspector.TextEditor.prototype.hasFormatter):
(WebInspector.TextEditor.prototype._format):
(WebInspector.TextEditor.prototype.prettyPrint):
(WebInspector.TextEditor.prototype._canUseFormatterWorker):
(WebInspector.TextEditor.prototype._startWorkerPrettyPrint):
(WebInspector.TextEditor.prototype._startCodeMirrorPrettyPrint):
(WebInspector.TextEditor.prototype._finishPrettyPrint):
(WebInspector.TextEditor.prototype._undoFormatting):
(WebInspector.TextEditor.prototype._updateAfterFormatting):
Break up the synchronous pretty printing code into multiple steps.
One path can be asynchronous formatting via FormatterWorker, another
path may be synchronous formatting using the CodeMirror formatters.

(WebInspector.TextEditor.prototype.set formatted): Deleted.
Remove the synchronous `set formatted` setter. Replace with setFormatted().

(WebInspector.TextEditor.prototype.set autoFormat): Deleted.
Remove the TextEditor's autoformat. Since formatting can be async, having
the TextEditor showing and asynchronously format its initial contents is
a recipe for poor performance causing multiple layouts of different content.
Instead, autoformatting is handled by SourceCodeTextEditor, and TextEditor
can then be shown when it has the right data.

Modified Paths

Diff

Modified: trunk/Source/WebInspectorUI/ChangeLog (199168 => 199169)


--- trunk/Source/WebInspectorUI/ChangeLog	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/ChangeLog	2016-04-07 19:29:38 UTC (rev 199169)
@@ -1,5 +1,117 @@
 2016-04-07  Joseph Pecoraro  <[email protected]>
 
+        Web Inspector: Inspector hangs when trying to view a large XHR resource
+        https://bugs.webkit.org/show_bug.cgi?id=144107
+        <rdar://problem/20669463>
+
+        Reviewed by Timothy Hatcher.
+
+        Previously auto formatting (initial pretty print of source code) in TextEditor
+        was done synchronously in this order:
+        
+          (1) revealing the Editor as soon as we have content
+          (2) set the CodeMirror value
+          (3) pretty print with the CodeMirror editor
+          (4) set the CodeMirror value
+              => Layout
+          
+        At the end, CodeMirror would layout once with the new content. This approach
+        performs very poorly when step (3) is an asynchronous action, because it would
+        mean CodeMirror would layout for both (2) and at the end (4) and the layout
+        itself can be very costly if the content is minified and so has very long
+        lines at the top of the file that need to be syntax highlighted and visible
+        since we do not wrap.
+
+        This patch changes the order of operations to benefit asynchronous formatting.
+        When SourceCodeTextEditor determines that it can autoformat it:
+
+          (1) set the CodeMirror value
+          (2) pretty print to source text
+          (3) reveal the Editor when pretty printing is done
+          (4) set the CodeMirror value
+              => Layout
+
+        This maintains the fact that to undo pretty printing we can just "undo" the
+        editor to get the original text. This also means we only do a single
+        CodeMirror layout, with the pretty printed and therefore more manageable
+        source text for highlighting. It also means we continue to show a loading
+        indicator in the editor while we are pretty printing. If this is truely
+        done asynchronously, which is the case for _javascript_ with FormatterWorker,
+        then the loading indicator will animate smoothly.
+
+        This sequence also works with the traditional synchronous formatters,
+        which we still have for CSS.
+
+        * UserInterface/Views/ContentView.js:
+        (WebInspector.ContentView.contentViewForRepresentedObject):
+        * UserInterface/Views/NavigationSidebarPanel.js:
+        (WebInspector.NavigationSidebarPanel.prototype.showDefaultContentViewForTreeElement):
+        (WebInspector.NavigationSidebarPanel.prototype._checkElementsForPendingViewStateCookie):
+        BreakpointTreeElements can now be restored and reselected when its
+        source code is null. Avoid deleting the pending cookie data if a
+        ContentView was not shown for the resource. When the Breakpoint
+        and SourceCode get hooked up, this code will run again and work.
+
+        * UserInterface/Views/ScriptContentView.js:
+        (WebInspector.ScriptContentView.prototype._togglePrettyPrint):
+        * UserInterface/Views/TextResourceContentView.js:
+        (WebInspector.TextResourceContentView.prototype._togglePrettyPrint):
+        * UserInterface/Views/TextContentView.js:
+        (WebInspector.TextContentView.prototype._togglePrettyPrint):
+        New API for toggling formatting, now that it is an async operation.
+
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WebInspector.SourceCodeTextEditor):
+        (WebInspector.SourceCodeTextEditor.prototype.toggleTypeAnnotations):
+        (WebInspector.SourceCodeTextEditor.prototype.prettyPrint):
+        (WebInspector.SourceCodeTextEditor.prototype._populateWithContent):
+        (WebInspector.SourceCodeTextEditor.prototype._proceedPopulateWithContent):
+        (WebInspector.SourceCodeTextEditor.prototype._prepareEditorForInitialContent):
+        (WebInspector.SourceCodeTextEditor.prototype._populateWithInlineScriptContent.scriptContentAvailable):
+        (WebInspector.SourceCodeTextEditor.prototype._populateWithInlineScriptContent):
+        (WebInspector.SourceCodeTextEditor.prototype._populateWithScriptContent):
+        (WebInspector.SourceCodeTextEditor.prototype.textEditorUpdatedFormatting):
+        (WebInspector.SourceCodeTextEditor.prototype._contentWillPopulate): Deleted.
+        Move auto formatting logic into SourceCodeTextEditor, because it
+        determines if content should be auto formatted, and it loads the
+        initial content so it can determine when to show the editor for
+        the first time.
+
+        When we get the initial content and determine we have to autoformat,
+        setup the TextEditor, but don't proceed with WillPopulate/DidPopulate
+        until after we have formatted text.
+
+        * UserInterface/Views/TextEditor.js:
+        (WebInspector.TextEditor):
+        (WebInspector.TextEditor.set string.update):
+        (WebInspector.TextEditor.prototype.set string):
+        (WebInspector.TextEditor.prototype.updateFormattedState):
+        (WebInspector.TextEditor.prototype.hasFormatter):
+        (WebInspector.TextEditor.prototype._format):
+        (WebInspector.TextEditor.prototype.prettyPrint):
+        (WebInspector.TextEditor.prototype._canUseFormatterWorker):
+        (WebInspector.TextEditor.prototype._startWorkerPrettyPrint):
+        (WebInspector.TextEditor.prototype._startCodeMirrorPrettyPrint):
+        (WebInspector.TextEditor.prototype._finishPrettyPrint):
+        (WebInspector.TextEditor.prototype._undoFormatting):
+        (WebInspector.TextEditor.prototype._updateAfterFormatting):
+        Break up the synchronous pretty printing code into multiple steps.
+        One path can be asynchronous formatting via FormatterWorker, another
+        path may be synchronous formatting using the CodeMirror formatters.
+
+        (WebInspector.TextEditor.prototype.set formatted): Deleted.
+        Remove the synchronous `set formatted` setter. Replace with
+        updateFormattedState().
+
+        (WebInspector.TextEditor.prototype.set autoFormat): Deleted.
+        Remove the TextEditor's autoformat. Since formatting can be async, having
+        the TextEditor showing and asynchronously format its initial contents is
+        a recipe for poor performance causing multiple layouts of different content.
+        Instead, autoformatting is handled by SourceCodeTextEditor, and TextEditor
+        can then be shown when it has the right data.
+
+2016-04-07  Joseph Pecoraro  <[email protected]>
+
         Web Inspector: Improve _javascript_ pretty printing
         https://bugs.webkit.org/show_bug.cgi?id=156178
         <rdar://problem/25535719>

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -153,7 +153,12 @@
     {
         console.assert(representedObject);
 
+        // Some represented objects attempt to resolve a better represented object.
+        // This may result in null, for example a Breakpoint which doesn't have a SourceCode.
         let resolvedRepresentedObject = WebInspector.ContentView.resolvedRepresentedObjectForRepresentedObject(representedObject);
+        if (!resolvedRepresentedObject)
+            return null;
+
         let existingContentView = resolvedRepresentedObject[WebInspector.ContentView.ContentViewForRepresentedObjectSymbol];
         console.assert(!existingContentView || existingContentView instanceof WebInspector.ContentView);
         if (existingContentView)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -202,10 +202,14 @@
         console.assert(treeElement);
         console.assert(treeElement.representedObject);
         if (!treeElement || !treeElement.representedObject)
-            return;
+            return false;
 
-        this.contentBrowser.showContentViewForRepresentedObject(treeElement.representedObject);
+        let contentView = this.contentBrowser.showContentViewForRepresentedObject(treeElement.representedObject);
+        if (!contentView)
+            return false;
+
         treeElement.revealAndSelect(true, false, true, true);
+        return true;
     }
 
     saveStateToCookie(cookie)
@@ -740,7 +744,9 @@
         }, this);
 
         if (matchedElement) {
-            this.showDefaultContentViewForTreeElement(matchedElement);
+            let didShowContentView = this.showDefaultContentViewForTreeElement(matchedElement);
+            if (!didShowContentView)
+                return;
 
             this._pendingViewStateCookie = null;
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -209,7 +209,7 @@
     _togglePrettyPrint(event)
     {
         var activated = !this._prettyPrintButtonNavigationItem.activated;
-        this._textEditor.formatted = activated;
+        this._textEditor.updateFormattedState(activated);
     }
 
     _toggleTypeAnnotations(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -40,11 +40,13 @@
         this._contentPopulated = false;
         this._invalidLineNumbers = {0: true};
         this._ignoreContentDidChange = 0;
+        this._requestingScriptContent = false;
 
         this._typeTokenScrollHandler = null;
         this._typeTokenAnnotator = null;
         this._basicBlockAnnotator = null;
 
+        this._autoFormat = false;
         this._isProbablyMinified = false;
 
         // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
@@ -260,8 +262,12 @@
             return false;
 
         var newActivatedState = !this._typeTokenAnnotator.isActive();
-        if (newActivatedState && this._isProbablyMinified && !this.formatted)
-            this.formatted = true;
+        if (newActivatedState && this._isProbablyMinified && !this.formatted) {
+            this.updateFormattedState(true).then(() => {
+                this._setTypeTokenAnnotatorEnabledState(newActivatedState);
+            });
+            return;
+        }
 
         this._setTypeTokenAnnotatorEnabledState(newActivatedState);
         return newActivatedState;
@@ -298,16 +304,16 @@
         if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator)
             this._setTypeTokenAnnotatorEnabledState(false);
 
-        super.prettyPrint(pretty);
-
-        if (pretty || !this._isProbablyMinified) {
-            if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator)
-                this._setTypeTokenAnnotatorEnabledState(true);
-        } else {
-            console.assert(!pretty && this._isProbablyMinified);
-            if (this._typeTokenAnnotator || this._basicBlockAnnotator)
-                this._setTypeTokenAnnotatorEnabledState(false);
-        }
+        return super.prettyPrint(pretty).then(() => {
+            if (pretty || !this._isProbablyMinified) {
+                if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator)
+                    this._setTypeTokenAnnotatorEnabledState(true);
+            } else {
+                console.assert(!pretty && this._isProbablyMinified);
+                if (this._typeTokenAnnotator || this._basicBlockAnnotator)
+                    this._setTypeTokenAnnotatorEnabledState(false);
+            }
+        });
     }
 
     // Private
@@ -358,40 +364,6 @@
             delete this._breakpointMap[lineInfo.lineNumber];
     }
 
-    _contentWillPopulate(content)
-    {
-        this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate);
-
-        // We only do the rest of this work before the first populate.
-        if (this._contentPopulated)
-            return;
-
-        if (this._supportsDebugging) {
-            this._breakpointMap = {};
-
-            var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
-            for (var i = 0; i < breakpoints.length; ++i) {
-                var breakpoint = breakpoints[i];
-                console.assert(this._matchesBreakpoint(breakpoint));
-                var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
-                this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
-                this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
-            }
-        }
-
-        if (this._sourceCode instanceof WebInspector.Resource)
-            this.mimeType = this._sourceCode.syntheticMIMEType;
-        else if (this._sourceCode instanceof WebInspector.Script)
-            this.mimeType = "text/_javascript_";
-
-        // Automatically format the content if it looks minified and it can be formatted.
-        console.assert(!this.formatted);
-        if (this.canBeFormatted() && this._isLikelyMinified(content)) {
-            this.autoFormat = true;
-            this._isProbablyMinified = true;
-        }
-    }
-
     _isLikelyMinified(content)
     {
         let whiteSpaceCount = 0;
@@ -412,6 +384,43 @@
         return ratio < 0.1;
     }
 
+    _populateWithContent(content)
+    {
+        content = content || "";
+
+        this._prepareEditorForInitialContent(content);
+
+        // If we can auto format, format the TextEditor before showing it.
+        if (this._autoFormat) {
+            console.assert(!this.formatted);
+            this._autoFormat = false;
+            this.string = content;
+            this.updateFormattedState(true).then(() => {
+                this._proceedPopulateWithContent(this.string);
+            });
+            return;
+        }
+
+        this._proceedPopulateWithContent(content);
+    }
+
+    _proceedPopulateWithContent(content)
+    {
+        this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate);
+
+        this.string = content;
+
+        this._makeTypeTokenAnnotator();
+        this._makeBasicBlockAnnotator();
+
+        if (WebInspector.showJavaScriptTypeInformationSetting.value) {
+            if (this._basicBlockAnnotator || this._typeTokenAnnotator)
+                this._setTypeTokenAnnotatorEnabledState(true);
+        }
+
+        this._contentDidPopulate();
+    }
+
     _contentDidPopulate()
     {
         this._contentPopulated = true;
@@ -427,22 +436,36 @@
         this._updateEditableMarkers();
     }
 
-    _populateWithContent(content)
+    _prepareEditorForInitialContent(content)
     {
-        content = content || "";
+        // Only do this work before the first populate.
+        if (this._contentPopulated)
+            return;
 
-        this._contentWillPopulate(content);
-        this.string = content;
+        if (this._supportsDebugging) {
+            this._breakpointMap = {};
 
-        this._makeTypeTokenAnnotator();
-        this._makeBasicBlockAnnotator();
+            var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
+            for (var i = 0; i < breakpoints.length; ++i) {
+                var breakpoint = breakpoints[i];
+                console.assert(this._matchesBreakpoint(breakpoint));
+                var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+                this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+                this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+            }
+        }
 
-        if (WebInspector.showJavaScriptTypeInformationSetting.value) {
-            if (this._basicBlockAnnotator || this._typeTokenAnnotator)
-                this._setTypeTokenAnnotatorEnabledState(true);
+        if (this._sourceCode instanceof WebInspector.Resource)
+            this.mimeType = this._sourceCode.syntheticMIMEType;
+        else if (this._sourceCode instanceof WebInspector.Script)
+            this.mimeType = "text/_javascript_";
+
+        // Decide to automatically format the content if it looks minified and it can be formatted.
+        console.assert(!this.formatted);
+        if (this.canBeFormatted() && this._isLikelyMinified(content)) {
+            this._autoFormat = true;
+            this._isProbablyMinified = true;
         }
-
-        this._contentDidPopulate();
     }
 
     _contentAvailable(parameters)
@@ -662,7 +685,7 @@
             if (--pendingRequestCount)
                 return;
 
-            delete this._requestingScriptContent;
+            this._requestingScriptContent = false;
 
             // Abort if the full content populated while waiting for these async callbacks.
             if (this._fullContentPopulated)
@@ -727,7 +750,7 @@
         function scriptContentAvailable(parameters)
         {
             var content = parameters.content;
-            delete this._requestingScriptContent;
+            this._requestingScriptContent = false;
 
             // Abort if the full content populated while waiting for this async callback.
             if (this._fullContentPopulated)
@@ -1137,7 +1160,7 @@
     {
         this._ignoreAllBreakpointLocationUpdates = true;
         this._sourceCode.formatterSourceMap = this.formatterSourceMap;
-        delete this._ignoreAllBreakpointLocationUpdates;
+        this._ignoreAllBreakpointLocationUpdates = false;
 
         // Always put the source map on both the Script and Resource if both exist. For example,
         // if this SourceCode is a Resource, then there might also be a Script. In the debugger,

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TextContentView.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TextContentView.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TextContentView.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -152,7 +152,7 @@
     _togglePrettyPrint(event)
     {
         var activated = !this._prettyPrintButtonNavigationItem.activated;
-        this._textEditor.formatted = activated;
+        this._textEditor.updateFormattedState(formatted);
     }
 
     _textEditorFormattingDidChange(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TextEditor.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TextEditor.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TextEditor.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -64,6 +64,7 @@
         this._ignoreCodeMirrorContentDidChangeEvent = 0;
 
         this._formatted = false;
+        this._formattingPromise = null;
         this._formatterSourceMap = null;
 
         this._delegate = delegate || null;
@@ -89,22 +90,20 @@
             if (this._initialStringNotSet)
                 this._codeMirror.removeLineClass(0, "wrap");
 
-            this._codeMirror.setValue(newString);
-            console.assert(this.string.length === newString.length, "A lot of our code depends on precise text offsets, so the string should remain the same.");
+            if (this._codeMirror.getValue() !== newString) {
+                this._codeMirror.setValue(newString);
+                console.assert(this.string.length === newString.length, "A lot of our code depends on precise text offsets, so the string should remain the same.");
+            } else {
+                // Ensure we at display content even if the value did not change. This often happens when auto formatting.
+                this.layout();
+            }
 
             if (this._initialStringNotSet) {
                 this._codeMirror.clearHistory();
                 this._codeMirror.markClean();
-                delete this._initialStringNotSet;
+                this._initialStringNotSet = false;
             }
 
-            // Automatically format the content.
-            if (this._autoFormat) {
-                console.assert(!this.formatted);
-                this.formatted = true;
-                delete this._autoFormat;
-            }
-
             // Update the execution line now that we might have content for that line.
             this._updateExecutionLine();
 
@@ -137,39 +136,15 @@
         return this._formatted;
     }
 
-    set formatted(formatted)
+    updateFormattedState(formatted)
     {
-        if (this._formatted === formatted)
-            return;
-
-        console.assert(!formatted || this.canBeFormatted());
-        if (formatted && !this.canBeFormatted())
-            return;
-
-        this._ignoreCodeMirrorContentDidChangeEvent++;
-        this.prettyPrint(formatted);
-        this._ignoreCodeMirrorContentDidChangeEvent--;
-        console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0);
-
-        this._formatted = formatted;
-
-        this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange);
+        return this._format(formatted).catch(handlePromiseException);
     }
 
-    set autoFormat(auto)
-    {
-        this._autoFormat = auto;
-    }
-
     hasFormatter()
     {
-        var supportedModes = {
-            "_javascript_": true,
-            "css": true,
-        };
-
-        var mode = this._codeMirror.getMode();
-        return mode.name in supportedModes;
+        let mode = this._codeMirror.getMode().name;
+        return mode === "_javascript_" || mode === "css";
     }
 
     canBeFormatted()
@@ -464,8 +439,9 @@
         // If we need to unformat, reveal the line after a wait.
         // Otherwise the line highlight doesn't work properly.
         if (this._formatted && forceUnformatted) {
-            this.formatted = false;
-            setTimeout(this.revealPosition.bind(this), 0, position, textRangeToSelect);
+            this.updateFormattedState(false).then(() => {
+                setTimeout(this.revealPosition.bind(this), 0, position, textRangeToSelect);
+            });
             return;
         }
 
@@ -721,101 +697,186 @@
             this._codeMirror.refresh();
     }
 
+    _format(formatted)
+    {
+        if (this._formatted === formatted)
+            return Promise.resolve(this._formatted);
+
+        console.assert(!formatted || this.canBeFormatted());
+        if (formatted && !this.canBeFormatted())
+            return Promise.resolve(this._formatted);
+
+        if (this._formattingPromise)
+            return this._formattingPromise;
+
+        this._ignoreCodeMirrorContentDidChangeEvent++;
+        this._formattingPromise = this.prettyPrint(formatted).then(() => {
+            this._ignoreCodeMirrorContentDidChangeEvent--;
+            console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0);
+
+            this._formattingPromise = null;
+
+            let originalFormatted = this._formatted;
+            this._formatted = !!this._formatterSourceMap;
+
+            if (this._formatted !== originalFormatted)
+                this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange);
+
+            return this._formatted;
+        });
+
+        return this._formattingPromise;
+    }
+
     prettyPrint(pretty)
     {
-        function prettyPrintAndUpdateEditor()
-        {
-            var start = {line: 0, ch: 0};
-            var end = {line: this._codeMirror.lineCount() - 1};
+        return new Promise((resolve, reject) => {
+            let beforePrettyPrintState = {
+                selectionAnchor: this._codeMirror.getCursor("anchor"),
+                selectionHead: this._codeMirror.getCursor("head"),
+            };
 
-            var oldSelectionAnchor = this._codeMirror.getCursor("anchor");
-            var oldSelectionHead = this._codeMirror.getCursor("head");
-            var newSelectionAnchor, newSelectionHead;
-            var newExecutionLocation = null;
+            if (!pretty)
+                this._undoFormatting(beforePrettyPrintState, resolve);
+            else if (this._canUseFormatterWorker())
+                this._startWorkerPrettyPrint(beforePrettyPrintState, resolve);
+            else
+                this._startCodeMirrorPrettyPrint(beforePrettyPrintState, resolve);
+        });
+    }
 
-            if (pretty) {
-                // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
-                var indentString = "    ";
-                var builder = new FormatterContentBuilder(indentString);
-                var formatter = new WebInspector.Formatter(this._codeMirror, builder);
-                formatter.format(start, end);
+    _canUseFormatterWorker()
+    {
+        return this._codeMirror.getMode().name === "_javascript_";
+    }
 
-                this._formatterSourceMap = WebInspector.FormatterSourceMap.fromSourceMapData(builder.sourceMapData);
+    _startWorkerPrettyPrint(beforePrettyPrintState, callback)
+    {
+        // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
+        let indentString = "    ";
+        let sourceText = this._codeMirror.getValue();
+        let includeSourceMapData = true;
 
-                this._codeMirror.setValue(builder.formattedContent);
+        let workerProxy = WebInspector.FormatterWorkerProxy.singleton();
+        workerProxy.formatJavaScript(sourceText, indentString, includeSourceMapData, ({formattedText, sourceMapData}) => {
+            // Handle if formatting failed, which is possible for invalid programs.
+            if (formattedText === null) {
+                callback();
+                return;
+            }
+            this._finishPrettyPrint(beforePrettyPrintState, formattedText, sourceMapData, callback);
+        });
+    }
 
-                if (this._positionToReveal) {
-                    var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
-                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
-                }
+    _startCodeMirrorPrettyPrint(beforePrettyPrintState, callback)
+    {
+        // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
+        let indentString = "    ";
+        let start = {line: 0, ch: 0};
+        let end = {line: this._codeMirror.lineCount() - 1};
+        let builder = new FormatterContentBuilder(indentString);
+        let formatter = new WebInspector.Formatter(this._codeMirror, builder);
+        formatter.format(start, end);
 
-                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);
-                }
+        let formattedText = builder.formattedContent;
+        let sourceMapData = builder.sourceMapData;
+        this._finishPrettyPrint(beforePrettyPrintState, formattedText, sourceMapData, callback);
+    }
 
-                if (!isNaN(this._executionLineNumber)) {
-                    console.assert(!isNaN(this._executionColumnNumber));
-                    newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
-                }
+    _finishPrettyPrint(beforePrettyPrintState, formattedText, sourceMapData, callback)
+    {
+        this._codeMirror.operation(() => {
+            this._formatterSourceMap = WebInspector.FormatterSourceMap.fromSourceMapData(sourceMapData);
+            this._codeMirror.setValue(formattedText);
+            this._updateAfterFormatting(true, beforePrettyPrintState);
+        });
 
-                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();
+        callback();
+    }
 
-                if (this._positionToReveal) {
-                    var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
-                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
-                }
+    _undoFormatting(beforePrettyPrintState, callback)
+    {
+        this._codeMirror.operation(() => {
+            this._codeMirror.undo();
+            this._updateAfterFormatting(false, beforePrettyPrintState);
+        });
 
-                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);
-                }
+        callback();
+    }
 
-                if (!isNaN(this._executionLineNumber)) {
-                    console.assert(!isNaN(this._executionColumnNumber));
-                    newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
-                }
+    _updateAfterFormatting(pretty, beforePrettyPrintState)
+    {
+        let oldSelectionAnchor = beforePrettyPrintState.selectionAnchor;
+        let oldSelectionHead = beforePrettyPrintState.selectionHead;        
+        let newSelectionAnchor, newSelectionHead;
+        let newExecutionLocation = null;
 
-                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};
+        if (pretty) {
+            if (this._positionToReveal) {
+                let newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
+                this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
+            }
 
-                this._formatterSourceMap = null;
+            if (this._textRangeToSelect) {
+                let mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
+                let mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
+                this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
             }
 
-            this._scrollIntoViewCentered(newSelectionAnchor);
-            this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
+            if (!isNaN(this._executionLineNumber)) {
+                console.assert(!isNaN(this._executionColumnNumber));
+                newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
+            }
 
-            if (newExecutionLocation) {
-                delete this._executionLineHandle;
-                this.executionColumnNumber = newExecutionLocation.columnNumber;
-                this.executionLineNumber = newExecutionLocation.lineNumber;
+            let mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch);
+            let mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch);
+            newSelectionAnchor = {line: mappedAnchorLocation.lineNumber, ch: mappedAnchorLocation.columnNumber};
+            newSelectionHead = {line: mappedHeadLocation.lineNumber, ch: mappedHeadLocation.columnNumber};
+        } else {
+            if (this._positionToReveal) {
+                let newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
+                this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
             }
 
-            // 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._textRangeToSelect) {
+                let mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
+                let mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
+                this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
             }
 
-            if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
-                this._delegate.textEditorUpdatedFormatting(this);
+            if (!isNaN(this._executionLineNumber)) {
+                console.assert(!isNaN(this._executionColumnNumber));
+                newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
+            }
+
+            let mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch);
+            let 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._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this));
+        this._scrollIntoViewCentered(newSelectionAnchor);
+        this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
+
+        if (newExecutionLocation) {
+            this._executionLineHandle = null;
+            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) {
+            let 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(() => { this.performSearch(searchQuery) }, 0);
+        }
+
+        if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
+            this._delegate.textEditorUpdatedFormatting(this);
     }
 
     // Private

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js (199168 => 199169)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js	2016-04-07 19:29:32 UTC (rev 199168)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js	2016-04-07 19:29:38 UTC (rev 199169)
@@ -203,7 +203,7 @@
     _togglePrettyPrint(event)
     {
         var activated = !this._prettyPrintButtonNavigationItem.activated;
-        this._textEditor.formatted = activated;
+        this._textEditor.updateFormattedState(activated);
     }
 
     _toggleTypeAnnotations(event)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to