Web Inspector: Canvas tab: show detailed status during canvas recording
authorwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 11 Nov 2017 09:22:08 +0000 (09:22 +0000)
committerwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 11 Nov 2017 09:22:08 +0000 (09:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178185
<rdar://problem/34939862>

Reviewed by Brian Burg.

Source/JavaScriptCore:

* inspector/protocol/Canvas.json:
Add a `recordingProgress` event that is sent to the frontend that contains all the frame
payloads since the last Canvas.recordingProgress event and the current buffer usage.

* inspector/protocol/Recording.json:
Remove the required `frames` parameter from the Recording protocol object, as they will be
sent in batches via the Canvas.recordingProgress event.

Source/WebCore:

Updated existing tests to assert that the correct number of progress events are fired.

* inspector/agents/InspectorCanvasAgent.cpp:
(WebCore::InspectorCanvasAgent::didFinishRecordingCanvasFrame):
After each recorded frame, fire a progress event with the recorded frames as data. This will
release the frames from the associated InspectorCanvas, meaning that the frames must be
aggregated in the frontend before a Recording can be created.

The protocol is designed with arrays so that if we decide to add throttling (e.g. one event
per second) we are able to do so without modifying the protocol.

* inspector/InspectorCanvas.h:
* inspector/InspectorCanvas.cpp:
(WebCore::InspectorCanvas::hasRecordingData const):
(WebCore::InspectorCanvas::currentFrameHasData const):
(WebCore::InspectorCanvas::recordAction):
(WebCore::InspectorCanvas::finalizeFrame):
(WebCore::InspectorCanvas::markCurrentFrameIncomplete):
Add additional checks that the list of recorded frames is not nullptr when finalizing or
marking as incomplete.

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js:

* UserInterface/Protocol/CanvasObserver.js:
(WI.CanvasObserver.prototype.recordingProgress):
* UserInterface/Controllers/CanvasManager.js:
(WI.CanvasManager):
(WI.CanvasManager.prototype.recordingProgress):
(WI.CanvasManager.prototype.recordingFinished):
Maintain arrays of recorded frames for each canvas and add new frames each time a progress
event is fired. When the recording is finished, use the array as part of the payload for
creating a WI.Recording object.

* UserInterface/Models/Recording.js:
(WI.Recording.fromPayload):

* UserInterface/Models/RecordingAction.js:
(WI.RecordingAction.prototype.apply.getContent):
(WI.RecordingAction.prototype.apply):
Drive-by fix: toDataURL is very slow, so add alternative ways of getting and comparing the
content of the canvas to determine if the action has a visible effect.

* UserInterface/Views/CanvasContentView.css:
(.content-view.canvas:not(.tab)):
(.content-view.canvas:not(.tab) > .progress):
(.content-view.canvas:not(.tab) > .progress > .frame-count):
* UserInterface/Views/CanvasContentView.js:
(WI.CanvasContentView):
(WI.CanvasContentView.prototype.attached):
(WI.CanvasContentView.prototype._recordingStarted):
(WI.CanvasContentView.prototype._recordingProgress):
(WI.CanvasContentView.prototype._recordingStopped):
Steal some space from the preview area to display a count of the number of recorded frames
and the current buffer usage.

* UserInterface/Views/CanvasOverviewContentView.css:
(.content-view.canvas-overview .content-view.canvas > :matches(header, .progress, .preview, footer)):
(.content-view.canvas-overview .content-view.canvas.selected > :matches(.progress, .preview, footer),):
(.content-view.canvas-overview .content-view.canvas > :matches(.progress, .preview)):
(.content-view.canvas-overview .content-view.canvas > .preview):
(.content-view.canvas-overview .content-view.canvas > .progress ~ .preview):
(.content-view.canvas-overview .content-view.canvas > :matches(header, .preview, footer)): Deleted.
(.content-view.canvas-overview .content-view.canvas.selected > :matches(.preview, footer),): Deleted.

LayoutTests:

* inspector/canvas/resources/recording-utilities.js:
(TestPage.registerInitializer.window.startRecording.handleRecordingProgress):
(TestPage.registerInitializer.window.startRecording):

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

18 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/canvas/resources/recording-utilities.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Canvas.json
Source/JavaScriptCore/inspector/protocol/Recording.json
Source/WebCore/ChangeLog
Source/WebCore/inspector/InspectorCanvas.cpp
Source/WebCore/inspector/InspectorCanvas.h
Source/WebCore/inspector/agents/InspectorCanvasAgent.cpp
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/CanvasManager.js
Source/WebInspectorUI/UserInterface/Models/Recording.js
Source/WebInspectorUI/UserInterface/Models/RecordingAction.js
Source/WebInspectorUI/UserInterface/Protocol/CanvasObserver.js
Source/WebInspectorUI/UserInterface/Views/CanvasContentView.css
Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js
Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css

index c4663bf..2ba3268 100644 (file)
@@ -1,3 +1,15 @@
+2017-11-11  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas tab: show detailed status during canvas recording
+        https://bugs.webkit.org/show_bug.cgi?id=178185
+        <rdar://problem/34939862>
+
+        Reviewed by Brian Burg.
+
+        * inspector/canvas/resources/recording-utilities.js:
+        (TestPage.registerInitializer.window.startRecording.handleRecordingProgress):
+        (TestPage.registerInitializer.window.startRecording):
+
 2017-11-10  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, unskip & rebaseline several service worker tests
index d7a164d..cb8a9d6 100644 (file)
@@ -106,13 +106,27 @@ TestPage.registerInitializer(() => {
             return;
         }
 
+        let bufferUsed = 0;
+        let frameCount = 0;
+        function handleRecordingProgress(event) {
+            InspectorTest.assert(event.data.frameCount > frameCount, "Additional frames were captured for this progress event.");
+            frameCount = event.data.frameCount;
+
+            InspectorTest.assert(event.data.bufferUsed > bufferUsed, "Total memory usage increases with each progress event.");
+            bufferUsed = event.data.bufferUsed;
+        }
+        WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingProgress, handleRecordingProgress);
+
         WI.canvasManager.awaitEvent(WI.CanvasManager.Event.RecordingStopped).then((event) => {
+            WI.canvasManager.removeEventListener(WI.CanvasManager.Event.RecordingProgress, handleRecordingProgress);
+
             InspectorTest.evaluateInPage(`cancelActions()`);
 
             let recording = event.data.recording;
             InspectorTest.assert(recording.source === canvas, "Recording should be of the given canvas.");
             InspectorTest.assert(recording.source.contextType === type, `Recording should be of a canvas with type "${type}".`);
             InspectorTest.assert(recording.source.recordingCollection.items.has(recording), "Recording should be in the canvas' list of recordings.");
+            InspectorTest.assert(recording.frames.length === frameCount, `Recording should have ${frameCount} frames.`)
 
             return recording.actions.then(() => {
                 logRecording(recording, type);
index 877f9b1..36e9e2c 100644 (file)
@@ -1,3 +1,19 @@
+2017-11-11  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas tab: show detailed status during canvas recording
+        https://bugs.webkit.org/show_bug.cgi?id=178185
+        <rdar://problem/34939862>
+
+        Reviewed by Brian Burg.
+
+        * inspector/protocol/Canvas.json:
+        Add a `recordingProgress` event that is sent to the frontend that contains all the frame
+        payloads since the last Canvas.recordingProgress event and the current buffer usage.
+
+        * inspector/protocol/Recording.json:
+        Remove the required `frames` parameter from the Recording protocol object, as they will be
+        sent in batches via the Canvas.recordingProgress event.
+
 2017-11-10  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Make http status codes be "integer" instead of "number" in protocol
index 123a604..ead07d7 100644 (file)
             ]
         },
         {
+            "name": "recordingProgress",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" },
+                { "name": "frames", "type": "array", "items": { "$ref": "Recording.Frame" }},
+                { "name": "bufferUsed", "type": "integer", "description": "Total memory size in bytes of all data recorded since the recording began." }
+            ]
+        },
+        {
             "name": "recordingFinished",
             "parameters": [
                 { "name": "canvasId", "$ref": "CanvasId" },
index c841bfc..2912179 100644 (file)
@@ -35,7 +35,6 @@
                 { "name": "version", "type": "integer", "description": "Used for future/backwards compatibility." },
                 { "name": "type", "$ref": "Type" },
                 { "name": "initialState", "$ref": "InitialState", "description": "JSON data of inital state of object before recording." },
-                { "name": "frames", "type": "array", "items": { "$ref": "Frame" }, "description": "JSON data of all object API calls." },
                 { "name": "data", "type": "array", "items": { "type": "any" }, "description": "Array of objects that can be referenced by index. Used to avoid duplicating objects." }
             ]
         }
index f576080..7fb98c5 100644 (file)
@@ -1,3 +1,32 @@
+2017-11-11  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas tab: show detailed status during canvas recording
+        https://bugs.webkit.org/show_bug.cgi?id=178185
+        <rdar://problem/34939862>
+
+        Reviewed by Brian Burg.
+
+        Updated existing tests to assert that the correct number of progress events are fired.
+
+        * inspector/agents/InspectorCanvasAgent.cpp:
+        (WebCore::InspectorCanvasAgent::didFinishRecordingCanvasFrame):
+        After each recorded frame, fire a progress event with the recorded frames as data. This will
+        release the frames from the associated InspectorCanvas, meaning that the frames must be
+        aggregated in the frontend before a Recording can be created.
+
+        The protocol is designed with arrays so that if we decide to add throttling (e.g. one event
+        per second) we are able to do so without modifying the protocol.
+
+        * inspector/InspectorCanvas.h:
+        * inspector/InspectorCanvas.cpp:
+        (WebCore::InspectorCanvas::hasRecordingData const):
+        (WebCore::InspectorCanvas::currentFrameHasData const):
+        (WebCore::InspectorCanvas::recordAction):
+        (WebCore::InspectorCanvas::finalizeFrame):
+        (WebCore::InspectorCanvas::markCurrentFrameIncomplete):
+        Add additional checks that the list of recorded frames is not nullptr when finalizing or
+        marking as incomplete.
+
 2017-11-11  Helmut Grohne  <helmut@subdivi.de>
 
         [GTK] PlatformGTK.cmake: use the PKG_CONFIG_EXECUTABLE variable
index 4bb1ec0..0e1e843 100644 (file)
@@ -110,7 +110,12 @@ void InspectorCanvas::resetRecordingData()
 
 bool InspectorCanvas::hasRecordingData() const
 {
-    return m_initialState && m_frames;
+    return m_bufferUsed > 0;
+}
+
+bool InspectorCanvas::currentFrameHasData() const
+{
+    return !!m_frames;
 }
 
 static bool shouldSnapshotWebGLAction(const String& name)
@@ -122,12 +127,13 @@ static bool shouldSnapshotWebGLAction(const String& name)
 
 void InspectorCanvas::recordAction(const String& name, Vector<RecordCanvasActionVariant>&& parameters)
 {
-    if (!hasRecordingData()) {
+    if (!m_initialState) {
         m_initialState = buildInitialState();
         m_bufferUsed += m_initialState->memoryCost();
+    }
 
+    if (!m_frames)
         m_frames = Inspector::Protocol::Array<Inspector::Protocol::Recording::Frame>::create();
-    }
 
     if (!m_currentActions) {
         m_currentActions = Inspector::Protocol::Array<InspectorValue>::create();
@@ -173,7 +179,7 @@ RefPtr<Inspector::Protocol::Array<InspectorValue>>&& InspectorCanvas::releaseDat
 
 void InspectorCanvas::finalizeFrame()
 {
-    if (m_frames->length() && !std::isnan(m_currentFrameStartTime)) {
+    if (m_frames && m_frames->length() && !std::isnan(m_currentFrameStartTime)) {
         auto currentFrame = static_cast<Inspector::Protocol::Recording::Frame*>(m_frames->get(m_frames->length() - 1).get());
         currentFrame->setDuration(monotonicallyIncreasingTimeMS() - m_currentFrameStartTime);
 
@@ -185,7 +191,7 @@ void InspectorCanvas::finalizeFrame()
 
 void InspectorCanvas::markCurrentFrameIncomplete()
 {
-    if (!m_currentActions)
+    if (!m_currentActions || !m_frames || !m_frames->length())
         return;
 
     static_cast<Inspector::Protocol::Recording::Frame*>(m_frames->get(m_frames->length() - 1).get())->setIncomplete(true);
index b29456b..01faabd 100644 (file)
@@ -59,6 +59,7 @@ public:
 
     void resetRecordingData();
     bool hasRecordingData() const;
+    bool currentFrameHasData() const;
     void recordAction(const String&, Vector<RecordCanvasActionVariant>&& = { });
 
     RefPtr<Inspector::Protocol::Recording::InitialState>&& releaseInitialState();
@@ -70,6 +71,7 @@ public:
 
     void setBufferLimit(long);
     bool hasBufferSpace() const;
+    long bufferUsed() const { return m_bufferUsed; }
 
     bool singleFrame() const { return m_singleFrame; }
     void setSingleFrame(bool singleFrame) { m_singleFrame = singleFrame; }
index 1bbff97..d17f56c 100644 (file)
@@ -490,6 +490,9 @@ void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canv
         inspectorCanvas->markCurrentFrameIncomplete();
 
     inspectorCanvas->finalizeFrame();
+    if (inspectorCanvas->currentFrameHasData())
+        m_frontendDispatcher->recordingProgress(inspectorCanvas->identifier(), inspectorCanvas->releaseFrames(), inspectorCanvas->bufferUsed());
+
     if (!forceDispatch && !inspectorCanvas->singleFrame())
         return;
 
@@ -511,7 +514,6 @@ void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canv
         .setVersion(1)
         .setType(type)
         .setInitialState(inspectorCanvas->releaseInitialState())
-        .setFrames(inspectorCanvas->releaseFrames())
         .setData(inspectorCanvas->releaseData())
         .release();
 
index cc7dca2..5f83d43 100644 (file)
@@ -1,3 +1,54 @@
+2017-11-11  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Canvas tab: show detailed status during canvas recording
+        https://bugs.webkit.org/show_bug.cgi?id=178185
+        <rdar://problem/34939862>
+
+        Reviewed by Brian Burg.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+        * UserInterface/Protocol/CanvasObserver.js:
+        (WI.CanvasObserver.prototype.recordingProgress):
+        * UserInterface/Controllers/CanvasManager.js:
+        (WI.CanvasManager):
+        (WI.CanvasManager.prototype.recordingProgress):
+        (WI.CanvasManager.prototype.recordingFinished):
+        Maintain arrays of recorded frames for each canvas and add new frames each time a progress
+        event is fired. When the recording is finished, use the array as part of the payload for
+        creating a WI.Recording object.
+
+        * UserInterface/Models/Recording.js:
+        (WI.Recording.fromPayload):
+
+        * UserInterface/Models/RecordingAction.js:
+        (WI.RecordingAction.prototype.apply.getContent):
+        (WI.RecordingAction.prototype.apply):
+        Drive-by fix: toDataURL is very slow, so add alternative ways of getting and comparing the
+        content of the canvas to determine if the action has a visible effect.
+
+        * UserInterface/Views/CanvasContentView.css:
+        (.content-view.canvas:not(.tab)):
+        (.content-view.canvas:not(.tab) > .progress):
+        (.content-view.canvas:not(.tab) > .progress > .frame-count):
+        * UserInterface/Views/CanvasContentView.js:
+        (WI.CanvasContentView):
+        (WI.CanvasContentView.prototype.attached):
+        (WI.CanvasContentView.prototype._recordingStarted):
+        (WI.CanvasContentView.prototype._recordingProgress):
+        (WI.CanvasContentView.prototype._recordingStopped):
+        Steal some space from the preview area to display a count of the number of recorded frames
+        and the current buffer usage.
+
+        * UserInterface/Views/CanvasOverviewContentView.css:
+        (.content-view.canvas-overview .content-view.canvas > :matches(header, .progress, .preview, footer)):
+        (.content-view.canvas-overview .content-view.canvas.selected > :matches(.progress, .preview, footer),):
+        (.content-view.canvas-overview .content-view.canvas > :matches(.progress, .preview)):
+        (.content-view.canvas-overview .content-view.canvas > .preview):
+        (.content-view.canvas-overview .content-view.canvas > .progress ~ .preview):
+        (.content-view.canvas-overview .content-view.canvas > :matches(header, .preview, footer)): Deleted.
+        (.content-view.canvas-overview .content-view.canvas.selected > :matches(.preview, footer),): Deleted.
+
 2017-11-09  Nikita Vasilyev  <nvasilyev@apple.com>
 
         Web Inspector: Styles Redesign: clicking on inline swatches and property checkboxes should not add a new property
index 2cdbb7a..e2396ad 100644 (file)
@@ -24,6 +24,8 @@ localizedStrings["%.2fs"] = "%.2fs";
 localizedStrings["%.3fms"] = "%.3fms";
 localizedStrings["%d Errors"] = "%d Errors";
 localizedStrings["%d Errors, %d Warnings"] = "%d Errors, %d Warnings";
+localizedStrings["%d Frame"] = "%d Frame";
+localizedStrings["%d Frames"] = "%d Frames";
 localizedStrings["%d More\u2026"] = "%d More\u2026";
 localizedStrings["%d Threads"] = "%d Threads";
 localizedStrings["%d Warnings"] = "%d Warnings";
@@ -1011,6 +1013,7 @@ localizedStrings["View variable value"] = "View variable value";
 localizedStrings["Visibility"] = "Visibility";
 localizedStrings["Visible"] = "Visible";
 localizedStrings["Waiting"] = "Waiting";
+localizedStrings["Waiting for frames…"] = "Waiting for frames…";
 localizedStrings["Warning"] = "Warning";
 localizedStrings["Warning: "] = "Warning: ";
 localizedStrings["Warnings"] = "Warnings";
index 311950f..f73b92f 100644 (file)
@@ -33,7 +33,9 @@ WI.CanvasManager = class CanvasManager extends WI.Object
 
         this._canvasIdentifierMap = new Map;
         this._shaderProgramIdentifierMap = new Map;
+
         this._recordingCanvas = null;
+        this._recordingFrameMap = new Map;
 
         if (window.CanvasAgent)
             CanvasAgent.enable();
@@ -138,6 +140,30 @@ WI.CanvasManager = class CanvasManager extends WI.Object
         canvas.cssCanvasClientNodesChanged();
     }
 
+    recordingProgress(canvasIdentifier, framesPayload, bufferUsed)
+    {
+        // Called from WI.CanvasObserver.
+
+        let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
+        console.assert(canvas);
+        if (!canvas)
+            return;
+
+        let existingFrames = this._recordingFrameMap.get(canvasIdentifier);
+        if (!existingFrames) {
+            existingFrames = [];
+            this._recordingFrameMap.set(canvasIdentifier, existingFrames);
+        }
+
+        existingFrames.push(...framesPayload.map(WI.RecordingFrame.fromPayload));
+
+        this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingProgress, {
+            canvas,
+            frameCount: existingFrames.length,
+            bufferUsed,
+        });
+    }
+
     recordingFinished(canvasIdentifier, recordingPayload)
     {
         // Called from WI.CanvasObserver.
@@ -149,7 +175,8 @@ WI.CanvasManager = class CanvasManager extends WI.Object
         if (!canvas)
             return;
 
-        let recording = recordingPayload ? WI.Recording.fromPayload(recordingPayload) : null;
+        let frames = this._recordingFrameMap.take(canvasIdentifier);
+        let recording = recordingPayload ? WI.Recording.fromPayload(recordingPayload, frames) : null;
         if (recording) {
             recording.source = canvas;
             recording.createDisplayName();
@@ -242,6 +269,7 @@ WI.CanvasManager.Event = {
     CanvasAdded: "canvas-manager-canvas-was-added",
     CanvasRemoved: "canvas-manager-canvas-was-removed",
     RecordingStarted: "canvas-manager-recording-started",
+    RecordingProgress: "canvas-manager-recording-progress",
     RecordingStopped: "canvas-manager-recording-stopped",
     ShaderProgramAdded: "canvas-manager-shader-program-added",
     ShaderProgramRemoved: "canvas-manager-shader-program-removed",
index 0082c6d..cf598ce 100644 (file)
@@ -68,7 +68,7 @@ WI.Recording = class Recording
         });
     }
 
-    static fromPayload(payload)
+    static fromPayload(payload, frames)
     {
         if (typeof payload !== "object" || payload === null)
             return null;
@@ -104,7 +104,9 @@ WI.Recording = class Recording
         if (!Array.isArray(payload.data))
             payload.data = [];
 
-        let frames = payload.frames.map(WI.RecordingFrame.fromPayload);
+        if (!frames)
+            frames = payload.frames.map(WI.RecordingFrame.fromPayload)
+
         return new WI.Recording(payload.version, type, payload.initialState, frames, payload.data);
     }
 
index db19c3d..0141999 100644 (file)
@@ -120,10 +120,27 @@ WI.RecordingAction = class RecordingAction extends WI.Object
         if (!this.valid)
             return;
 
+        function getContent() {
+            if (context instanceof CanvasRenderingContext2D)
+                return context.getImageData(0, 0, context.canvas.width, context.canvas.height);
+
+            if (context instanceof WebGLRenderingContext || context instanceof WebGL2RenderingContext) {
+                let pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
+                context.readPixels(0, 0, context.canvas.width, context.canvas.height, context.RGBA, context.UNSIGNED_BYTE, pixels);
+                return pixels;
+            }
+
+            if (context.canvas instanceof HTMLCanvasElement)
+                return [context.canvas.toDataURL()];
+
+            console.assert("Unknown context type", context);
+            return [];
+        }
+
         let contentBefore = null;
         let shouldCheckForChange = this._isVisual && this._hasVisibleEffect === undefined;
         if (shouldCheckForChange)
-            contentBefore = context.canvas.toDataURL();
+            contentBefore = getContent();
 
         try {
             let name = options.nameOverride || this._name;
@@ -137,7 +154,7 @@ WI.RecordingAction = class RecordingAction extends WI.Object
             }
 
             if (shouldCheckForChange) {
-                this._hasVisibleEffect = contentBefore !== context.canvas.toDataURL();
+                this._hasVisibleEffect = Array.shallowEqual(contentBefore, getContent());
                 if (!this._hasVisibleEffect)
                     this.dispatchEventToListeners(WI.RecordingAction.Event.HasVisibleEffectChanged);
             }
index 656783d..8773ecd 100644 (file)
@@ -47,6 +47,11 @@ WI.CanvasObserver = class CanvasObserver
         WI.canvasManager.cssCanvasClientNodesChanged(canvasId);
     }
 
+    recordingProgress(canvasId, frames, bufferUsed)
+    {
+        WI.canvasManager.recordingProgress(canvasId, frames, bufferUsed);
+    }
+
     recordingFinished(canvasId, recording)
     {
         WI.canvasManager.recordingFinished(canvasId, recording);
index e7b9be4..38a0ac6 100644 (file)
 
 .content-view.canvas:not(.tab) {
     background-color: hsl(0, 0%, 90%);
+
+    --progress-padding: 8px;
+}
+
+.content-view.canvas:not(.tab) > .progress {
+    padding: var(--progress-padding) 0;
+    text-align: center;
+    color: var(--text-color-gray-medium);
+}
+
+.content-view.canvas:not(.tab) > .progress > .frame-count {
+    color: var(--text-color-gray-dark);
 }
 
 .content-view.canvas:not(.tab) > .preview {
index 45ab55e..e1efddf 100644 (file)
@@ -33,6 +33,7 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
 
         this.element.classList.add("canvas");
 
+        this._recordingProgressElement = null;
         this._previewContainerElement = null;
         this._previewImageElement = null;
         this._errorElement = null;
@@ -196,6 +197,7 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
         });
 
         WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStarted, this._recordingStarted, this);
+        WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingProgress, this._recordingProgress, this);
         WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._recordingStopped, this);
 
         WI.settings.showImageGrid.addEventListener(WI.Setting.Event.Changed, this._updateImageGrid, this);
@@ -260,6 +262,34 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
     _recordingStarted(event)
     {
         this._updateRecordNavigationItem();
+
+        if (!this._recordingProgressElement) {
+            this._recordingProgressElement = this._previewContainerElement.insertAdjacentElement("beforebegin", document.createElement("div"));
+            this._recordingProgressElement.className = "progress";
+        }
+
+        this._recordingProgressElement.textContent = WI.UIString("Waiting for frames…");
+    }
+
+    _recordingProgress(event)
+    {
+        let {canvas, frameCount, bufferUsed} = event.data;
+        if (canvas !== this.representedObject)
+            return;
+
+        this._recordingProgressElement.removeChildren();
+
+        let frameCountElement = this._recordingProgressElement.appendChild(document.createElement("span"));
+        frameCountElement.className = "frame-count";
+
+        let frameString = frameCount === 1 ? WI.UIString("%d Frame") : WI.UIString("%d Frames");
+        frameCountElement.textContent = frameString.format(frameCount);
+
+        this._recordingProgressElement.append(" ");
+
+        let bufferUsedElement = this._recordingProgressElement.appendChild(document.createElement("span"));
+        bufferUsedElement.className = "buffer-used";
+        bufferUsedElement.textContent = "(" + Number.bytesToString(bufferUsed) + ")";
     }
 
     _recordingStopped(event)
@@ -271,6 +301,9 @@ WI.CanvasContentView = class CanvasContentView extends WI.ContentView
             return;
 
         this._addRecording(recording);
+
+        this._recordingProgressElement.remove();
+        this._recordingProgressElement = null;
     }
 
     _handleRecordingSelectElementChange(event)
index 86be362..2120364 100644 (file)
     background-color: white;
 }
 
-.content-view.canvas-overview .content-view.canvas > :matches(header, .preview, footer) {
+.content-view.canvas-overview .content-view.canvas > :matches(header, .progress, .preview, footer) {
     border: 1px solid var(--border-color);
 }
 
-.content-view.canvas-overview .content-view.canvas.selected > :matches(.preview, footer),
+.content-view.canvas-overview .content-view.canvas.selected > :matches(.progress, .preview, footer),
 .content-view.canvas-overview .content-view.canvas.selected:not(.is-recording) > header {
     border-color: var(--selected-background-color);
 }
     filter: brightness(80%);
 }
 
-.content-view.canvas-overview .content-view.canvas > .preview {
-    height: 280px;
+.content-view.canvas-overview .content-view.canvas > :matches(.progress, .preview) {
     border-top: none;
     border-bottom: none;
 }
 
+.content-view.canvas-overview .content-view.canvas > .preview {
+    height: var(--preview-height);
+
+    --preview-height: 280px;
+}
+
+.content-view.canvas-overview .content-view.canvas > .progress ~ .preview {
+    /* Keep the height of each CanvasContentView constant by subtracting the padding-top, */
+    /* padding-bottom, and text-height (1em) from the previously set height. */
+    height: calc(var(--preview-height) - 1em - (2 * var(--progress-padding)));
+}
+
 .content-view.canvas-overview .content-view.canvas > .preview > img {
     border-radius: 4px;
     box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.58);