Web Inspector: Show recordings in CanvasTabContentView
authorwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 24 Oct 2017 20:47:11 +0000 (20:47 +0000)
committerwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 24 Oct 2017 20:47:11 +0000 (20:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177606
<rdar://problem/34715819>

Reviewed by Brian Burg.

Original patch by Matt Baker <mattbaker@apple.com>.

This patch folds canvas recordings into the new Canvas tab, which now
supports showing both CanvasCollections and Recordings.

When viewing recordings, a back button is shown at the start of the
navigation bar, allowing the user to return to the overview. It has been
styled with a back arrow, to make its function as clear as possible.
Like other navigation path components, the item for the recording can
clicked to select another recording taken from the same canvas.

The recording action scrubber has been moved from the content browser's
navigation bar to a more prominent location in the recording content view.
Selecting a frame tree element in the navigation sidebar no longer selects
the last action for that frame. This was changed to prevent the scrubber
position from behaving non-monotonically when selecting actions in sequential
order.

While this patch retains support for importing recordings, the feature
is not polished. Currently it is not possible to return to an imported
recording after leaving closing the view. In the future we may want to
consider including a navigation sidebar panel for the overview, which
could list canvas recordings (and eventually shaders).

* .eslintrc:
Drive-by: remove duplicate key.
* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Base/Main.js:
(WI.contentLoaded):
(WI.tabContentViewClassForRepresentedObject):
* UserInterface/Main.html:
* UserInterface/Views/RecordingTabContentView.js: Removed.
Remove RecordingTabContentView.

* UserInterface/Images/Recording.svg:
New recording icon, used for tree elements and canvas preview UI.

* UserInterface/Models/Recording.js:
(WI.Recording):
(WI.Recording.prototype.get visualActionIndexes):
Visual action indexes should be computed by the recording.

* UserInterface/Views/CanvasContentView.js:
(WI.CanvasContentView):
(WI.CanvasContentView.prototype.refresh):
(WI.CanvasContentView.prototype.initialLayout):
(WI.CanvasContentView.prototype.attached):
(WI.CanvasContentView.prototype._recordingStopped):
(WI.CanvasContentView.prototype._handleRecordingSelectElementChange):
New UI for showing CanvasContentView as a CollectionView item. Includes
recordings button and select for choosing a recording to view.

* UserInterface/Views/CanvasOverviewContentView.css:
(.content-view.canvas-overview .content-view.canvas > header):
Switch to 13px, which is more frequently used.

(.content-view.canvas-overview .content-view.canvas > header .subtitle::before):
Switch to literal em dash. Surrounding spaces were ignored when using
the backslash-escaped character.

(.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording, .selected) > header > .navigation-bar):
(.content-view.canvas-overview .content-view.canvas > footer > .recordings):
(.content-view.canvas-overview .content-view.canvas > footer > .recordings::before):
(.content-view.canvas-overview .content-view.canvas > footer > .recordings > select):
(.content-view.canvas-overview .content-view.canvas > footer .recordings > select:focus):
(.content-view.canvas-overview .content-view.canvas > footer > .flexible-space):
(.popover-content > .tree-outline .item.recording > .icon):
(.popover-content > .tree-outline .item.recording:hover):
(.popover-content > .tree-outline .item.recording:hover > .icon):
(.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording) > header > .navigation-bar): Deleted.
(.content-view.canvas-overview .content-view.canvas > footer): Deleted.
New styles for the recording picker.

* UserInterface/Views/CanvasOverviewContentView.js:
(WI.CanvasOverviewContentView):
(WI.CanvasOverviewContentView.prototype.get selectionPathComponents):
(WI.CanvasOverviewContentView.prototype.contentViewAdded):
(WI.CanvasOverviewContentView.prototype.contentViewRemoved):
(WI.CanvasOverviewContentView.prototype._supplementalRepresentedObjectsDidChange):
(WI.CanvasOverviewContentView.prototype._selectedPathComponentChanged): Deleted.
(WI.CanvasOverviewContentView.prototype._supplementalRepresentedObjectsDidChange.createCanvasPathComponent): Deleted.
Canvas tree elements are now managed by CanvasTabContentView, which is
now responsible for maintaining the tree of canvas objects. For now the
tree holds canvases and recordings, but will eventually include shader programs.

* UserInterface/Views/CanvasTabContentView.css:
(.content-view.tab.canvas .navigation-bar > .item .recording > .icon):
(.content-view.tab.canvas .navigation-bar > .item > .canvas-overview > .icon): Deleted.
Add styles for recording path components.

* UserInterface/Views/CanvasTabContentView.js:
(WI.CanvasTabContentView):
(WI.CanvasTabContentView.prototype.treeElementForRepresentedObject):
(WI.CanvasTabContentView.prototype.canShowRepresentedObject):
(WI.CanvasTabContentView.prototype.showRepresentedObject):
(WI.CanvasTabContentView.prototype.shown):
(WI.CanvasTabContentView.prototype.restoreStateFromCookie):
(WI.CanvasTabContentView.prototype.attached):
(WI.CanvasTabContentView.prototype.detached):
(WI.CanvasTabContentView.prototype._canvasAdded):
(WI.CanvasTabContentView.prototype._canvasRemoved):
(WI.CanvasTabContentView.prototype._canvasTreeOutlineSelectionDidChange):
(WI.CanvasTabContentView.prototype._recordingStopped):
(WI.CanvasTabContentView.prototype._navigationSidebarImport):
(WI.CanvasTabContentView.prototype._navigationSidebarTreeOutlineSelectionChanged):
(WI.CanvasTabContentView.prototype._recordingAdded):
(WI.CanvasTabContentView.prototype._recordingActionIndexChanged):
(WI.CanvasTabContentView.prototype._updateActionIndex):
(WI.CanvasTabContentView.prototype.restoreFromCookie): Deleted.
(WI.CanvasTabContentView.prototype._overviewPathComponentClicked): Deleted.

* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):

* UserInterface/Views/RecordingActionTreeElement.css:
(body:not(.window-inactive, .window-docked-inactive) .tree-outline:matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,):
(body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,): Deleted.

* UserInterface/Views/RecordingContentView.css:
(.content-view:not(.tab).recording):
(.content-view:not(.tab).recording > header):
(.content-view:not(.tab).recording > header > .slider-container):
(.content-view:not(.tab).recording > header > .slider-container > input[type=range]):
(.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-runnable-track):
(.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-thumb):
(.content-view:not(.tab).recording > .preview-container):

* UserInterface/Views/RecordingContentView.js:
(WI.RecordingContentView):
(WI.RecordingContentView.prototype.updateActionIndex):
(WI.RecordingContentView.prototype.initialLayout):
(WI.RecordingContentView.prototype.async._generateContentCanvas2D):
(WI.RecordingContentView.prototype._updateSliderValue):
(WI.RecordingContentView.prototype._sliderChanged):
(WI.RecordingContentView.prototype.get supplementalRepresentedObjects): Deleted.

* UserInterface/Views/RecordingNavigationSidebarPanel.js:
(WI.RecordingNavigationSidebarPanel.prototype.set recording):
(WI.RecordingNavigationSidebarPanel.prototype.updateActionIndex):

* UserInterface/Views/ResourceIcons.css:
(.canvas .icon):
(.canvas.canvas-2d .icon): Deleted.
(.canvas:matches(.webgl, .webgl2, .webgpu) .icon): Deleted.

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

19 files changed:
Source/WebInspectorUI/.eslintrc
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Images/Recording.svg
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/Recording.js
Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js
Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css
Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js
Source/WebInspectorUI/UserInterface/Views/CanvasTabContentView.css
Source/WebInspectorUI/UserInterface/Views/CanvasTabContentView.js
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.css
Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css
Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js
Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/RecordingTabContentView.js [deleted file]
Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css

index 891f341..74f71e0 100644 (file)
@@ -78,7 +78,6 @@
         "SyncTestSuite": true,
         "TestHarness": true,
         "TestSuite": true,
-        "WI": true,
 
         // Externals
         "CodeMirror": true,
index 951df7a..2515f9d 100644 (file)
@@ -1,3 +1,157 @@
+2017-10-24  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Show recordings in CanvasTabContentView
+        https://bugs.webkit.org/show_bug.cgi?id=177606
+        <rdar://problem/34715819>
+
+        Reviewed by Brian Burg.
+
+        Original patch by Matt Baker <mattbaker@apple.com>.
+
+        This patch folds canvas recordings into the new Canvas tab, which now
+        supports showing both CanvasCollections and Recordings.
+
+        When viewing recordings, a back button is shown at the start of the
+        navigation bar, allowing the user to return to the overview. It has been
+        styled with a back arrow, to make its function as clear as possible.
+        Like other navigation path components, the item for the recording can
+        clicked to select another recording taken from the same canvas.
+
+        The recording action scrubber has been moved from the content browser's
+        navigation bar to a more prominent location in the recording content view.
+        Selecting a frame tree element in the navigation sidebar no longer selects
+        the last action for that frame. This was changed to prevent the scrubber
+        position from behaving non-monotonically when selecting actions in sequential
+        order.
+
+        While this patch retains support for importing recordings, the feature
+        is not polished. Currently it is not possible to return to an imported
+        recording after leaving closing the view. In the future we may want to
+        consider including a navigation sidebar panel for the overview, which
+        could list canvas recordings (and eventually shaders).
+
+        * .eslintrc:
+        Drive-by: remove duplicate key.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Base/Main.js:
+        (WI.contentLoaded):
+        (WI.tabContentViewClassForRepresentedObject):
+        * UserInterface/Main.html:
+        * UserInterface/Views/RecordingTabContentView.js: Removed.
+        Remove RecordingTabContentView.
+
+        * UserInterface/Images/Recording.svg:
+        New recording icon, used for tree elements and canvas preview UI.
+
+        * UserInterface/Models/Recording.js:
+        (WI.Recording):
+        (WI.Recording.prototype.get visualActionIndexes):
+        Visual action indexes should be computed by the recording.
+
+        * UserInterface/Views/CanvasContentView.js:
+        (WI.CanvasContentView):
+        (WI.CanvasContentView.prototype.refresh):
+        (WI.CanvasContentView.prototype.initialLayout):
+        (WI.CanvasContentView.prototype.attached):
+        (WI.CanvasContentView.prototype._recordingStopped):
+        (WI.CanvasContentView.prototype._handleRecordingSelectElementChange):
+        New UI for showing CanvasContentView as a CollectionView item. Includes
+        recordings button and select for choosing a recording to view.
+
+        * UserInterface/Views/CanvasOverviewContentView.css:
+        (.content-view.canvas-overview .content-view.canvas > header):
+        Switch to 13px, which is more frequently used.
+
+        (.content-view.canvas-overview .content-view.canvas > header .subtitle::before):
+        Switch to literal em dash. Surrounding spaces were ignored when using
+        the backslash-escaped character.
+
+        (.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording, .selected) > header > .navigation-bar):
+        (.content-view.canvas-overview .content-view.canvas > footer > .recordings):
+        (.content-view.canvas-overview .content-view.canvas > footer > .recordings::before):
+        (.content-view.canvas-overview .content-view.canvas > footer > .recordings > select):
+        (.content-view.canvas-overview .content-view.canvas > footer .recordings > select:focus):
+        (.content-view.canvas-overview .content-view.canvas > footer > .flexible-space):
+        (.popover-content > .tree-outline .item.recording > .icon):
+        (.popover-content > .tree-outline .item.recording:hover):
+        (.popover-content > .tree-outline .item.recording:hover > .icon):
+        (.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording) > header > .navigation-bar): Deleted.
+        (.content-view.canvas-overview .content-view.canvas > footer): Deleted.
+        New styles for the recording picker.
+
+        * UserInterface/Views/CanvasOverviewContentView.js:
+        (WI.CanvasOverviewContentView):
+        (WI.CanvasOverviewContentView.prototype.get selectionPathComponents):
+        (WI.CanvasOverviewContentView.prototype.contentViewAdded):
+        (WI.CanvasOverviewContentView.prototype.contentViewRemoved):
+        (WI.CanvasOverviewContentView.prototype._supplementalRepresentedObjectsDidChange):
+        (WI.CanvasOverviewContentView.prototype._selectedPathComponentChanged): Deleted.
+        (WI.CanvasOverviewContentView.prototype._supplementalRepresentedObjectsDidChange.createCanvasPathComponent): Deleted.
+        Canvas tree elements are now managed by CanvasTabContentView, which is
+        now responsible for maintaining the tree of canvas objects. For now the
+        tree holds canvases and recordings, but will eventually include shader programs.
+
+        * UserInterface/Views/CanvasTabContentView.css:
+        (.content-view.tab.canvas .navigation-bar > .item .recording > .icon):
+        (.content-view.tab.canvas .navigation-bar > .item > .canvas-overview > .icon): Deleted.
+        Add styles for recording path components.
+
+        * UserInterface/Views/CanvasTabContentView.js:
+        (WI.CanvasTabContentView):
+        (WI.CanvasTabContentView.prototype.treeElementForRepresentedObject):
+        (WI.CanvasTabContentView.prototype.canShowRepresentedObject):
+        (WI.CanvasTabContentView.prototype.showRepresentedObject):
+        (WI.CanvasTabContentView.prototype.shown):
+        (WI.CanvasTabContentView.prototype.restoreStateFromCookie):
+        (WI.CanvasTabContentView.prototype.attached):
+        (WI.CanvasTabContentView.prototype.detached):
+        (WI.CanvasTabContentView.prototype._canvasAdded):
+        (WI.CanvasTabContentView.prototype._canvasRemoved):
+        (WI.CanvasTabContentView.prototype._canvasTreeOutlineSelectionDidChange):
+        (WI.CanvasTabContentView.prototype._recordingStopped):
+        (WI.CanvasTabContentView.prototype._navigationSidebarImport):
+        (WI.CanvasTabContentView.prototype._navigationSidebarTreeOutlineSelectionChanged):
+        (WI.CanvasTabContentView.prototype._recordingAdded):
+        (WI.CanvasTabContentView.prototype._recordingActionIndexChanged):
+        (WI.CanvasTabContentView.prototype._updateActionIndex):
+        (WI.CanvasTabContentView.prototype.restoreFromCookie): Deleted.
+        (WI.CanvasTabContentView.prototype._overviewPathComponentClicked): Deleted.
+
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+
+        * UserInterface/Views/RecordingActionTreeElement.css:
+        (body:not(.window-inactive, .window-docked-inactive) .tree-outline:matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,):
+        (body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,): Deleted.
+
+        * UserInterface/Views/RecordingContentView.css:
+        (.content-view:not(.tab).recording):
+        (.content-view:not(.tab).recording > header):
+        (.content-view:not(.tab).recording > header > .slider-container):
+        (.content-view:not(.tab).recording > header > .slider-container > input[type=range]):
+        (.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-runnable-track):
+        (.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-thumb):
+        (.content-view:not(.tab).recording > .preview-container):
+
+        * UserInterface/Views/RecordingContentView.js:
+        (WI.RecordingContentView):
+        (WI.RecordingContentView.prototype.updateActionIndex):
+        (WI.RecordingContentView.prototype.initialLayout):
+        (WI.RecordingContentView.prototype.async._generateContentCanvas2D):
+        (WI.RecordingContentView.prototype._updateSliderValue):
+        (WI.RecordingContentView.prototype._sliderChanged):
+        (WI.RecordingContentView.prototype.get supplementalRepresentedObjects): Deleted.
+
+        * UserInterface/Views/RecordingNavigationSidebarPanel.js:
+        (WI.RecordingNavigationSidebarPanel.prototype.set recording):
+        (WI.RecordingNavigationSidebarPanel.prototype.updateActionIndex):
+
+        * UserInterface/Views/ResourceIcons.css:
+        (.canvas .icon):
+        (.canvas.canvas-2d .icon): Deleted.
+        (.canvas:matches(.webgl, .webgl2, .webgpu) .icon): Deleted.
+
 2017-10-24  Ross Kirsling  <ross.kirsling@sony.com>
 
         Web Inspector: Layer mutations should be purely based on layerId, not based on nodeId
index 52c98f1..7a4e362 100644 (file)
@@ -31,6 +31,7 @@ localizedStrings["%d \xd7 %d pixels"] = "%d \xd7 %d pixels";
 localizedStrings["%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)"] = "%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)";
 localizedStrings["%d fps"] = "%d fps";
 localizedStrings["%d matches"] = "%d matches";
+localizedStrings["%d of %d"] = "%d of %d";
 localizedStrings["%dpx"] = "%dpx";
 localizedStrings["%dpx²"] = "%dpx²";
 localizedStrings["%s (%s)"] = "%s (%s)";
@@ -393,6 +394,7 @@ localizedStrings["Expanded"] = "Expanded";
 localizedStrings["Experimental"] = "Experimental";
 localizedStrings["Expires"] = "Expires";
 localizedStrings["Export"] = "Export";
+localizedStrings["Export HAR"] = "Export HAR";
 localizedStrings["Expression"] = "Expression";
 localizedStrings["Extension Scripts"] = "Extension Scripts";
 localizedStrings["Extra Scripts"] = "Extra Scripts";
@@ -986,6 +988,7 @@ localizedStrings["Version"] = "Version";
 localizedStrings["Vertex"] = "Vertex";
 localizedStrings["Vertex Shader"] = "Vertex Shader";
 localizedStrings["Vertical"] = "Vertical";
+localizedStrings["View Recordings... (%d)"] = "View Recordings... (%d)";
 localizedStrings["View variable value"] = "View variable value";
 localizedStrings["Visibility"] = "Visibility";
 localizedStrings["Visible"] = "Visible";
index c16e70a..2f87db3 100644 (file)
@@ -433,7 +433,6 @@ WI.contentLoaded = function()
         WI.LayersTabContentView,
         WI.NetworkTabContentView,
         WI.NewTabContentView,
-        WI.RecordingTabContentView,
         WI.ResourcesTabContentView,
         WI.SearchTabContentView,
         WI.SettingsTabContentView,
@@ -1102,7 +1101,7 @@ WI.tabContentViewClassForRepresentedObject = function(representedObject)
         return WI.CanvasTabContentView;
 
     if (representedObject instanceof WI.Recording)
-        return WI.RecordingTabContentView;
+        return WI.CanvasTabContentView;
 
     return null;
 };
index 8b6cdea..0f32a3d 100644 (file)
@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright © 2017 Apple Inc. All rights reserved. -->
 <svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
-    <path fill="currentColor" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
-    <rect fill="currentColor" x="4.25" y="4.5" width="7.5" height="1"/>
-    <rect fill="currentColor" x="4.25" y="6.5" width="7.5" height="1"/>
-    <rect fill="currentColor" x="4.25" y="8.5" width="7.5" height="1"/>
-    <rect fill="currentColor" x="4.25" y="10.5" width="7.5" height="1"/>
+    <path d="M 6.5 8 A 1.5 1.5 0 1 1 5 6.5 1.5 1.5 0 0 1 6.5 8 Z M 5 9.5 h 6 m 0 -3 A 1.5 1.5 0 1 0 12.5 8 1.5 1.5 0 0 0 11 6.5 Z" fill="none" stroke="currentColor"/>
+    <path d="M1.5 3.729 v 8.542 a 1.127 1.127 0 0 0 1 1.22 h 11 a 1.127 1.127 0 0 0 1 -1.22 V 3.729 a 1.127 1.127 0 0 0 -1 -1.22 H 2.5 A 1.127 1.127 0 0 0 1.5 3.729 Z" fill="none" stroke="currentColor"/>
 </svg>
index e4fb40d..4cf802c 100644 (file)
     <script src="Views/DebuggerTabContentView.js"></script>
     <script src="Views/ElementsTabContentView.js"></script>
     <script src="Views/LayersTabContentView.js"></script>
-    <script src="Views/RecordingTabContentView.js"></script>
     <script src="Views/ResourceTreeElement.js"></script>
     <script src="Views/ResourcesTabContentView.js"></script>
     <script src="Views/SearchTabContentView.js"></script>
index 74aac12..b693c1b 100644 (file)
@@ -35,13 +35,14 @@ WI.Recording = class Recording
         this._displayName = WI.UIString("Recording");
 
         this._swizzle = [];
+        this._visualActionIndexes = [];
         this._source = null;
 
         let actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions));
         this._actions = Promise.all(actions.map((action) => action.swizzle(this))).then(() => {
-            for (let action of actions) {
+            actions.forEach((action, index) => {
                 if (!action.valid)
-                    continue;
+                    return;
 
                 let prototype = null;
                 if (this._type === WI.Recording.Type.Canvas2D)
@@ -58,7 +59,10 @@ WI.Recording = class Recording
                         WI.Recording.synthesizeError(WI.UIString("“%s” is invalid.").format(this._name));
                     }
                 }
-            }
+
+                if (action.isVisual)
+                    this._visualActionIndexes.push(index);
+            });
 
             return actions;
         });
@@ -169,6 +173,7 @@ WI.Recording = class Recording
     get initialState() { return this._initialState; }
     get frames() { return this._frames; }
     get data() { return this._data; }
+    get visualActionIndexes() { return this._visualActionIndexes; }
 
     get actions() { return this._actions; }
 
index f9222ad..2c6b402 100644 (file)
@@ -41,6 +41,7 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
         this._pixelSize = null;
         this._pixelSizeElement = null;
         this._canvasNode = null;
+        this._recordingOptionElementMap = new WeakMap;
 
         if (this.representedObject.contextType === WI.Canvas.ContextType.Canvas2D || this.representedObject.contextType === WI.Canvas.ContextType.WebGL) {
             const toolTip = WI.UIString("Start recording canvas actions. Shift-click to record a single frame.");
@@ -76,6 +77,11 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
 
         this.representedObject.requestContent().then((content) => {
             this._pendingContent = content;
+            if (!this._pendingContent) {
+                console.error("Canvas content not available.", this.representedObject);
+                return;
+            }
+
             this.needsLayout();
         })
         .catch(() => {
@@ -102,10 +108,9 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
         subtitle.textContent = WI.Canvas.displayNameForContextType(this.representedObject.contextType);
 
         let navigationBar = new WI.NavigationBar;
-        if (this._recordButtonNavigationItem) {
+        if (this._recordButtonNavigationItem)
             navigationBar.addNavigationItem(this._recordButtonNavigationItem);
-            navigationBar.addNavigationItem(new WI.DividerNavigationItem);
-        }
+
         navigationBar.addNavigationItem(this._refreshButtonNavigationItem);
 
         header.append(navigationBar.element);
@@ -114,6 +119,18 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
         this._previewContainerElement.className = "preview";
 
         let footer = this.element.appendChild(document.createElement("footer"));
+
+        this._recordingSelectContainer = footer.appendChild(document.createElement("div"));
+        this._recordingSelectContainer.classList.add("recordings", "hidden");
+
+        this._recordingSelectText = this._recordingSelectContainer.appendChild(document.createElement("span"));
+
+        this._recordingSelectElement = this._recordingSelectContainer.appendChild(document.createElement("select"));
+        this._recordingSelectElement.addEventListener("change", this._handleRecordingSelectElementChange.bind(this));
+
+        let flexibleSpaceElement = footer.appendChild(document.createElement("div"));
+        flexibleSpaceElement.className = "flexible-space";
+
         let metrics = footer.appendChild(document.createElement("div"));
         this._pixelSizeElement = metrics.appendChild(document.createElement("span"));
         this._pixelSizeElement.className = "pixel-size";
@@ -179,6 +196,9 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
         WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._recordingStopped, this);
 
         WI.settings.showImageGrid.addEventListener(WI.Setting.Event.Changed, this._updateImageGrid, this);
+
+        if (this.didInitialLayout)
+            this._recordingSelectElement.selectedIndex = -1;
     }
 
     detached()
@@ -229,17 +249,41 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
     {
         this._updateRecordNavigationItem();
 
-        if (event.data.canvas !== this.representedObject)
+        let {canvas, recording} = event.data;
+        if (canvas !== this.representedObject || !recording)
             return;
 
-        if (!event.data.recording) {
-            console.error("Missing recording.");
-            return;
-        }
+        const subtitle = null;
+        let recordingTreeElement = new WI.GeneralTreeElement(["recording"], recording.displayName, subtitle, recording);
+        recordingTreeElement.tooltip = ""; // Tree element tooltips aren't needed in a popover.
+
+        let optionElement = this._recordingSelectElement.appendChild(document.createElement("option"));
+        optionElement.textContent = recording.displayName;
+
+        this._recordingOptionElementMap.set(optionElement, recording);
+
+        let recordingCount = this._recordingSelectElement.options.length;
+        this._recordingSelectText.textContent = WI.UIString("View Recordings... (%d)").format(recordingCount);
+        this._recordingSelectContainer.classList.remove("hidden");
 
         WI.showRepresentedObject(event.data.recording);
     }
 
+    _handleRecordingSelectElementChange(event)
+    {
+        let selectedOption = this._recordingSelectElement.options[this._recordingSelectElement.selectedIndex];
+        console.assert(selectedOption, "An option should have been selected.");
+        if (!selectedOption)
+            return;
+
+        let representedObject = this._recordingOptionElementMap.get(selectedOption);
+        console.assert(representedObject, "Missing map entry for option.");
+        if (!representedObject)
+            return;
+
+        WI.showRepresentedObject(representedObject);
+    }
+
     _refreshPixelSize()
     {
         this._pixelSize = null;
index bf1c14c..43e257c 100644 (file)
@@ -52,7 +52,7 @@
 }
 
 .content-view.canvas-overview .content-view.canvas > header {
-    font-size: 14px;
+    font-size: 13px;
 }
 
 .content-view.canvas-overview .content-view.canvas.is-recording > header {
@@ -74,7 +74,7 @@
 }
 
 .content-view.canvas-overview .content-view.canvas > header .subtitle::before {
-    content: " \2014 ";
+    content: "  ";
 }
 
 .content-view.canvas-overview .content-view.canvas.is-recording > header > .titles > .title {
@@ -94,7 +94,7 @@
     border: none;
 }
 
-.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording) > header > .navigation-bar {
+.content-view.canvas-overview .content-view.canvas:not(:hover, .is-recording, .selected) > header > .navigation-bar {
     visibility: hidden;
 }
 
     position: static;
 }
 
-.content-view.canvas-overview .content-view.canvas > footer {
-    /* FIXME: this can be removed once <https://webkit.org/b/177606> is complete.*/
-    justify-content: flex-end;
+.content-view.canvas-overview .content-view.canvas > footer > .recordings {
+    position: absolute;
+    display: flex;
+    align-items: center;
+}
+
+.content-view.canvas-overview .content-view.canvas > footer > .recordings::before {
+    display: inline-block;
+    width: 16px;
+    margin-top: 3px;
+    -webkit-padding-end: 4px;
+    content: url(../Images/Recording.svg);
+}
+
+.content-view.canvas-overview .content-view.canvas > footer > .recordings > select {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    margin: 0;
+    padding: 0;
+    border: none;
+    opacity: 0;
+    -webkit-appearance: none;
+}
+
+.content-view.canvas-overview .content-view.canvas > footer .recordings > select:focus {
+    -webkit-margin-start: 11px;
+}
+
+.content-view.canvas-overview .content-view.canvas > footer > .flexible-space {
+    flex: 1;
 }
 
 .content-view.canvas-overview .content-view.canvas > footer .memory-cost {
     -webkit-padding-start: 4px;
 }
+
+.popover-content > .tree-outline .item.recording > .icon {
+    content: url(../Images/Recording.svg);
+}
+
+.popover-content > .tree-outline .item.recording:hover {
+    color: var(--selected-foreground-color);
+    background-color: var(--selected-background-color);
+    border-radius: 3px;
+}
+
+.popover-content > .tree-outline .item.recording:hover > .icon {
+    filter: invert();
+}
index 728c409..9e037e8 100644 (file)
@@ -33,8 +33,6 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
 
         this.element.classList.add("canvas-overview");
 
-        this._canvasTreeOutline = new WI.TreeOutline;
-
         this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("refresh-all", WI.UIString("Refresh all"), "Images/ReloadFull.svg", 13, 13);
         this._refreshButtonNavigationItem.disabled = true;
         this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshPreviews, this);
@@ -44,8 +42,6 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
         this._showGridButtonNavigationItem.disabled = true;
         this._showGridButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._showGridButtonClicked, this);
 
-        this._selectedCanvasPathComponent = null;
-
         this.selectionEnabled = true;
     }
 
@@ -59,14 +55,18 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
     get selectionPathComponents()
     {
         let components = [];
-        let canvas = this.supplementalRepresentedObjects[0];
-        if (canvas) {
-            let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
-            console.assert(treeElement);
-            if (treeElement) {
-                let pathComponent = new WI.GeneralTreeElementPathComponent(treeElement, canvas);
-                pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._selectedPathComponentChanged, this);
-                components.push(pathComponent);
+
+        if (this.supplementalRepresentedObjects.length) {
+            let [canvas] = this.supplementalRepresentedObjects;
+            let tabContentView = WI.tabBrowser.selectedTabContentView;
+            if (tabContentView) {
+                let treeElement = tabContentView.treeElementForRepresentedObject(canvas);
+                console.assert(treeElement);
+                if (treeElement) {
+                    let pathComponent = new WI.GeneralTreeElementPathComponent(treeElement);
+                    pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._selectionPathComponentsChanged, this);
+                    components.push(pathComponent);
+                }
             }
         }
 
@@ -84,29 +84,12 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
 
     contentViewAdded(contentView)
     {
-        let canvas = contentView.representedObject;
-        let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
-        console.assert(!treeElement, "Already added tree element for canvas.", canvas);
-        if (treeElement)
-            return;
-
-        treeElement = new WI.CanvasTreeElement(canvas);
-        this._canvasTreeOutline.appendChild(treeElement);
-
         contentView.element.addEventListener("mouseenter", this._contentViewMouseEnter);
         contentView.element.addEventListener("mouseleave", this._contentViewMouseLeave);
     }
 
     contentViewRemoved(contentView)
     {
-        let canvas = contentView.representedObject;
-        let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
-        console.assert(treeElement, "Missing tree element for canvas.", canvas);
-        if (!treeElement)
-            return;
-
-        this._canvasTreeOutline.removeChild(treeElement);
-
         contentView.element.removeEventListener("mouseenter", this._contentViewMouseEnter);
         contentView.element.removeEventListener("mouseleave", this._contentViewMouseLeave);
     }
@@ -137,7 +120,7 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
             canvasContentView.refresh();
     }
 
-    _selectedPathComponentChanged(event)
+    _selectionPathComponentsChanged(event)
     {
         let pathComponent = event.data.pathComponent;
         if (pathComponent.representedObject instanceof WI.Canvas)
@@ -151,16 +134,6 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec
 
     _supplementalRepresentedObjectsDidChange()
     {
-        function createCanvasPathComponent(canvas) {
-            return new WI.HierarchicalPathComponent(canvas.displayName, "canvas", canvas);
-        }
-
-        let currentCanvas = this.supplementalRepresentedObjects[0];
-        if (currentCanvas)
-            this._selectedCanvasPathComponent = createCanvasPathComponent(currentCanvas);
-        else
-            this._selectedCanvasPathComponent = null;
-
         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
     }
 
index cb04513..0233ab9 100644 (file)
     opacity: 0.7;
 }
 
-.content-view.tab.canvas .navigation-bar > .item > .canvas-overview > .icon {
-    content: url(../Images/CanvasOverview.svg);
-}
-
 /* FIXME: this can be removed once <https://webkit.org/b/177606> is complete. */
 .content-view.tab.canvas .navigation-bar > .item .canvas .icon {
     content: url(../Images/Canvas.svg);
 }
+
+.content-view.tab.canvas .navigation-bar > .item .recording > .icon {
+    content: url(../Images/Recording.svg);
+}
index dd794b7..dc3b446 100644 (file)
@@ -32,16 +32,30 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
         let {image, title} = WI.CanvasTabContentView.tabInfo();
         let tabBarItem = new WI.GeneralTabBarItem(image, title);
 
-        const navigationSidebarPanelConstructor = null;
+        const navigationSidebarPanelConstructor = WI.RecordingNavigationSidebarPanel;
         const detailsSidebarPanelConstructors = [WI.RecordingStateDetailsSidebarPanel, WI.RecordingTraceDetailsSidebarPanel, WI.CanvasDetailsSidebarPanel];
         const disableBackForward = true;
         super("canvas", ["canvas"], tabBarItem, navigationSidebarPanelConstructor, detailsSidebarPanelConstructors, disableBackForward);
 
-        this._overviewPathComponent = new WI.HierarchicalPathComponent(WI.UIString("Canvas Overview"), "canvas-overview");
-        this._overviewPathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._overviewPathComponentClicked, this);
-
         this._canvasCollection = null;
-        this._canvasOverviewContentView = null;
+
+        this._canvasTreeOutline = new WI.TreeOutline;
+        this._canvasTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._canvasTreeOutlineSelectionDidChange, this);
+
+        const leftArrow = "Images/BackForwardArrows.svg#left-arrow-mask";
+        const rightArrow = "Images/BackForwardArrows.svg#right-arrow-mask";
+        let isRTL = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL;
+        let backButtonImage = isRTL ? rightArrow : leftArrow;
+        this._overviewNavigationItem = new WI.ButtonNavigationItem("canvas-overview", WI.UIString("Canvas Overview"), backButtonImage, 8, 13);
+        this._overviewNavigationItem.hidden = true;
+        this._overviewNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High;
+        this._overviewNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this.showRepresentedObject(this._canvasCollection); });
+
+        this.contentBrowser.navigationBar.insertNavigationItem(this._overviewNavigationItem, 2);
+        this.contentBrowser.navigationBar.insertNavigationItem(new WI.DividerNavigationItem, 3);
+
+        this.navigationSidebarPanel.addEventListener(WI.RecordingNavigationSidebarPanel.Event.Import, this._navigationSidebarImport, this);
+        this.navigationSidebarPanel.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._navigationSidebarTreeOutlineSelectionChanged, this);
     }
 
     static tabInfo()
@@ -60,6 +74,11 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
 
     // Public
 
+    treeElementForRepresentedObject(representedObject)
+    {
+        return this._canvasTreeOutline.findTreeElement(representedObject);
+    }
+
     get type()
     {
         return WI.CanvasTabContentView.Type;
@@ -72,31 +91,42 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
 
     canShowRepresentedObject(representedObject)
     {
-        return representedObject instanceof WI.CanvasCollection;
+        return representedObject instanceof WI.CanvasCollection || representedObject instanceof WI.Recording;
     }
 
-    shown()
+    showRepresentedObject(representedObject, cookie)
     {
-        super.shown();
+        super.showRepresentedObject(representedObject, cookie);
+
+        this.navigationSidebarPanel.recording = null;
+
+        if (representedObject instanceof WI.CanvasCollection) {
+            this._overviewNavigationItem.hidden = true;
+            WI.navigationSidebar.collapsed = true;
+            return;
+        }
 
-        if (this.contentBrowser.currentContentView)
+        if (representedObject instanceof WI.Recording) {
+            this._overviewNavigationItem.hidden = false;
+            representedObject.actions.then((actions) => {
+                this.navigationSidebarPanel.recording = representedObject;
+                WI.navigationSidebar.collapsed = false;
+            });
             return;
+        }
 
-        this._canvasOverviewContentView = new WI.CanvasOverviewContentView(this._canvasCollection);
-        this.contentBrowser.showContentView(this._canvasOverviewContentView);
+        console.assert(false, "Should not be reached.");
     }
 
-    treeElementForRepresentedObject(representedObject)
+    shown()
     {
-        if (!this._overviewTreeElement) {
-            const title = WI.UIString("Canvas Overview");
-            this._overviewTreeElement = new WI.GeneralTreeElement(["canvas-overview"], title, null, representedObject);
-        }
+        super.shown();
 
-        return this._overviewTreeElement;
+        if (!this.contentBrowser.currentContentView)
+            this.showRepresentedObject(this._canvasCollection);
     }
 
-    restoreFromCookie(cookie)
+    restoreStateFromCookie(cookie)
     {
         // FIXME: implement once <https://webkit.org/b/177606> is complete.
     }
@@ -114,18 +144,26 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
 
         WI.canvasManager.addEventListener(WI.CanvasManager.Event.CanvasWasAdded, this._canvasAdded, this);
         WI.canvasManager.addEventListener(WI.CanvasManager.Event.CanvasWasRemoved, this._canvasRemoved, this);
+        WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._recordingStopped, this);
         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+        WI.RecordingContentView.addEventListener(WI.RecordingContentView.Event.RecordingActionIndexChanged, this._recordingActionIndexChanged, this);
 
         this._canvasCollection = new WI.CanvasCollection(WI.canvasManager.canvases);
+
+        for (let canvas of this._canvasCollection.items)
+            this._canvasTreeOutline.appendChild(new WI.CanvasTreeElement(canvas));
     }
 
     detached()
     {
         WI.canvasManager.removeEventListener(null, null, this);
         WI.Frame.removeEventListener(null, null, this);
+        WI.RecordingContentView.removeEventListener(null, null, this);
 
         this._canvasCollection = null;
 
+        this._canvasTreeOutline.removeChildren();
+
         super.detached();
     }
 
@@ -134,19 +172,40 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
     _canvasAdded(event)
     {
         let canvas = event.data.canvas;
+        this._canvasTreeOutline.appendChild(new WI.CanvasTreeElement(canvas));
         this._canvasCollection.add(canvas);
     }
 
     _canvasRemoved(event)
     {
         let canvas = event.data.canvas;
+        let treeElement = this._canvasTreeOutline.findTreeElement(canvas);
+        console.assert(treeElement, "Missing tree element for canvas.", canvas);
+        this._canvasTreeOutline.removeChild(treeElement);
         this._canvasCollection.remove(canvas);
     }
 
-    _overviewPathComponentClicked(event)
+    _canvasTreeOutlineSelectionDidChange(event)
     {
-        console.assert(this._canvasOverviewContentView);
-        this.contentBrowser.showContentView(this._canvasOverviewContentView);
+        let selectedElement = event.data.selectedElement;
+        if (!selectedElement)
+            return;
+
+        let representedObject = selectedElement.representedObject;
+
+        if (this.canShowRepresentedObject(representedObject)) {
+            this.showRepresentedObject(representedObject);
+            this._updateActionIndex(0);
+            return;
+        }
+
+        if (representedObject instanceof WI.Canvas) {
+            this.showRepresentedObject(this._canvasCollection);
+            this.contentBrowser.currentContentView.setSelectedItem(representedObject);
+            return;
+        }
+
+        console.assert(false, "Unexpected representedObject.", representedObject);
     }
 
     _mainResourceDidChange(event)
@@ -156,6 +215,91 @@ WI.CanvasTabContentView = class CanvasTabContentView extends WI.ContentBrowserTa
 
         this._canvasCollection.clear();
     }
+
+    _recordingStopped(event)
+    {
+        let recording = event.data.recording;
+        if (!recording) {
+            // FIXME: <https://webkit.org/b/178185> Web Inspector: Canvas tab: show detailed status during canvas recording
+            return;
+        }
+
+        this._recordingAdded(recording);
+    }
+
+    _navigationSidebarImport(event)
+    {
+        let {filename, payload} = event.data;
+        let recording = WI.Recording.fromPayload(payload);
+        if (!recording) {
+            WI.Recording.synthesizeError(WI.UIString("unsupported version."));
+            return;
+        }
+
+        let extensionStart = filename.lastIndexOf(".");
+        if (extensionStart !== -1)
+            filename = filename.substring(0, extensionStart);
+
+        recording.createDisplayName(filename);
+
+        this._recordingAdded(recording);
+    }
+
+    _navigationSidebarTreeOutlineSelectionChanged(event)
+    {
+        if (!event.data.selectedElement)
+            return;
+
+        let selectedTreeElement = event.data.selectedElement;
+        if (selectedTreeElement instanceof WI.FolderTreeElement)
+            return;
+
+        let recordingContentView = this.contentBrowser.currentContentView;
+        if (!(recordingContentView instanceof WI.RecordingContentView))
+            return;
+
+        this._updateActionIndex(selectedTreeElement.index, {suppressNavigationSidebarUpdate: true});
+    }
+
+    _recordingAdded(recording)
+    {
+        const subtitle = null;
+        let recordingTreeElement = new WI.GeneralTreeElement(["recording"], recording.displayName, subtitle, recording);
+
+        if (recording.source) {
+            let canvasTreeElement = this._canvasTreeOutline.findTreeElement(recording.source);
+            console.assert(canvasTreeElement, "Missing tree element for canvas.", recording.source);
+            if (canvasTreeElement)
+                canvasTreeElement.appendChild(recordingTreeElement);
+        } else
+            this._canvasTreeOutline.appendChild(recordingTreeElement);
+
+        this.showRepresentedObject(recording);
+        this._updateActionIndex(0, {suppressNavigationSidebarUpdate: true});
+    }
+
+    _recordingActionIndexChanged(event)
+    {
+        if (event.target !== this.contentBrowser.currentContentView)
+            return;
+
+        this._updateActionIndex(event.data.index);
+    }
+
+    _updateActionIndex(index, options = {})
+    {
+        options.actionCompletedCallback = (action, context) => {
+            for (let detailsSidebarPanel of this.detailsSidebarPanels) {
+                if (detailsSidebarPanel.updateAction)
+                    detailsSidebarPanel.updateAction(action, context, options);
+            }
+        };
+
+        if (!options.suppressNavigationSidebarUpdate)
+            this.navigationSidebarPanel.updateActionIndex(index, options);
+
+        this.contentBrowser.currentContentView.updateActionIndex(index, options);
+    }
 };
 
 WI.CanvasTabContentView.Type = "canvas";
index 012c6a0..fc1b817 100644 (file)
@@ -60,6 +60,9 @@ WI.ContentView = class ContentView extends WI.View
         if (representedObject instanceof WI.Canvas)
             return new WI.CanvasContentView(representedObject, extraArguments);
 
+        if (representedObject instanceof WI.CanvasCollection)
+            return new WI.CanvasOverviewContentView(representedObject, extraArguments);
+
         if (representedObject instanceof WI.ShaderProgram)
             return new WI.ShaderProgramContentView(representedObject, extraArguments);
 
index df66e0b..55c3556 100644 (file)
@@ -65,9 +65,9 @@ body:not(.window-inactive, .window-docked-inactive) .item.action > .titles .para
     color: var(--text-color-gray-medium);
 }
 
-body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,
-body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected::before {
-    color: var(--console-secondary-text-color);
+body:not(.window-inactive, .window-docked-inactive) .tree-outline:matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,
+body:not(.window-inactive, .window-docked-inactive) .tree-outline:matches(:focus, .force-focus) .item.action.selected::before {
+    color: var(--selected-secondary-text-color);
 }
 
 .tree-outline:matches(:focus, .force-focus) .item.action > .titles .parameters > .inline-swatch {
index 785d91b..dff9a5f 100644 (file)
  */
 
 .content-view:not(.tab).recording {
-    padding: 15px;
+    display: flex;
+    flex-direction: column;
+    padding: 10px;
     background-color: hsl(0, 0%, 90%);
 }
 
+.content-view:not(.tab).recording > header {
+    margin-bottom: 10px;
+}
+
+.content-view:not(.tab).recording > header > .slider-container {
+    display: flex;
+    padding: 8px;
+    background-color: white;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+}
+
+.content-view:not(.tab).recording > header > .slider-container > input[type=range] {
+    flex: 1;
+    -webkit-margin-start: 10px;
+}
+
+.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-runnable-track {
+    height: 2px;
+    background-color: var(--border-color);
+    border-radius: 1px;
+}
+
+.content-view:not(.tab).recording > header > .slider-container > input[type=range]::-webkit-slider-thumb {
+    margin-top: -6px;
+}
+
 .content-view:not(.tab).recording > .preview-container {
     display: flex;
-    align-items: center;
+    flex: 1;
     justify-content: center;
+    align-items: center;
     position: relative;
     width: 100%;
     height: 100%;
index 5b16507..c1bf16c 100644 (file)
@@ -53,12 +53,6 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
             this._showGridButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._showGridButtonClicked, this);
             this._showGridButtonNavigationItem.activated = !!WI.settings.showImageGrid.value;
         }
-
-        this._previewContainer = this.element.appendChild(document.createElement("div"));
-        this._previewContainer.classList.add("preview-container");
-
-        this._messageElement = this.element.appendChild(document.createElement("div"));
-        this._messageElement.classList.add("message");
     }
 
     // Static
@@ -107,12 +101,16 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
         if (!this.representedObject)
             return;
 
+        if (this._index === index)
+            return;
+
         this.representedObject.actions.then((actions) => {
             console.assert(index >= 0 && index < actions.length);
-            if (index < 0 || index >= actions.length || index === this._index)
+            if (index < 0 || index >= actions.length)
                 return;
 
             this._index = index;
+            this._updateSliderValue();
 
             if (this.representedObject.type === WI.Recording.Type.Canvas2D)
                 this._generateContentCanvas2D(index, actions, options);
@@ -136,13 +134,6 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
         }
     }
 
-    get supplementalRepresentedObjects()
-    {
-        let supplementalRepresentedObjects = super.supplementalRepresentedObjects;
-        if (this.representedObject)
-            supplementalRepresentedObjects.push(this.representedObject);
-        return supplementalRepresentedObjects;
-    }
 
     get supportsSave()
     {
@@ -162,6 +153,34 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
         };
     }
 
+    // Protected
+
+    initialLayout()
+    {
+        let previewHeader = this.element.appendChild(document.createElement("header"));
+
+        let sliderContainer = previewHeader.appendChild(document.createElement("div"));
+        sliderContainer.className = "slider-container hidden";
+
+        this._previewContainer = this.element.appendChild(document.createElement("div"));
+        this._previewContainer.className = "preview-container";
+
+        this._sliderValueElement = sliderContainer.appendChild(document.createElement("div"));
+        this._sliderValueElement.className = "slider-value";
+
+        this._sliderElement = sliderContainer.appendChild(document.createElement("input"));
+        this._sliderElement.addEventListener("input", this._sliderChanged.bind(this));
+        this._sliderElement.type = "range";
+        this._sliderElement.min = 0;
+        this._sliderElement.max = 0;
+
+        this.representedObject.actions.then(() => {
+            sliderContainer.classList.remove("hidden");
+            this._sliderElement.max = this.representedObject.visualActionIndexes.length;
+            this._updateSliderValue();
+        });
+    }
+
     // Private
 
     async _generateContentCanvas2D(index, actions, options = {})
@@ -396,7 +415,6 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
         }
 
         this._previewContainer.removeChildren();
-        this._messageElement.removeChildren();
 
         if (showCanvasPath) {
             indexOfLastBeginPathAction = this._index;
@@ -481,6 +499,22 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
             this._snapshots[snapshotIndex].element.classList.toggle("show-grid", activated);
     }
 
+    _updateSliderValue()
+    {
+        if (!this._sliderElement)
+            return;
+
+        let visualActionIndexes = this.representedObject.visualActionIndexes;
+        let visualActionIndex = 0;
+        if (this._index > 0) {
+            while (visualActionIndex < visualActionIndexes.length && visualActionIndexes[visualActionIndex] <= this._index)
+                visualActionIndex++;
+        }
+
+        this._sliderElement.value = visualActionIndex;
+        this._sliderValueElement.textContent = WI.UIString("%d of %d").format(visualActionIndex, visualActionIndexes.length);
+    }
+
     _showPathButtonClicked(event)
     {
         WI.settings.showCanvasPath.value = !this._showPathButtonNavigationItem.activated;
@@ -494,6 +528,21 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView
 
         this._updateImageGrid();
     }
+
+    _sliderChanged()
+    {
+        let index = 0;
+
+        let visualActionIndex = parseInt(this._sliderElement.value) - 1;
+        if (visualActionIndex !== -1)
+            index = this.representedObject.visualActionIndexes[visualActionIndex];
+
+        this.dispatchEventToListeners(WI.RecordingContentView.Event.RecordingActionIndexChanged, {index});
+    }
 };
 
 WI.RecordingContentView.SnapshotInterval = 5000;
+
+WI.RecordingContentView.Event = {
+    RecordingActionIndexChanged: "recording-action-index-changed",
+};
index 20db4a3..126ed31 100644 (file)
@@ -88,6 +88,9 @@ WI.RecordingNavigationSidebarPanel = class RecordingNavigationSidebarPanel exten
             });
 
             this._exportButton.disabled = !actions.length;
+
+            let index = this._recording[WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol] || 0;
+            this.updateActionIndex(index);
         });
     }
 
@@ -97,31 +100,23 @@ WI.RecordingNavigationSidebarPanel = class RecordingNavigationSidebarPanel exten
             return;
 
         this._recording.actions.then((actions) => {
-            console.assert(index >= 0 && index < actions.length);
-            if (index < 0 || index >= actions.length)
+            let recordingAction = actions[index];
+            console.assert(recordingAction, "Invalid recording action index.", index);
+            if (!recordingAction)
                 return;
 
-            let treeOutline = this.contentTreeOutline;
-            if (!(actions[0] instanceof WI.RecordingInitialStateAction) || index) {
-                treeOutline = treeOutline.children[0];
-                while (index > treeOutline.children.length) {
-                    index -= treeOutline.children.length;
-                    treeOutline = treeOutline.nextSibling;
-                }
-
-                // Must subtract one from the final result since the initial state is considered index 0.
-                --index;
-            }
+            let treeElement = this.contentTreeOutline.findTreeElement(recordingAction);
+            console.assert(treeElement, "Missing tree element for recording action.", recordingAction);
+            if (!treeElement)
+                return;
 
-            let treeElementToSelect = treeOutline.children[index];
-            if (treeElementToSelect.parent && !treeElementToSelect.parent.expanded)
-                treeElementToSelect = treeElementToSelect.parent;
+            this._recording[WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol] = index;
 
             const omitFocus = false;
             const selectedByUser = false;
-            let suppressOnSelect = !(treeElementToSelect instanceof WI.FolderTreeElement);
+            const suppressOnSelect = true;
             const suppressOnDeselect = true;
-            treeElementToSelect.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
+            treeElement.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
         });
     }
 
@@ -203,6 +198,8 @@ WI.RecordingNavigationSidebarPanel = class RecordingNavigationSidebarPanel exten
     }
 };
 
+WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol = Symbol("selected-action-index");
+
 WI.RecordingNavigationSidebarPanel.Event = {
     Import: "recording-navigation-sidebar-panel-import",
 };
diff --git a/Source/WebInspectorUI/UserInterface/Views/RecordingTabContentView.js b/Source/WebInspectorUI/UserInterface/Views/RecordingTabContentView.js
deleted file mode 100644 (file)
index c7560d2..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-WI.RecordingTabContentView = class RecordingTabContentView extends WI.ContentBrowserTabContentView
-{
-    constructor()
-    {
-        let {image, title} = WI.RecordingTabContentView.tabInfo();
-        let tabBarItem = new WI.GeneralTabBarItem(image, title);
-
-        const navigationSidebarPanelConstructor = WI.RecordingNavigationSidebarPanel;
-        const detailsSidebarPanelConstructors = [WI.RecordingStateDetailsSidebarPanel, WI.RecordingTraceDetailsSidebarPanel, WI.CanvasDetailsSidebarPanel];
-        const disableBackForward = true;
-        let flexibleNavigationItem = new WI.ScrubberNavigationItem;
-        super("recording", "recording", tabBarItem, navigationSidebarPanelConstructor, detailsSidebarPanelConstructors, disableBackForward, flexibleNavigationItem);
-
-        this._visualActionIndexes = [];
-
-        this._scrubberNavigationItem = flexibleNavigationItem;
-        this._scrubberNavigationItem.value = 0;
-        this._scrubberNavigationItem.disabled = true;
-        this._scrubberNavigationItem.addEventListener(WI.ScrubberNavigationItem.Event.ValueChanged, this._scrubberNavigationItemValueChanged, this);
-
-        this.navigationSidebarPanel.addEventListener(WI.RecordingNavigationSidebarPanel.Event.Import, this._navigationSidebarImport, this);
-        this.navigationSidebarPanel.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._navigationSidebarTreeOutlineSelectionChanged, this);
-
-        this._recording = null;
-    }
-
-    // Static
-
-    static tabInfo()
-    {
-        return {
-            image: "Images/Recording.svg",
-            title: WI.UIString("Recording"),
-        };
-    }
-
-    static isTabAllowed()
-    {
-        return !!window.CanvasAgent;
-    }
-
-    // Public
-
-    get type()
-    {
-        return WI.RecordingTabContentView.Type;
-    }
-
-    canShowRepresentedObject(representedObject)
-    {
-        // Once a recording has been loaded for this tab, do not allow another one to be loaded.
-        // This will cause new tabs to be opened for each recording, which is the desired behavior.
-        if (this._recording)
-            return false;
-
-        return representedObject instanceof WI.Recording;
-    }
-
-    showRepresentedObject(representedObject, cookie)
-    {
-        super.showRepresentedObject(representedObject, cookie);
-
-        this._recording = representedObject;
-        this._recording.actions.then((actions) => {
-            this._visualActionIndexes = [];
-            actions.forEach((action, i) => {
-                if (action.isVisual)
-                    this._visualActionIndexes.push(i);
-            });
-
-            this._scrubberNavigationItem.value = 0;
-            this._scrubberNavigationItem.min = 0;
-            this._scrubberNavigationItem.max = actions.length - 1;
-            this._scrubberNavigationItem.disabled = false;
-
-            this.navigationSidebarPanel.recording = this._recording;
-
-            this._updateActionIndex(this._scrubberNavigationItem.value);
-        });
-    }
-
-    // Protected
-
-    restoreStateFromCookie(restorationType)
-    {
-        // Don't attempt to do anything to this tab.
-    }
-
-    saveStateToCookie(cookie)
-    {
-        // Don't attempt to do anything to this tab.
-    }
-
-    closed()
-    {
-        super.closed();
-
-        for (let detailsSidebarPanel of this.detailsSidebarPanels)
-            detailsSidebarPanel.recording = null;
-
-        this.navigationSidebarPanel.recording = null;
-    }
-
-    // Private
-
-    _updateActionIndex(index, options = {})
-    {
-        this._scrubberNavigationItem.value = index;
-
-        options.actionCompletedCallback = (action, context) => {
-            for (let detailsSidebarPanel of this.detailsSidebarPanels) {
-                if (detailsSidebarPanel.updateAction)
-                    detailsSidebarPanel.updateAction(action, context, options);
-            }
-        };
-
-        this.contentBrowser.currentContentView.updateActionIndex(index, options);
-
-        // This must be placed last, as it is possible for the update of the NavigationSidebarPanel
-        // to trigger another update of the index, such as if the selected index is not expanded.
-        if (!options.suppressNavigationUpdate)
-            this.navigationSidebarPanel.updateActionIndex(index, options);
-    }
-
-    _scrubberNavigationItemValueChanged(event)
-    {
-        for (let i = 0; i <= this._visualActionIndexes.length; ++i) {
-            if (this._visualActionIndexes[i] < this._scrubberNavigationItem.value)
-                continue;
-
-            let min = i ? this._visualActionIndexes[i - 1] : this._scrubberNavigationItem.min;
-            let max = i < this._visualActionIndexes.length ? this._visualActionIndexes[i] : this._scrubberNavigationItem.max;
-            this._updateActionIndex(this._scrubberNavigationItem.value >= (min + max) / 2 ? max : min);
-            return;
-        }
-    }
-
-    _navigationSidebarImport(event)
-    {
-        let {filename, payload} = event.data;
-        let recording = WI.Recording.fromPayload(payload);
-        if (!recording) {
-            WI.Recording.synthesizeError(WI.UIString("unsupported version."));
-            return;
-        }
-
-        let extensionStart = filename.lastIndexOf(".");
-        if (extensionStart !== -1)
-            filename = filename.substring(0, extensionStart);
-
-        recording.createDisplayName(filename);
-
-        this.showRepresentedObject(recording);
-    }
-
-    _navigationSidebarTreeOutlineSelectionChanged(event)
-    {
-        // Ignore deselect events.
-        if (!event.data.selectedElement)
-            return;
-
-        let options = {suppressNavigationUpdate: true};
-        let selectedTreeElement = this.navigationSidebarPanel.contentTreeOutline.selectedTreeElement;
-        if (selectedTreeElement instanceof WI.FolderTreeElement)
-            selectedTreeElement = selectedTreeElement.children.lastValue;
-        this._updateActionIndex(selectedTreeElement.index, options);
-    }
-};
-
-WI.RecordingTabContentView.Type = "recording";
index 59bc02e..9649cd4 100644 (file)
     content: url(../Images/Beacon.svg);
 }
 
-.canvas.canvas-2d .icon {
-    content: url(../Images/Canvas2D.svg);
-}
-
-.canvas:matches(.webgl, .webgl2, .webgpu) .icon {
-    content: url(../Images/Canvas3D.svg);
+.canvas .icon {
+    content: url(../Images/Canvas.svg);
 }
 
 .shader-program .icon {