Diff
Modified: trunk/Source/WebInspectorUI/ChangeLog (235094 => 235095)
--- trunk/Source/WebInspectorUI/ChangeLog 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/ChangeLog 2018-08-20 21:31:55 UTC (rev 235095)
@@ -1,3 +1,98 @@
+2018-08-20 Devin Rousso <[email protected]>
+
+ Web Inspector: Canvas tab: allow recording processing to be stopped midway
+ https://bugs.webkit.org/show_bug.cgi?id=185152
+
+ Reviewed by Joseph Pecoraro.
+
+ Previously, `WI.Recording` used a `WI.YieldableTask` to process every action in such a way
+ as to not block the UI. The downside to this approach was that it used a message view to
+ indicate the progress of this process, and prevented the user from viewing the `WI.Recording`
+ until that process was completed.
+
+ This patch changes `WI.Recording` to instead use `async/await` and fire events whenever a
+ `WI.RecordingAction` (and `WI.RecordingFrame`) finished processing, allowing it to be added
+ to the recording `WI.TreeOutline` and selected by the user. Additionally, a pause/resume
+ button is added to the `WI.CanvasSidebarPanel` so the user has greater control over what
+ how much of the `WI.Recording` they want to process.
+
+ * Localizations/en.lproj/localizedStrings.js:
+
+ * UserInterface/Base/Utilities.js:
+ (Promise.delay)
+ Utility function for promisifying `setTimeout`.
+
+ * UserInterface/Models/Recording.js:
+ (WI.Recording):
+ (WI.Recording.prototype.get processing): Added.
+ (WI.Recording.prototype.get ready): Added.
+ (WI.Recording.prototype.startProcessing): Added.
+ (WI.Recording.prototype.stopProcessing): Added.
+ (WI.Recording.prototype.async._process): Added.
+ (WI.Recording.prototype.process): Deleted.
+ (WI.Recording.prototype.async.yieldableTaskWillProcessItem): Deleted.
+ (WI.Recording.prototype.async.yieldableTaskDidFinish): Deleted.
+
+ * UserInterface/Models/RecordingAction.js:
+ (WI.RecordingAction):
+ (WI.RecordingAction.prototype.get ready): Added.
+ (WI.RecordingAction.prototype.async.swizzle):
+ (WI.RecordingAction.prototype.apply):
+
+ * UserInterface/Models/RecordingInitialStateAction.js:
+ (WI.RecordingInitialStateAction):
+
+ * UserInterface/Views/CanvasSidebarPanel.js:
+ (WI.CanvasSidebarPanel):
+ (WI.CanvasSidebarPanel.prototype.set recording):
+ (WI.CanvasSidebarPanel.prototype.set action):
+ (WI.CanvasSidebarPanel.prototype._recordingAdded):
+ (WI.CanvasSidebarPanel.prototype._recordingRemoved):
+ (WI.CanvasSidebarPanel.prototype._currentRepresentedObjectsDidChange):
+ (WI.CanvasSidebarPanel.prototype._treeOutlineSelectionDidChange):
+ (WI.CanvasSidebarPanel.prototype._recordingChanged):
+ (WI.CanvasSidebarPanel.prototype._recordingChanged.createPauseButton): Added.
+ (WI.CanvasSidebarPanel.prototype._recordingChanged.createResumeButton): Added.
+ (WI.CanvasSidebarPanel.prototype._createRecordingFrameTreeElement): Added.
+ (WI.CanvasSidebarPanel.prototype._createRecordingActionTreeElement): Added.
+ (WI.CanvasSidebarPanel.prototype._handleRecordingProcessedAction): Added.
+ (WI.CanvasSidebarPanel.prototype._handleRecordingStartProcessingFrame): Added.
+ * UserInterface/Views/CanvasSidebarPanel.css:
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle > progress): Added.
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline:matches(:focus, .force-focus) .item.processing.selected .subtitle > progress): Added.
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle::before): Added.
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options): Added.
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options > .indeterminate-progress-spinner): Added.
+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner): Deleted.
+
+ * UserInterface/Views/RecordingContentView.js:
+ (WI.RecordingContentView):
+ (WI.RecordingContentView.prototype.updateActionIndex):
+ (WI.RecordingContentView.prototype.initialLayout):
+ (WI.RecordingContentView.prototype._updateCanvasPath):
+ (WI.RecordingContentView.prototype._handleRecordingProcessedAction): Added.
+ (WI.RecordingContentView.prototype._updateProcessProgress): Deleted.
+ (WI.RecordingContentView.prototype._handleRecordingProcessedActionSwizzle): Deleted.
+ (WI.RecordingContentView.prototype._handleRecordingProcessedActionApply): Deleted.
+ * UserInterface/Views/RecordingContentView.css:
+ (.content-view:not(.tab).recording > header > .slider-container > .slider-value): Added.
+
+ * UserInterface/Views/FolderTreeElement.js:
+ (WI.FolderTreeElement):
+
+ * UserInterface/Views/GeneralTreeElement.js:
+ (WI.GeneralTreeElement.prototype.get statusElement): Added.
+ (WI.GeneralTreeElement.prototype._updateTitleElements):
+
+ * UserInterface/Views/RecordingContentView.js:
+ (WI.CanvasContentView.prototype._handleViewShaderButtonClicked):
+ (WI.CanvasContentView.prototype._handleViewRecordingButtonClicked):
+ Drive-by: `WI.Collection` doesn't have a `values()` accessor for the underlying `Set`.
+
+ * UserInterface/Views/RecordingNavigationSidebarPanel.css: Removed.
+ * UserInterface/Views/RecordingNavigationSidebarPanel.js: Removed.
+ These files are no longer used since they were "merged" into `WI.CanvasSidebarPanel`.
+
2018-08-20 Devin Rousso <[email protected]>
Web Inspector: Canvas tab: create icons for recordings/shaders in the preview tile
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -568,7 +568,6 @@
localizedStrings["Live Size"] = "Live Size";
localizedStrings["Load \u2014 %s"] = "Load \u2014 %s";
localizedStrings["Load cancelled"] = "Load cancelled";
-localizedStrings["Loading Recording"] = "Loading Recording";
localizedStrings["Local File"] = "Local File";
localizedStrings["Local Storage"] = "Local Storage";
localizedStrings["Local Variables"] = "Local Variables";
@@ -641,7 +640,6 @@
localizedStrings["No Properties"] = "No Properties";
localizedStrings["No Properties \u2014 Click to Edit"] = "No Properties \u2014 Click to Edit";
localizedStrings["No Query Parameters"] = "No Query Parameters";
-localizedStrings["No Recording Data"] = "No Recording Data";
localizedStrings["No Request Headers"] = "No Request Headers";
localizedStrings["No Response Headers"] = "No Response Headers";
localizedStrings["No Results Found"] = "No Results Found";
@@ -704,6 +702,7 @@
localizedStrings["Partial Garbage Collection"] = "Partial Garbage Collection";
localizedStrings["Passive"] = "Passive";
localizedStrings["Path"] = "Path";
+localizedStrings["Pause Processing"] = "Pause Processing";
localizedStrings["Pause Reason"] = "Pause Reason";
localizedStrings["Pause script execution (%s or %s)"] = "Pause script execution (%s or %s)";
localizedStrings["Ping"] = "Ping";
@@ -730,7 +729,6 @@
localizedStrings["Probe Sample Recorded"] = "Probe Sample Recorded";
localizedStrings["Probes"] = "Probes";
localizedStrings["Processing Instruction"] = "Processing Instruction";
-localizedStrings["Processing Recording"] = "Processing Recording";
localizedStrings["Program %d"] = "Program %d";
localizedStrings["Properties"] = "Properties";
localizedStrings["Property"] = "Property";
@@ -798,6 +796,7 @@
localizedStrings["Response:"] = "Response:";
localizedStrings["Restart (%s)"] = "Restart (%s)";
localizedStrings["Restart animation"] = "Restart animation";
+localizedStrings["Resume Processing"] = "Resume Processing";
localizedStrings["Resume Thread"] = "Resume Thread";
localizedStrings["Retained Size"] = "Retained Size";
localizedStrings["Return type for anonymous function"] = "Return type for anonymous function";
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -1337,6 +1337,14 @@
}
});
+Object.defineProperty(Promise, "delay",
+{
+ value(delay)
+ {
+ return new Promise((resolve) => setTimeout(resolve, delay || 0));
+ }
+});
+
(function() {
// The `debounce` function lets you call any function on an object with a delay
// and if the function keeps getting called, the delay gets reset. Since `debounce`
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -41,10 +41,8 @@
this._visualActionIndexes = [];
this._source = null;
- this._swizzleTask = null;
- this._applyTask = null;
this._processContext = null;
- this._processPromise = null;
+ this._processing = false;
}
static fromPayload(payload, frames)
@@ -162,20 +160,35 @@
get source() { return this._source; }
set source(source) { this._source = source; }
- process()
+ get processing() { return this._processing; }
+
+ get ready()
{
- if (!this._processPromise) {
- this._processPromise = new WI.WrappedPromise;
+ return this._actions.lastValue.ready;
+ }
- let items = this._actions.map((action, index) => { return {action, index} });
- this._swizzleTask = new WI.YieldableTask(this, items);
- this._applyTask = new WI.YieldableTask(this, items);
+ startProcessing()
+ {
+ console.assert(!this._processing, "Cannot start an already started process().");
+ console.assert(!this.ready, "Cannot start a completed process().");
+ if (this._processing || this.ready)
+ return;
- this._swizzleTask.start();
- }
- return this._processPromise.promise;
+ this._processing = true;
+
+ this._process();
}
+ stopProcessing()
+ {
+ console.assert(this._processing, "Cannot stop an already stopped process().");
+ console.assert(!this.ready, "Cannot stop a completed process().");
+ if (!this._processing || this.ready)
+ return;
+
+ this._processing = false;
+ }
+
createDisplayName(suggestedName)
{
let recordingNameSet;
@@ -332,29 +345,11 @@
};
}
- // YieldableTask delegate
+ // Private
- async yieldableTaskWillProcessItem(task, item)
+ async _process()
{
- if (task === this._swizzleTask) {
- await item.action.swizzle(this);
-
- this.dispatchEventToListeners(WI.Recording.Event.ProcessedActionSwizzle, {index: item.index});
- } else if (task === this._applyTask) {
- item.action.process(this, this._processContext);
-
- if (item.action.isVisual)
- this._visualActionIndexes.push(item.index);
-
- this.dispatchEventToListeners(WI.Recording.Event.ProcessedActionApply, {index: item.index});
- }
- }
-
- async yieldableTaskDidFinish(task)
- {
- if (task === this._swizzleTask) {
- this._swizzleTask = null;
-
+ if (!this._processContext) {
this._processContext = this.createContext();
if (this._type === WI.Recording.Type.Canvas2D) {
@@ -411,19 +406,68 @@
} catch { }
}
}
+ }
- this._applyTask.start();
- } else if (task === this._applyTask) {
- this._applyTask = null;
- this._processContext = null;
- this._processPromise.resolve();
+ // The first action is always a WI.RecordingInitialStateAction, which doesn't need to swizzle().
+ // Since it is not associated with a WI.RecordingFrame, it has to manually process().
+ if (!this._actions[0].ready) {
+ this._actions[0].process(this, this._processContext);
+ this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action: this._actions[0], index: 0});
}
+
+ const workInterval = 10;
+ let startTime = Date.now();
+
+ let cumulativeActionIndex = 0;
+ for (let frameIndex = 0; frameIndex < this._frames.length; ++frameIndex) {
+ let frame = this._frames[frameIndex];
+
+ if (frame.actions.lastValue.ready) {
+ cumulativeActionIndex += frame.actions.length;
+ continue;
+ }
+
+ for (let actionIndex = 0; actionIndex < frame.actions.length; ++actionIndex) {
+ ++cumulativeActionIndex;
+
+ let action = ""
+ if (action.ready)
+ continue;
+
+ await action.swizzle(this);
+
+ action.process(this, this._processContext);
+
+ if (action.isVisual)
+ this._visualActionIndexes.push(cumulativeActionIndex);
+
+ if (!actionIndex)
+ this.dispatchEventToListeners(WI.Recording.Event.StartProcessingFrame, {frame, index: frameIndex});
+
+ this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action, index: cumulativeActionIndex});
+
+ if (Date.now() - startTime > workInterval) {
+ await Promise.delay(); // yield
+
+ startTime = Date.now();
+ }
+
+ if (!this._processing)
+ return;
+ }
+
+ if (!this._processing)
+ return;
+ }
+
+ this._processContext = null;
+ this._processing = false;
}
};
WI.Recording.Event = {
- ProcessedActionApply: "recording-processed-action-apply",
- ProcessedActionSwizzle: "recording-processed-action-swizzle",
+ ProcessedAction: "recording-processed-action",
+ StartProcessingFrame: "recording-start-processing-frame",
};
WI.Recording._importedRecordingNameSet = new Set;
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -48,6 +48,9 @@
this._state = null;
this._stateModifiers = new Set;
+
+ this._swizzled = false;
+ this._processed = false;
}
// Static
@@ -108,8 +111,18 @@
get state() { return this._state; }
get stateModifiers() { return this._stateModifiers; }
+ get ready()
+ {
+ return this._swizzled && this._processed;
+ }
+
process(recording, context)
{
+ console.assert(this._swizzled, "You must swizzle() before you can process().");
+ console.assert(!this._processed, "You should only process() once.");
+
+ this._processed = true;
+
if (recording.type === WI.Recording.Type.CanvasWebGL) {
// We add each RecordingAction to the list of visualActionIndexes after it is processed.
if (this._valid && this._isVisual) {
@@ -187,8 +200,12 @@
async swizzle(recording)
{
- if (!this._valid)
+ console.assert(!this._swizzled, "You should only swizzle() once.");
+
+ if (!this._valid) {
+ this._swizzled = true;
return;
+ }
let swizzleParameter = (item, index) => {
return recording.swizzle(item, this._payloadSwizzleTypes[index]);
@@ -254,10 +271,15 @@
this._stateModifiers.add(item);
}
}
+
+ this._swizzled = true;
}
apply(context, options = {})
{
+ console.assert(this._swizzled, "You must swizzle() before you can apply().");
+ console.assert(this._processed, "You must process() before you can apply().");
+
if (!this.valid)
return;
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -32,5 +32,7 @@
this._name = WI.UIString("Initial State");
this._valid = false;
+
+ this._swizzled = true;
}
};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -435,7 +435,7 @@
return;
if (shaderPrograms.size === 1) {
- WI.showRepresentedObject(shaderPrograms.values().next().value);
+ WI.showRepresentedObject(Array.from(shaderPrograms)[0]);
return;
}
@@ -458,7 +458,7 @@
return;
if (recordings.size === 1) {
- WI.showRepresentedObject(recordings.values().next().value);
+ WI.showRepresentedObject(Array.from(recordings)[0]);
return;
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css 2018-08-20 21:31:55 UTC (rev 235095)
@@ -72,6 +72,27 @@
line-height: 16px;
}
-.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner {
- margin: 16px auto;
+.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle > progress {
+ width: 100%;
+ max-width: 100px;
+ margin: 2px 4px 0;
}
+
+.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline:matches(:focus, .force-focus) .item.processing.selected .subtitle > progress {
+ filter: brightness(10);
+}
+
+.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle::before {
+ content: "";
+}
+
+.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 16px 0;
+}
+
+.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options > .indeterminate-progress-spinner {
+ margin-bottom: 4px;
+}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -68,8 +68,9 @@
WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStarted, this._updateRecordNavigationItem, this);
WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._updateRecordNavigationItem, this);
- this._recordingProcessPromise = null;
- this._recordingProcessSpinner = null;
+ this._recordingProcessingOptionsContainer = null;
+
+ this._selectedRecordingActionIndex = NaN;
}
// Public
@@ -103,18 +104,32 @@
if (recording === this._recording)
return;
+ if (this._recording) {
+ this._recording.removeEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
+ this._recording.removeEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
+ }
+
if (recording)
this.canvas = recording.source;
this._recording = recording;
+
+ if (this._recording) {
+ this._recording.addEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
+ this._recording.addEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
+ }
+
this._recordingChanged();
}
set action(action)
{
- if (!this._recording || this._recordingProcessPromise)
+ if (!this._recording)
return;
+ if (action ="" this._recording.actions[this._selectedRecordingActionIndex])
+ return;
+
let selectedTreeElement = this._recordingTreeOutline.selectedTreeElement;
if (!action) {
if (selectedTreeElement)
@@ -138,6 +153,8 @@
const omitFocus = false;
const selectedByUser = false;
treeElement.revealAndSelect(omitFocus, selectedByUser);
+
+ this._selectedRecordingActionIndex = this._recording.actions.indexOf(action);
}
shown()
@@ -204,19 +221,19 @@
_recordingAdded(event)
{
- this.recording = event.data.item;
-
this._updateRecordNavigationItem();
this._updateRecordingScopeBar();
+
+ this.recording = event.data.item;
}
_recordingRemoved(event)
{
+ this._updateRecordingScopeBar();
+
let recording = event.data.item;
if (recording === this.recording)
this.recording = this._canvas ? Array.from(this._canvas.recordingCollection).lastValue : null;
-
- this._updateRecordingScopeBar();
}
_scopeBarSelectionChanged()
@@ -260,11 +277,12 @@
let recording = objects.find((object) => object instanceof WI.Recording);
if (recording) {
+ this.recording = recording;
+
let recordingAction = objects.find((object) => object instanceof WI.RecordingAction);
if (recordingAction !== recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
this.action = ""
- this.recording = recording;
return;
}
@@ -297,8 +315,11 @@
const _onlyExisting_ = true;
let recordingContentView = this.contentBrowser.contentViewForRepresentedObject(this._recording, onlyExisting);
- if (recordingContentView)
- recordingContentView.updateActionIndex(treeElement.index);
+ if (!recordingContentView)
+ return;
+
+ this._selectedRecordingActionIndex = treeElement.index;
+ recordingContentView.updateActionIndex(this._selectedRecordingActionIndex);
}
_canvasChanged()
@@ -329,66 +350,86 @@
{
this._recordingTreeOutline.removeChildren();
- if (!this._recording)
+ if (!this._recording) {
+ if (this._recordingProcessingOptionsContainer) {
+ this._recordingProcessingOptionsContainer.remove();
+ this._recordingProcessingOptionsContainer = null;
+ }
return;
-
- if (!this._recordingProcessSpinner) {
- this._recordingProcessSpinner = new WI.IndeterminateProgressSpinner;
- this._recordingContentContainer.appendChild(this._recordingProcessSpinner.element);
}
- this.contentBrowser.showContentViewForRepresentedObject(this._recording);
+ if (!this._recording.ready) {
+ if (!this._recording.processing)
+ this._recording.startProcessing();
- let recording = this._recording;
+ if (!this._recordingProcessingOptionsContainer) {
+ this._recordingProcessingOptionsContainer = this._recordingContentContainer.appendChild(document.createElement("div"));
+ this._recordingProcessingOptionsContainer.classList.add("recording-processing-options");
- let promise = this._recording.process().then(() => {
- if (recording !== this._recording || promise !== this._recordingProcessPromise)
- return;
+ let createPauseButton = () => {
+ let spinner = new WI.IndeterminateProgressSpinner;
+ this._recordingProcessingOptionsContainer.appendChild(spinner.element);
- this._recordingProcessPromise = null;
+ let pauseButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
+ pauseButton.textContent = WI.UIString("Pause Processing");
+ pauseButton.addEventListener("click", (event) => {
+ this._recording.stopProcessing();
- if (this._recordingProcessSpinner) {
- this._recordingProcessSpinner.element.remove();
- this._recordingProcessSpinner = null;
+ spinner.element.remove();
+ pauseButton.remove();
+ createResumeButton();
+ });
+ };
+
+ let createResumeButton = () => {
+ let resumeButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
+ resumeButton.textContent = WI.UIString("Resume Processing");
+ resumeButton.addEventListener("click", (event) => {
+ this._recording.startProcessing();
+
+ resumeButton.remove();
+ createPauseButton();
+ });
+ };
+
+ if (this._recording.processing)
+ createPauseButton();
+ else
+ createResumeButton();
}
+ }
- this._recordingTreeOutline.element.dataset.indent = Number.countDigits(this._recording.actions.length);
+ this.contentBrowser.showContentViewForRepresentedObject(this._recording);
- if (this._recording.actions[0] instanceof WI.RecordingInitialStateAction)
- this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type));
+ if (this._scopeBar) {
+ let scopeBarItem = this._scopeBar.item(this._recording.displayName);
+ console.assert(scopeBarItem, "Missing scopeBarItem for recording.", this._recording);
+ scopeBarItem.selected = true;
+ }
- let cumulativeActionIndex = 1;
- this._recording.frames.forEach((frame, frameIndex) => {
- let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((frameIndex + 1).toLocaleString()));
- this._recordingTreeOutline.appendChild(folder);
+ if (this._recording.actions[0].ready) {
+ this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type));
- for (let i = 0; i < frame.actions.length; ++i)
- folder.appendChild(new WI.RecordingActionTreeElement(frame.actions[i], cumulativeActionIndex + i, this._recording.type));
+ if (!this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
+ this.action = ""
+ }
- if (!isNaN(frame.duration)) {
- const higherResolution = true;
- folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
- }
+ let cumulativeActionIndex = 0;
+ this._recording.frames.forEach((frame, frameIndex) => {
+ if (!frame.actions[0].ready)
+ return;
- if (frame.incomplete)
- folder.subtitle = WI.UIString("Incomplete");
+ let folder = this._createRecordingFrameTreeElement(frame, frameIndex, this._recordingTreeOutline);
- if (this._recording.frames.length === 1)
- folder.expand();
+ for (let action of frame.actions) {
+ if (!action.ready)
+ break;
- cumulativeActionIndex += frame.actions.length;
- });
+ ++cumulativeActionIndex;
- if (this._scopeBar) {
- let scopeBarItem = this._scopeBar.item(this._recording.displayName);
- console.assert(scopeBarItem, "Missing scopeBarItem for recording.", this._recording);
- scopeBarItem.selected = true;
+ this._createRecordingActionTreeElement(action, cumulativeActionIndex, folder);
}
-
- this.action = "" || this._recording.actions[0];
});
-
- this._recordingProcessPromise = promise;
}
_updateRecordNavigationItem()
@@ -447,6 +488,79 @@
this._scopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionChanged, this);
this._recordingNavigationBar.insertNavigationItem(this._scopeBar, 0);
}
+
+ _createRecordingFrameTreeElement(frame, index, parent)
+ {
+ let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((index + 1).toLocaleString()), frame);
+
+ if (!isNaN(frame.duration)) {
+ const higherResolution = true;
+ folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
+ }
+
+ parent.appendChild(folder);
+
+ return folder;
+ }
+
+ _createRecordingActionTreeElement(action, index, parent)
+ {
+ let treeElement = new WI.RecordingActionTreeElement(action, index, this._recording.type);
+
+ parent.appendChild(treeElement);
+
+ if (parent instanceof WI.FolderTreeElement && parent.representedObject instanceof WI.RecordingFrame) {
+ if (action !== parent.representedObject.actions.lastValue) {
+ parent.addClassName("processing");
+
+ if (!(parent.subtitle instanceof HTMLProgressElement))
+ parent.subtitle = document.createElement("progress");
+
+ if (parent.statusElement)
+ parent.subtitle.style.setProperty("width", `calc(100% - ${parent.statusElement.offsetWidth + 4}px`);
+
+ parent.subtitle.value = parent.representedObject.actions.indexOf(action) / parent.representedObject.actions.length;
+ } else {
+ parent.removeClassName("processing");
+ if (parent.representedObject.incomplete)
+ parent.subtitle = WI.UIString("Incomplete");
+ else
+ parent.subtitle = "";
+ }
+ }
+
+ if (action ="" this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
+ this.action = ""
+
+ return treeElement;
+ }
+
+ _handleRecordingProcessedAction(event)
+ {
+ let {action, index} = event.data;
+
+ this._recordingTreeOutline.element.dataset.indent = Number.countDigits(index);
+
+ let isInitialStateAction = !index;
+
+ console.assert(isInitialStateAction || this._recordingTreeOutline.children.lastValue instanceof WI.FolderTreeElement, "There should be a WI.FolderTreeElement for the frame for this action.");
+ this._createRecordingActionTreeElement(action, index, isInitialStateAction ? this._recordingTreeOutline : this._recordingTreeOutline.children.lastValue);
+
+ if (isInitialStateAction && !this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
+ this.action = ""
+
+ if (action ="" this._recording.actions.lastValue && this._recordingProcessingOptionsContainer) {
+ this._recordingProcessingOptionsContainer.remove();
+ this._recordingProcessingOptionsContainer = null;
+ }
+ }
+
+ _handleRecordingStartProcessingFrame(event)
+ {
+ let {frame, index} = event.data;
+
+ this._createRecordingFrameTreeElement(frame, index, this._recordingTreeOutline);
+ }
};
WI.CanvasSidebarPanel.SelectedActionSymbol = Symbol("selected-action");
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/FolderTreeElement.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/FolderTreeElement.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/FolderTreeElement.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -27,8 +27,6 @@
{
constructor(title, representedObject)
{
- console.assert(!representedObject || representedObject instanceof WI.Collection);
-
const classNames = [WI.FolderTreeElement.FolderIconStyleClassName];
const subtitle = null;
super(classNames, title, subtitle, representedObject, {hasChildren: true});
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/GeneralTreeElement.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/GeneralTreeElement.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/GeneralTreeElement.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -50,6 +50,11 @@
return this._iconElement;
}
+ get statusElement()
+ {
+ return this._statusElement;
+ }
+
get titlesElement()
{
this._createElementsIfNeeded();
@@ -301,6 +306,7 @@
this._createSubtitleElementIfNeeded();
this._subtitleElement.removeChildren();
this._subtitleElement.appendChild(this._subtitle);
+ this._titlesElement.classList.remove(WI.GeneralTreeElement.NoSubtitleStyleClassName);
} else {
if (this._subtitleElement)
this._subtitleElement.textContent = "";
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css 2018-08-20 21:31:55 UTC (rev 235095)
@@ -47,6 +47,11 @@
margin: 4px 8px;
}
+.content-view:not(.tab).recording > header > .slider-container > .slider-value {
+ font-family: Menlo, monospace;
+ font-family: 11px;
+}
+
.content-view:not(.tab).recording > header > .slider-container > input[type=range] {
flex: 1;
width: 100%;
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -61,9 +61,6 @@
this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High;
this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this._exportRecording(); });
}
-
- this._processing = true;
- this._processMessageTextView = null;
}
// Static
@@ -123,9 +120,6 @@
this._index = index;
- if (this._processing)
- return;
-
this._updateSliderValue();
if (this.representedObject.type === WI.Recording.Type.Canvas2D)
@@ -178,7 +172,7 @@
let previewHeader = this.element.appendChild(document.createElement("header"));
let sliderContainer = previewHeader.appendChild(document.createElement("div"));
- sliderContainer.className = "slider-container hidden";
+ sliderContainer.className = "slider-container";
this._previewContainer = this.element.appendChild(document.createElement("div"));
this._previewContainer.className = "preview-container";
@@ -192,25 +186,7 @@
this._sliderElement.min = 0;
this._sliderElement.max = 0;
- this.representedObject.addEventListener(WI.Recording.Event.ProcessedActionSwizzle, this._handleRecordingProcessedActionSwizzle, this);
- this.representedObject.addEventListener(WI.Recording.Event.ProcessedActionApply, this._handleRecordingProcessedActionApply, this);
-
- this.representedObject.process().then(() => {
- if (this._processMessageTextView)
- this._processMessageTextView.remove();
-
- sliderContainer.classList.remove("hidden");
- this._sliderElement.max = this.representedObject.visualActionIndexes.length;
- this._updateSliderValue();
-
- this._processing = false;
-
- let index = this._index;
- if (!isNaN(index)) {
- this._index = NaN;
- this.updateActionIndex(index);
- }
- });
+ this.representedObject.addEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
}
// Private
@@ -454,7 +430,7 @@
{
let activated = WI.settings.showCanvasPath.value;
- if (this._showPathButtonNavigationItem.activated !== activated && !this._processing)
+ if (this._showPathButtonNavigationItem.activated !== activated)
this._generateContentCanvas2D(this._index);
this._showPathButtonNavigationItem.activated = activated;
@@ -486,18 +462,6 @@
this._sliderValueElement.textContent = WI.UIString("%d of %d").format(visualActionIndex, visualActionIndexes.length);
}
- _updateProcessProgress(message, index)
- {
- if (this._processMessageTextView)
- this._processMessageTextView.remove();
-
- this._processMessageTextView = WI.createMessageTextView(message);
- this.element.appendChild(this._processMessageTextView);
-
- this._processProgressElement = this._processMessageTextView.appendChild(document.createElement("progress"));
- this._processProgressElement.value = index / this.representedObject.actions.length;
- }
-
_showPathButtonClicked(event)
{
WI.settings.showCanvasPath.value = !this._showPathButtonNavigationItem.activated;
@@ -523,15 +487,11 @@
this.updateActionIndex(index);
}
- _handleRecordingProcessedActionSwizzle(event)
+ _handleRecordingProcessedAction(event)
{
- this._updateProcessProgress(WI.UIString("Loading Recording"), event.data.index);
+ this._sliderElement.max = this.representedObject.visualActionIndexes.length;
+ this._updateSliderValue();
}
-
- _handleRecordingProcessedActionApply(event)
- {
- this._updateProcessProgress(WI.UIString("Processing Recording"), event.data.index);
- }
};
WI.RecordingContentView.SnapshotInterval = 5000;
Deleted: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.css (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.css 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.css 2018-08-20 21:31:55 UTC (rev 235095)
@@ -1,40 +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.
- */
-
-.sidebar > .panel.navigation.recording > .content {
- top: var(--navigation-bar-height);
-}
-
-.sidebar > .panel.navigation.recording > .content > .tree-outline {
- min-height: 100%;
-}
-
-.sidebar > .panel.navigation.recording > .content > .tree-outline .item.folder-icon > .icon {
- content: url(../Images/RenderingFrame.svg);
-}
-
-.sidebar > .panel.navigation.recording > .content > .tree-outline .item.folder-icon > .status {
- line-height: 16px;
-}
Deleted: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js (235094 => 235095)
--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js 2018-08-20 21:09:26 UTC (rev 235094)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js 2018-08-20 21:31:55 UTC (rev 235095)
@@ -1,188 +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.RecordingNavigationSidebarPanel = class RecordingNavigationSidebarPanel extends WI.NavigationSidebarPanel
-{
- constructor()
- {
- super("recording", WI.UIString("Recording"));
-
- this.contentTreeOutline.customIndent = true;
- this.contentTreeOutline.registerScrollVirtualizer(this.contentView.element, 20);
-
- this.recording = null;
-
- this._importButton = null;
- this._exportButton = null;
- }
-
- // Public
-
- set recording(recording)
- {
- if (recording === this._recording)
- return;
-
- this.contentTreeOutline.removeChildren();
-
- this._recording = recording;
-
- this.updateEmptyContentPlaceholder(WI.UIString("No Recording Data"));
-
- if (!this._recording) {
- if (this._exportButton)
- this._exportButton.disabled = true;
- return;
- }
-
- this._recording.actions.then((actions) => {
- if (recording !== this._recording)
- return;
-
- this.contentTreeOutline.element.dataset.indent = Number.countDigits(actions.length);
-
- if (actions[0] instanceof WI.RecordingInitialStateAction)
- this.contentTreeOutline.appendChild(new WI.RecordingActionTreeElement(actions[0], 0, this._recording.type));
-
- let cumulativeActionIndex = 1;
- this._recording.frames.forEach((frame, frameIndex) => {
- let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((frameIndex + 1).toLocaleString()));
- this.contentTreeOutline.appendChild(folder);
-
- for (let i = 0; i < frame.actions.length; ++i)
- folder.appendChild(new WI.RecordingActionTreeElement(frame.actions[i], cumulativeActionIndex + i, this._recording.type));
-
- if (!isNaN(frame.duration)) {
- const higherResolution = true;
- folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
- }
-
- if (frame.incomplete)
- folder.subtitle = WI.UIString("Incomplete");
-
- if (this._recording.frames.length === 1)
- folder.expand();
-
- cumulativeActionIndex += frame.actions.length;
- });
-
- this._exportButton.disabled = !actions.length;
-
- let index = this._recording[WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol] || 0;
- this.updateActionIndex(index);
- });
- }
-
- updateActionIndex(index, options = {})
- {
- if (!this._recording)
- return;
-
- this._recording.actions.then((actions) => {
- let recordingAction = actions[index];
- console.assert(recordingAction, "Invalid recording action index.", index);
- if (!recordingAction)
- return;
-
- let treeElement = this.contentTreeOutline.findTreeElement(recordingAction);
- console.assert(treeElement, "Missing tree element for recording action.", recordingAction);
- if (!treeElement)
- return;
-
- this._recording[WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol] = index;
-
- const omitFocus = false;
- const selectedByUser = false;
- const suppressOnSelect = true;
- const suppressOnDeselect = true;
- treeElement.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
- });
- }
-
- // Protected
-
- initialLayout()
- {
- super.initialLayout();
-
- const role = "button";
-
- const importLabel = WI.UIString("Import");
- let importNavigationItem = new WI.NavigationItem("recording-import", role, importLabel);
-
- this._importButton = importNavigationItem.element.appendChild(document.createElement("button"));
- this._importButton.textContent = importLabel;
- this._importButton.addEventListener("click", () => { WI.canvasManager.importRecording(); });
-
- const exportLabel = WI.UIString("Export");
- let exportNavigationItem = new WI.NavigationItem("recording-export", role, exportLabel);
-
- this._exportButton = exportNavigationItem.element.appendChild(document.createElement("button"));
- this._exportButton.textContent = exportLabel;
- this._exportButton.disabled = !this.contentTreeOutline.children.length;
- this._exportButton.addEventListener("click", this._exportNavigationItemClicked.bind(this));
-
- const element = null;
- this.addSubview(new WI.NavigationBar(element, [importNavigationItem, exportNavigationItem]));
-
- let filterFunction = (treeElement) => {
- if (!(treeElement instanceof WI.RecordingActionTreeElement))
- return false;
-
- return treeElement.representedObject.isVisual;
- };
-
- const activatedByDefault = false;
- const defaultToolTip = WI.UIString("Only show visual actions");
- const activatedToolTip = WI.UIString("Show all actions");
- this.filterBar.addFilterBarButton("recording-show-visual-only", filterFunction, activatedByDefault, defaultToolTip, activatedToolTip, "Images/Paint.svg", 15, 15);
- }
-
- matchTreeElementAgainstCustomFilters(treeElement)
- {
- // Keep recording frame tree elements.
- if (treeElement instanceof WI.FolderTreeElement)
- return true;
-
- return super.matchTreeElementAgainstCustomFilters(treeElement);
- }
-
- // Private
-
- _exportNavigationItemClicked(event)
- {
- if (!this._recording || !this.contentBrowser || !this.contentBrowser.currentContentView || !this.contentBrowser.currentContentView.supportsSave)
- return;
-
- const forceSaveAs = true;
- WI.saveDataToFile(this.contentBrowser.currentContentView.saveData, forceSaveAs);
- }
-};
-
-WI.RecordingNavigationSidebarPanel.SelectedActionIndexSymbol = Symbol("selected-action-index");
-
-WI.RecordingNavigationSidebarPanel.Event = {
- Import: "recording-navigation-sidebar-panel-import",
-};