Changeset 235095 in webkit


Ignore:
Timestamp:
Aug 20, 2018 2:31:55 PM (6 years ago)
Author:
Devin Rousso
Message:

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.

Location:
trunk/Source/WebInspectorUI
Files:
2 deleted
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r235093 r235095  
     12018-08-20  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Canvas tab: allow recording processing to be stopped midway
     4        https://bugs.webkit.org/show_bug.cgi?id=185152
     5
     6        Reviewed by Joseph Pecoraro.
     7
     8        Previously, `WI.Recording` used a `WI.YieldableTask` to process every action in such a way
     9        as to not block the UI. The downside to this approach was that it used a message view to
     10        indicate the progress of this process, and prevented the user from viewing the `WI.Recording`
     11        until that process was completed.
     12
     13        This patch changes `WI.Recording` to instead use `async/await` and fire events whenever a
     14        `WI.RecordingAction` (and `WI.RecordingFrame`) finished processing, allowing it to be added
     15        to the recording `WI.TreeOutline` and selected by the user. Additionally, a pause/resume
     16        button is added to the `WI.CanvasSidebarPanel` so the user has greater control over what
     17        how much of the `WI.Recording` they want to process.
     18
     19        * Localizations/en.lproj/localizedStrings.js:
     20
     21        * UserInterface/Base/Utilities.js:
     22        (Promise.delay)
     23        Utility function for promisifying `setTimeout`.
     24
     25        * UserInterface/Models/Recording.js:
     26        (WI.Recording):
     27        (WI.Recording.prototype.get processing): Added.
     28        (WI.Recording.prototype.get ready): Added.
     29        (WI.Recording.prototype.startProcessing): Added.
     30        (WI.Recording.prototype.stopProcessing): Added.
     31        (WI.Recording.prototype.async._process): Added.
     32        (WI.Recording.prototype.process): Deleted.
     33        (WI.Recording.prototype.async.yieldableTaskWillProcessItem): Deleted.
     34        (WI.Recording.prototype.async.yieldableTaskDidFinish): Deleted.
     35
     36        * UserInterface/Models/RecordingAction.js:
     37        (WI.RecordingAction):
     38        (WI.RecordingAction.prototype.get ready): Added.
     39        (WI.RecordingAction.prototype.async.swizzle):
     40        (WI.RecordingAction.prototype.apply):
     41
     42        * UserInterface/Models/RecordingInitialStateAction.js:
     43        (WI.RecordingInitialStateAction):
     44
     45        * UserInterface/Views/CanvasSidebarPanel.js:
     46        (WI.CanvasSidebarPanel):
     47        (WI.CanvasSidebarPanel.prototype.set recording):
     48        (WI.CanvasSidebarPanel.prototype.set action):
     49        (WI.CanvasSidebarPanel.prototype._recordingAdded):
     50        (WI.CanvasSidebarPanel.prototype._recordingRemoved):
     51        (WI.CanvasSidebarPanel.prototype._currentRepresentedObjectsDidChange):
     52        (WI.CanvasSidebarPanel.prototype._treeOutlineSelectionDidChange):
     53        (WI.CanvasSidebarPanel.prototype._recordingChanged):
     54        (WI.CanvasSidebarPanel.prototype._recordingChanged.createPauseButton): Added.
     55        (WI.CanvasSidebarPanel.prototype._recordingChanged.createResumeButton): Added.
     56        (WI.CanvasSidebarPanel.prototype._createRecordingFrameTreeElement): Added.
     57        (WI.CanvasSidebarPanel.prototype._createRecordingActionTreeElement): Added.
     58        (WI.CanvasSidebarPanel.prototype._handleRecordingProcessedAction): Added.
     59        (WI.CanvasSidebarPanel.prototype._handleRecordingStartProcessingFrame): Added.
     60        * UserInterface/Views/CanvasSidebarPanel.css:
     61        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle > progress): Added.
     62        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline:matches(:focus, .force-focus) .item.processing.selected .subtitle > progress): Added.
     63        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle::before): Added.
     64        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options): Added.
     65        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options > .indeterminate-progress-spinner): Added.
     66        (.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner): Deleted.
     67
     68        * UserInterface/Views/RecordingContentView.js:
     69        (WI.RecordingContentView):
     70        (WI.RecordingContentView.prototype.updateActionIndex):
     71        (WI.RecordingContentView.prototype.initialLayout):
     72        (WI.RecordingContentView.prototype._updateCanvasPath):
     73        (WI.RecordingContentView.prototype._handleRecordingProcessedAction): Added.
     74        (WI.RecordingContentView.prototype._updateProcessProgress): Deleted.
     75        (WI.RecordingContentView.prototype._handleRecordingProcessedActionSwizzle): Deleted.
     76        (WI.RecordingContentView.prototype._handleRecordingProcessedActionApply): Deleted.
     77        * UserInterface/Views/RecordingContentView.css:
     78        (.content-view:not(.tab).recording > header > .slider-container > .slider-value): Added.
     79
     80        * UserInterface/Views/FolderTreeElement.js:
     81        (WI.FolderTreeElement):
     82
     83        * UserInterface/Views/GeneralTreeElement.js:
     84        (WI.GeneralTreeElement.prototype.get statusElement): Added.
     85        (WI.GeneralTreeElement.prototype._updateTitleElements):
     86
     87        * UserInterface/Views/RecordingContentView.js:
     88        (WI.CanvasContentView.prototype._handleViewShaderButtonClicked):
     89        (WI.CanvasContentView.prototype._handleViewRecordingButtonClicked):
     90        Drive-by: `WI.Collection` doesn't have a `values()` accessor for the underlying `Set`.
     91
     92        * UserInterface/Views/RecordingNavigationSidebarPanel.css: Removed.
     93        * UserInterface/Views/RecordingNavigationSidebarPanel.js: Removed.
     94        These files are no longer used since they were "merged" into `WI.CanvasSidebarPanel`.
     95
    1962018-08-20  Devin Rousso  <webkit@devinrousso.com>
    297
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r235093 r235095  
    569569localizedStrings["Load \u2014 %s"] = "Load \u2014 %s";
    570570localizedStrings["Load cancelled"] = "Load cancelled";
    571 localizedStrings["Loading Recording"] = "Loading Recording";
    572571localizedStrings["Local File"] = "Local File";
    573572localizedStrings["Local Storage"] = "Local Storage";
     
    642641localizedStrings["No Properties \u2014 Click to Edit"] = "No Properties \u2014 Click to Edit";
    643642localizedStrings["No Query Parameters"] = "No Query Parameters";
    644 localizedStrings["No Recording Data"] = "No Recording Data";
    645643localizedStrings["No Request Headers"] = "No Request Headers";
    646644localizedStrings["No Response Headers"] = "No Response Headers";
     
    705703localizedStrings["Passive"] = "Passive";
    706704localizedStrings["Path"] = "Path";
     705localizedStrings["Pause Processing"] = "Pause Processing";
    707706localizedStrings["Pause Reason"] = "Pause Reason";
    708707localizedStrings["Pause script execution (%s or %s)"] = "Pause script execution (%s or %s)";
     
    731730localizedStrings["Probes"] = "Probes";
    732731localizedStrings["Processing Instruction"] = "Processing Instruction";
    733 localizedStrings["Processing Recording"] = "Processing Recording";
    734732localizedStrings["Program %d"] = "Program %d";
    735733localizedStrings["Properties"] = "Properties";
     
    799797localizedStrings["Restart (%s)"] = "Restart (%s)";
    800798localizedStrings["Restart animation"] = "Restart animation";
     799localizedStrings["Resume Processing"] = "Resume Processing";
    801800localizedStrings["Resume Thread"] = "Resume Thread";
    802801localizedStrings["Retained Size"] = "Retained Size";
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r230021 r235095  
    13381338});
    13391339
     1340Object.defineProperty(Promise, "delay",
     1341{
     1342    value(delay)
     1343    {
     1344        return new Promise((resolve) => setTimeout(resolve, delay || 0));
     1345    }
     1346});
     1347
    13401348(function() {
    13411349    // The `debounce` function lets you call any function on an object with a delay
  • trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js

    r231218 r235095  
    4242        this._source = null;
    4343
    44         this._swizzleTask = null;
    45         this._applyTask = null;
    4644        this._processContext = null;
    47         this._processPromise = null;
     45        this._processing = false;
    4846    }
    4947
     
    163161    set source(source) { this._source = source; }
    164162
    165     process()
    166     {
    167         if (!this._processPromise) {
    168             this._processPromise = new WI.WrappedPromise;
    169 
    170             let items = this._actions.map((action, index) => { return {action, index} });
    171             this._swizzleTask = new WI.YieldableTask(this, items);
    172             this._applyTask = new WI.YieldableTask(this, items);
    173 
    174             this._swizzleTask.start();
    175         }
    176         return this._processPromise.promise;
     163    get processing() { return this._processing; }
     164
     165    get ready()
     166    {
     167        return this._actions.lastValue.ready;
     168    }
     169
     170    startProcessing()
     171    {
     172        console.assert(!this._processing, "Cannot start an already started process().");
     173        console.assert(!this.ready, "Cannot start a completed process().");
     174        if (this._processing || this.ready)
     175            return;
     176
     177        this._processing = true;
     178
     179        this._process();
     180    }
     181
     182    stopProcessing()
     183    {
     184        console.assert(this._processing, "Cannot stop an already stopped process().");
     185        console.assert(!this.ready, "Cannot stop a completed process().");
     186        if (!this._processing || this.ready)
     187            return;
     188
     189        this._processing = false;
    177190    }
    178191
     
    333346    }
    334347
    335     // YieldableTask delegate
    336 
    337     async yieldableTaskWillProcessItem(task, item)
    338     {
    339         if (task === this._swizzleTask) {
    340             await item.action.swizzle(this);
    341 
    342             this.dispatchEventToListeners(WI.Recording.Event.ProcessedActionSwizzle, {index: item.index});
    343         } else if (task === this._applyTask) {
    344             item.action.process(this, this._processContext);
    345 
    346             if (item.action.isVisual)
    347                 this._visualActionIndexes.push(item.index);
    348 
    349             this.dispatchEventToListeners(WI.Recording.Event.ProcessedActionApply, {index: item.index});
    350         }
    351     }
    352 
    353     async yieldableTaskDidFinish(task)
    354     {
    355         if (task === this._swizzleTask) {
    356             this._swizzleTask = null;
    357 
     348    // Private
     349
     350    async _process()
     351    {
     352        if (!this._processContext) {
    358353            this._processContext = this.createContext();
    359354
     
    412407                }
    413408            }
    414 
    415             this._applyTask.start();
    416         } else if (task === this._applyTask) {
    417             this._applyTask = null;
    418             this._processContext = null;
    419             this._processPromise.resolve();
    420         }
     409        }
     410
     411        // The first action is always a WI.RecordingInitialStateAction, which doesn't need to swizzle().
     412        // Since it is not associated with a WI.RecordingFrame, it has to manually process().
     413        if (!this._actions[0].ready) {
     414            this._actions[0].process(this, this._processContext);
     415            this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action: this._actions[0], index: 0});
     416        }
     417
     418        const workInterval = 10;
     419        let startTime = Date.now();
     420
     421        let cumulativeActionIndex = 0;
     422        for (let frameIndex = 0; frameIndex < this._frames.length; ++frameIndex) {
     423            let frame = this._frames[frameIndex];
     424
     425            if (frame.actions.lastValue.ready) {
     426                cumulativeActionIndex += frame.actions.length;
     427                continue;
     428            }
     429
     430            for (let actionIndex = 0; actionIndex < frame.actions.length; ++actionIndex) {
     431                ++cumulativeActionIndex;
     432
     433                let action = frame.actions[actionIndex];
     434                if (action.ready)
     435                    continue;
     436
     437                await action.swizzle(this);
     438
     439                action.process(this, this._processContext);
     440
     441                if (action.isVisual)
     442                    this._visualActionIndexes.push(cumulativeActionIndex);
     443
     444                if (!actionIndex)
     445                    this.dispatchEventToListeners(WI.Recording.Event.StartProcessingFrame, {frame, index: frameIndex});
     446
     447                this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action, index: cumulativeActionIndex});
     448
     449                if (Date.now() - startTime > workInterval) {
     450                    await Promise.delay(); // yield
     451
     452                    startTime = Date.now();
     453                }
     454
     455                if (!this._processing)
     456                    return;
     457            }
     458
     459            if (!this._processing)
     460                return;
     461        }
     462
     463        this._processContext = null;
     464        this._processing = false;
    421465    }
    422466};
    423467
    424468WI.Recording.Event = {
    425     ProcessedActionApply: "recording-processed-action-apply",
    426     ProcessedActionSwizzle: "recording-processed-action-swizzle",
     469    ProcessedAction: "recording-processed-action",
     470    StartProcessingFrame: "recording-start-processing-frame",
    427471};
    428472
  • trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js

    r231981 r235095  
    4949        this._state = null;
    5050        this._stateModifiers = new Set;
     51
     52        this._swizzled = false;
     53        this._processed = false;
    5154    }
    5255
     
    109112    get stateModifiers() { return this._stateModifiers; }
    110113
     114    get ready()
     115    {
     116        return this._swizzled && this._processed;
     117    }
     118
    111119    process(recording, context)
    112120    {
     121        console.assert(this._swizzled, "You must swizzle() before you can process().");
     122        console.assert(!this._processed, "You should only process() once.");
     123
     124        this._processed = true;
     125
    113126        if (recording.type === WI.Recording.Type.CanvasWebGL) {
    114127            // We add each RecordingAction to the list of visualActionIndexes after it is processed.
     
    188201    async swizzle(recording)
    189202    {
    190         if (!this._valid)
     203        console.assert(!this._swizzled, "You should only swizzle() once.");
     204
     205        if (!this._valid) {
     206            this._swizzled = true;
    191207            return;
     208        }
    192209
    193210        let swizzleParameter = (item, index) => {
     
    255272            }
    256273        }
     274
     275        this._swizzled = true;
    257276    }
    258277
    259278    apply(context, options = {})
    260279    {
     280        console.assert(this._swizzled, "You must swizzle() before you can apply().");
     281        console.assert(this._processed, "You must process() before you can apply().");
     282
    261283        if (!this.valid)
    262284            return;
  • trunk/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js

    r231218 r235095  
    3333
    3434        this._valid = false;
     35
     36        this._swizzled = true;
    3537    }
    3638};
  • trunk/Source/WebInspectorUI/UserInterface/Views/CanvasContentView.js

    r235093 r235095  
    436436
    437437        if (shaderPrograms.size === 1) {
    438             WI.showRepresentedObject(shaderPrograms.values().next().value);
     438            WI.showRepresentedObject(Array.from(shaderPrograms)[0]);
    439439            return;
    440440        }
     
    459459
    460460        if (recordings.size === 1) {
    461             WI.showRepresentedObject(recordings.values().next().value);
     461            WI.showRepresentedObject(Array.from(recordings)[0]);
    462462            return;
    463463        }
  • trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css

    r231218 r235095  
    7373}
    7474
    75 .sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner {
    76     margin: 16px auto;
     75.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle > progress {
     76    width: 100%;
     77    max-width: 100px;
     78    margin: 2px 4px 0;
    7779}
     80
     81.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline:matches(:focus, .force-focus) .item.processing.selected .subtitle > progress {
     82    filter: brightness(10);
     83}
     84
     85.sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.processing .subtitle::before {
     86    content: "";
     87}
     88
     89.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options {
     90    display: flex;
     91    flex-direction: column;
     92    align-items: center;
     93    margin: 16px 0;
     94}
     95
     96.sidebar > .panel.navigation.canvas > .content > .recording-content > .recording-processing-options > .indeterminate-progress-spinner {
     97    margin-bottom: 4px;
     98}
  • trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js

    r235093 r235095  
    6969        WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._updateRecordNavigationItem, this);
    7070
    71         this._recordingProcessPromise = null;
    72         this._recordingProcessSpinner = null;
     71        this._recordingProcessingOptionsContainer = null;
     72
     73        this._selectedRecordingActionIndex = NaN;
    7374    }
    7475
     
    104105            return;
    105106
     107        if (this._recording) {
     108            this._recording.removeEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
     109            this._recording.removeEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
     110        }
     111
    106112        if (recording)
    107113            this.canvas = recording.source;
    108114
    109115        this._recording = recording;
     116
     117        if (this._recording) {
     118            this._recording.addEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
     119            this._recording.addEventListener(WI.Recording.Event.StartProcessingFrame, this._handleRecordingStartProcessingFrame, this);
     120        }
     121
    110122        this._recordingChanged();
    111123    }
     
    113125    set action(action)
    114126    {
    115         if (!this._recording || this._recordingProcessPromise)
     127        if (!this._recording)
     128            return;
     129
     130        if (action === this._recording.actions[this._selectedRecordingActionIndex])
    116131            return;
    117132
     
    139154        const selectedByUser = false;
    140155        treeElement.revealAndSelect(omitFocus, selectedByUser);
     156
     157        this._selectedRecordingActionIndex = this._recording.actions.indexOf(action);
    141158    }
    142159
     
    205222    _recordingAdded(event)
    206223    {
    207         this.recording = event.data.item;
    208 
    209224        this._updateRecordNavigationItem();
    210225        this._updateRecordingScopeBar();
     226
     227        this.recording = event.data.item;
    211228    }
    212229
    213230    _recordingRemoved(event)
    214231    {
     232        this._updateRecordingScopeBar();
     233
    215234        let recording = event.data.item;
    216235        if (recording === this.recording)
    217236            this.recording = this._canvas ? Array.from(this._canvas.recordingCollection).lastValue : null;
    218 
    219         this._updateRecordingScopeBar();
    220237    }
    221238
     
    261278        let recording = objects.find((object) => object instanceof WI.Recording);
    262279        if (recording) {
     280            this.recording = recording;
     281
    263282            let recordingAction = objects.find((object) => object instanceof WI.RecordingAction);
    264283            if (recordingAction !== recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
    265284                this.action = recordingAction;
    266285
    267             this.recording = recording;
    268286            return;
    269287        }
     
    298316        const onlyExisting = true;
    299317        let recordingContentView = this.contentBrowser.contentViewForRepresentedObject(this._recording, onlyExisting);
    300         if (recordingContentView)
    301             recordingContentView.updateActionIndex(treeElement.index);
     318        if (!recordingContentView)
     319            return;
     320
     321        this._selectedRecordingActionIndex = treeElement.index;
     322        recordingContentView.updateActionIndex(this._selectedRecordingActionIndex);
    302323    }
    303324
     
    330351        this._recordingTreeOutline.removeChildren();
    331352
    332         if (!this._recording)
    333             return;
    334 
    335         if (!this._recordingProcessSpinner) {
    336             this._recordingProcessSpinner = new WI.IndeterminateProgressSpinner;
    337             this._recordingContentContainer.appendChild(this._recordingProcessSpinner.element);
     353        if (!this._recording) {
     354            if (this._recordingProcessingOptionsContainer) {
     355                this._recordingProcessingOptionsContainer.remove();
     356                this._recordingProcessingOptionsContainer = null;
     357            }
     358            return;
     359        }
     360
     361        if (!this._recording.ready) {
     362            if (!this._recording.processing)
     363                this._recording.startProcessing();
     364
     365            if (!this._recordingProcessingOptionsContainer) {
     366                this._recordingProcessingOptionsContainer = this._recordingContentContainer.appendChild(document.createElement("div"));
     367                this._recordingProcessingOptionsContainer.classList.add("recording-processing-options");
     368
     369                let createPauseButton = () => {
     370                    let spinner = new WI.IndeterminateProgressSpinner;
     371                    this._recordingProcessingOptionsContainer.appendChild(spinner.element);
     372
     373                    let pauseButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
     374                    pauseButton.textContent = WI.UIString("Pause Processing");
     375                    pauseButton.addEventListener("click", (event) => {
     376                        this._recording.stopProcessing();
     377
     378                        spinner.element.remove();
     379                        pauseButton.remove();
     380                        createResumeButton();
     381                    });
     382                };
     383
     384                let createResumeButton = () => {
     385                    let resumeButton = this._recordingProcessingOptionsContainer.appendChild(document.createElement("button"));
     386                    resumeButton.textContent = WI.UIString("Resume Processing");
     387                    resumeButton.addEventListener("click", (event) => {
     388                        this._recording.startProcessing();
     389
     390                        resumeButton.remove();
     391                        createPauseButton();
     392                    });
     393                };
     394
     395                if (this._recording.processing)
     396                    createPauseButton();
     397                else
     398                    createResumeButton();
     399            }
    338400        }
    339401
    340402        this.contentBrowser.showContentViewForRepresentedObject(this._recording);
    341403
    342         let recording = this._recording;
    343 
    344         let promise = this._recording.process().then(() => {
    345             if (recording !== this._recording || promise !== this._recordingProcessPromise)
     404        if (this._scopeBar) {
     405            let scopeBarItem = this._scopeBar.item(this._recording.displayName);
     406            console.assert(scopeBarItem, "Missing scopeBarItem for recording.", this._recording);
     407            scopeBarItem.selected = true;
     408        }
     409
     410        if (this._recording.actions[0].ready) {
     411            this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type));
     412
     413            if (!this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
     414                this.action = this._recording.actions[0];
     415        }
     416
     417        let cumulativeActionIndex = 0;
     418        this._recording.frames.forEach((frame, frameIndex) => {
     419            if (!frame.actions[0].ready)
    346420                return;
    347421
    348             this._recordingProcessPromise = null;
    349 
    350             if (this._recordingProcessSpinner) {
    351                 this._recordingProcessSpinner.element.remove();
    352                 this._recordingProcessSpinner = null;
     422            let folder = this._createRecordingFrameTreeElement(frame, frameIndex, this._recordingTreeOutline);
     423
     424            for (let action of frame.actions) {
     425                if (!action.ready)
     426                    break;
     427
     428                ++cumulativeActionIndex;
     429
     430                this._createRecordingActionTreeElement(action, cumulativeActionIndex, folder);
    353431            }
    354 
    355             this._recordingTreeOutline.element.dataset.indent = Number.countDigits(this._recording.actions.length);
    356 
    357             if (this._recording.actions[0] instanceof WI.RecordingInitialStateAction)
    358                 this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type));
    359 
    360             let cumulativeActionIndex = 1;
    361             this._recording.frames.forEach((frame, frameIndex) => {
    362                 let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((frameIndex + 1).toLocaleString()));
    363                 this._recordingTreeOutline.appendChild(folder);
    364 
    365                 for (let i = 0; i < frame.actions.length; ++i)
    366                     folder.appendChild(new WI.RecordingActionTreeElement(frame.actions[i], cumulativeActionIndex + i, this._recording.type));
    367 
    368                 if (!isNaN(frame.duration)) {
    369                     const higherResolution = true;
    370                     folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
    371                 }
    372 
    373                 if (frame.incomplete)
    374                     folder.subtitle = WI.UIString("Incomplete");
    375 
    376                 if (this._recording.frames.length === 1)
    377                     folder.expand();
    378 
    379                 cumulativeActionIndex += frame.actions.length;
    380             });
    381 
    382             if (this._scopeBar) {
    383                 let scopeBarItem = this._scopeBar.item(this._recording.displayName);
    384                 console.assert(scopeBarItem, "Missing scopeBarItem for recording.", this._recording);
    385                 scopeBarItem.selected = true;
    386             }
    387 
    388             this.action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] || this._recording.actions[0];
    389432        });
    390 
    391         this._recordingProcessPromise = promise;
    392433    }
    393434
     
    448489        this._recordingNavigationBar.insertNavigationItem(this._scopeBar, 0);
    449490    }
     491
     492    _createRecordingFrameTreeElement(frame, index, parent)
     493    {
     494        let folder = new WI.FolderTreeElement(WI.UIString("Frame %d").format((index + 1).toLocaleString()), frame);
     495
     496        if (!isNaN(frame.duration)) {
     497            const higherResolution = true;
     498            folder.status = Number.secondsToString(frame.duration / 1000, higherResolution);
     499        }
     500
     501        parent.appendChild(folder);
     502
     503        return folder;
     504    }
     505
     506    _createRecordingActionTreeElement(action, index, parent)
     507    {
     508        let treeElement = new WI.RecordingActionTreeElement(action, index, this._recording.type);
     509
     510        parent.appendChild(treeElement);
     511
     512        if (parent instanceof WI.FolderTreeElement && parent.representedObject instanceof WI.RecordingFrame) {
     513            if (action !== parent.representedObject.actions.lastValue) {
     514                parent.addClassName("processing");
     515
     516                if (!(parent.subtitle instanceof HTMLProgressElement))
     517                    parent.subtitle = document.createElement("progress");
     518
     519                if (parent.statusElement)
     520                    parent.subtitle.style.setProperty("width", `calc(100% - ${parent.statusElement.offsetWidth + 4}px`);
     521
     522                parent.subtitle.value = parent.representedObject.actions.indexOf(action) / parent.representedObject.actions.length;
     523            } else {
     524                parent.removeClassName("processing");
     525                if (parent.representedObject.incomplete)
     526                    parent.subtitle = WI.UIString("Incomplete");
     527                else
     528                    parent.subtitle = "";
     529            }
     530        }
     531
     532        if (action === this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
     533            this.action = action;
     534
     535        return treeElement;
     536    }
     537
     538    _handleRecordingProcessedAction(event)
     539    {
     540        let {action, index} = event.data;
     541
     542        this._recordingTreeOutline.element.dataset.indent = Number.countDigits(index);
     543
     544        let isInitialStateAction = !index;
     545
     546        console.assert(isInitialStateAction || this._recordingTreeOutline.children.lastValue instanceof WI.FolderTreeElement, "There should be a WI.FolderTreeElement for the frame for this action.");
     547        this._createRecordingActionTreeElement(action, index, isInitialStateAction ? this._recordingTreeOutline : this._recordingTreeOutline.children.lastValue);
     548
     549        if (isInitialStateAction && !this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol])
     550            this.action = action;
     551
     552        if (action === this._recording.actions.lastValue && this._recordingProcessingOptionsContainer) {
     553            this._recordingProcessingOptionsContainer.remove();
     554            this._recordingProcessingOptionsContainer = null;
     555        }
     556    }
     557
     558    _handleRecordingStartProcessingFrame(event)
     559    {
     560        let {frame, index} = event.data;
     561
     562        this._createRecordingFrameTreeElement(frame, index, this._recordingTreeOutline);
     563    }
    450564};
    451565
  • trunk/Source/WebInspectorUI/UserInterface/Views/FolderTreeElement.js

    r220119 r235095  
    2828    constructor(title, representedObject)
    2929    {
    30         console.assert(!representedObject || representedObject instanceof WI.Collection);
    31 
    3230        const classNames = [WI.FolderTreeElement.FolderIconStyleClassName];
    3331        const subtitle = null;
  • trunk/Source/WebInspectorUI/UserInterface/Views/GeneralTreeElement.js

    r234563 r235095  
    5151    }
    5252
     53    get statusElement()
     54    {
     55        return this._statusElement;
     56    }
     57
    5358    get titlesElement()
    5459    {
     
    302307            this._subtitleElement.removeChildren();
    303308            this._subtitleElement.appendChild(this._subtitle);
     309            this._titlesElement.classList.remove(WI.GeneralTreeElement.NoSubtitleStyleClassName);
    304310        } else {
    305311            if (this._subtitleElement)
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css

    r231218 r235095  
    4848}
    4949
     50.content-view:not(.tab).recording > header > .slider-container > .slider-value {
     51    font-family: Menlo, monospace;
     52    font-family: 11px;
     53}
     54
    5055.content-view:not(.tab).recording > header > .slider-container > input[type=range] {
    5156    flex: 1;
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js

    r231218 r235095  
    6262            this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this._exportRecording(); });
    6363        }
    64 
    65         this._processing = true;
    66         this._processMessageTextView = null;
    6764    }
    6865
     
    124121        this._index = index;
    125122
    126         if (this._processing)
    127             return;
    128 
    129123        this._updateSliderValue();
    130124
     
    179173
    180174        let sliderContainer = previewHeader.appendChild(document.createElement("div"));
    181         sliderContainer.className = "slider-container hidden";
     175        sliderContainer.className = "slider-container";
    182176
    183177        this._previewContainer = this.element.appendChild(document.createElement("div"));
     
    193187        this._sliderElement.max = 0;
    194188
    195         this.representedObject.addEventListener(WI.Recording.Event.ProcessedActionSwizzle, this._handleRecordingProcessedActionSwizzle, this);
    196         this.representedObject.addEventListener(WI.Recording.Event.ProcessedActionApply, this._handleRecordingProcessedActionApply, this);
    197 
    198         this.representedObject.process().then(() => {
    199             if (this._processMessageTextView)
    200                 this._processMessageTextView.remove();
    201 
    202             sliderContainer.classList.remove("hidden");
    203             this._sliderElement.max = this.representedObject.visualActionIndexes.length;
    204             this._updateSliderValue();
    205 
    206             this._processing = false;
    207 
    208             let index = this._index;
    209             if (!isNaN(index)) {
    210                 this._index = NaN;
    211                 this.updateActionIndex(index);
    212             }
    213         });
     189        this.representedObject.addEventListener(WI.Recording.Event.ProcessedAction, this._handleRecordingProcessedAction, this);
    214190    }
    215191
     
    455431        let activated = WI.settings.showCanvasPath.value;
    456432
    457         if (this._showPathButtonNavigationItem.activated !== activated && !this._processing)
     433        if (this._showPathButtonNavigationItem.activated !== activated)
    458434            this._generateContentCanvas2D(this._index);
    459435
     
    487463    }
    488464
    489     _updateProcessProgress(message, index)
    490     {
    491         if (this._processMessageTextView)
    492             this._processMessageTextView.remove();
    493 
    494         this._processMessageTextView = WI.createMessageTextView(message);
    495         this.element.appendChild(this._processMessageTextView);
    496 
    497         this._processProgressElement = this._processMessageTextView.appendChild(document.createElement("progress"));
    498         this._processProgressElement.value = index / this.representedObject.actions.length;
    499     }
    500 
    501465    _showPathButtonClicked(event)
    502466    {
     
    524488    }
    525489
    526     _handleRecordingProcessedActionSwizzle(event)
    527     {
    528         this._updateProcessProgress(WI.UIString("Loading Recording"), event.data.index);
    529     }
    530 
    531     _handleRecordingProcessedActionApply(event)
    532     {
    533         this._updateProcessProgress(WI.UIString("Processing Recording"), event.data.index);
     490    _handleRecordingProcessedAction(event)
     491    {
     492        this._sliderElement.max = this.representedObject.visualActionIndexes.length;
     493        this._updateSliderValue();
    534494    }
    535495};
Note: See TracChangeset for help on using the changeset viewer.