WebKit Bugzilla
Attachment 339193 Details for
Bug 182995
: Web Inspector: Canvas tab: determine hasVisibleEffect for all actions immediately after recording is added
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-182995-20180501004528.patch (text/plain), 51.60 KB, created by
Devin Rousso
on 2018-05-01 00:45:29 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Devin Rousso
Created:
2018-05-01 00:45:29 PDT
Size:
51.60 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index b462072e047ac250c0b4dc80d56c8502c98fa712..eac4629c615c2c5e0a2bf25dc7d439be7953c843 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,77 @@ >+2018-05-01 Devin Rousso <webkit@devinrousso.com> >+ >+ Web Inspector: Canvas tab: determine hasVisibleEffect for all actions immediately after recording is added >+ https://bugs.webkit.org/show_bug.cgi?id=182995 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Previously, we'd swizzle the entirety of the `WI.Recording` in one, which would usually >+ freeze the UI, especially for larger recordings. This patch uses `WI.YieldableTask` to split >+ the work and allow the rest of the UI to still be usable while `WI.Recording` are processing. >+ Additionally, since we no longer have to worry about hangs, we can do more work upfront, >+ such as calculating `hasVisibleEffect` and the current state of 2D canvases. >+ >+ These changes require that all uses of `WI.Recording` call `process()` before attempting to >+ use any `frames`/`actions`/`initialState`, as they will have their original payload values >+ and will have not been swizzled or applied. >+ >+ * Localizations/en.lproj/localizedStrings.js: >+ >+ * UserInterface/Models/Recording.js: >+ (WI.Recording): >+ (WI.Recording.prototype.process): >+ (WI.Recording.prototype.createContext): Added. >+ (WI.Recording.prototype.async yieldableTaskWillProcessItem): Added. >+ (WI.Recording.prototype.async yieldableTaskDidFinish): Added. >+ >+ * UserInterface/Models/RecordingAction.js: >+ (WI.RecordingAction): >+ (WI.RecordingAction.prototype.process): Added. >+ (WI.RecordingAction.prototype.async swizzle): Added. >+ (WI.RecordingAction.prototype.apply): >+ (WI.RecordingAction.prototype.toJSON): >+ (WI.RecordingAction.prototype.set state): Deleted. >+ (WI.RecordingAction.prototype.swizzle): Deleted. >+ (WI.RecordingAction.prototype.apply.getContent): Deleted. >+ (WI.RecordingAction.prototype.async _swizzle): Deleted. >+ * UserInterface/Models/RecordingInitialStateAction.js: >+ (WI.RecordingInitialStateAction): >+ >+ * UserInterface/Views/CanvasSidebarPanel.js: >+ (WI.CanvasSidebarPanel): >+ (WI.CanvasSidebarPanel.prototype.set action): >+ (WI.CanvasSidebarPanel.prototype._treeOutlineSelectionDidChange): >+ (WI.CanvasSidebarPanel.prototype._recordingChanged): >+ >+ * UserInterface/Views/CanvasSidebarPanel.css: >+ (.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner): >+ >+ * UserInterface/Views/RecordingActionTreeElement.js: >+ (WI.RecordingActionTreeElement): >+ (WI.RecordingActionTreeElement.prototype.onattach): >+ (WI.RecordingActionTreeElement.prototype._handleHasVisibleEffectChanged): Deleted. >+ >+ * UserInterface/Views/RecordingContentView.js: >+ (WI.RecordingContentView): >+ (WI.RecordingContentView.prototype.get navigationItems): >+ (WI.RecordingContentView.prototype.updateActionIndex): >+ (WI.RecordingContentView.prototype.initialLayout): >+ (WI.RecordingContentView.prototype._generateContentCanvas2D): Added. >+ (WI.RecordingContentView.prototype._generateContentCanvasWebGL): Added. >+ (WI.RecordingContentView.prototype._updateCanvasPath): >+ (WI.RecordingContentView.prototype._updateProcessProgress): Added. >+ (WI.RecordingContentView.prototype._handleRecordingProcessedActionSwizzle): Added. >+ (WI.RecordingContentView.prototype._handleRecordingProcessedActionApply): Added. >+ (WI.RecordingContentView.supportsCanvasPathDebugging): Deleted. >+ (WI.RecordingContentView.prototype.async _generateContentCanvas2D): Deleted. >+ (WI.RecordingContentView.prototype.async _generateContentCanvasWebGL): Deleted. >+ >+ * UserInterface/Views/RecordingContentView.css: >+ (.content-view:not(.tab).recording > .preview-container): >+ >+ * UserInterface/Base/ImageUtilities.js: >+ (WI.ImageUtilities.supportsCanvasPathDebugging): >+ > 2018-04-26 Jer Noble <jer.noble@apple.com> > > Unreviewed build fix; fix WebInspectorUI copy resources step after r231063. >diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >index c2e7195f6c75b3aeeb998f15fb02ab24d605d25a..803d9d41e85cb5cd2e84e6958dd2e794d051f16c 100644 >--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >@@ -562,6 +562,7 @@ localizedStrings["Live"] = "Live"; > 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"; >@@ -723,6 +724,7 @@ localizedStrings["Probe Expression"] = "Probe Expression"; > 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"; >diff --git a/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js b/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js >index fb19b4203413e63a7f9d8d5b4c72ec03ded6b856..0f9f6cced1b4aeb8527bf423cfdf9329b49e7089 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js >+++ b/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js >@@ -126,6 +126,14 @@ WI.ImageUtilities = class ImageUtilities { > }); > return image; > } >+ >+ static supportsCanvasPathDebugging() >+ { >+ return "getPath" in CanvasRenderingContext2D.prototype >+ && "setPath" in CanvasRenderingContext2D.prototype >+ && "currentX" in CanvasRenderingContext2D.prototype >+ && "currentY" in CanvasRenderingContext2D.prototype; >+ } > }; > > WI.ImageUtilities._scratchContext2D = null; >diff --git a/Source/WebInspectorUI/UserInterface/Models/Recording.js b/Source/WebInspectorUI/UserInterface/Models/Recording.js >index 58238cdf3f69b004875c7724388d4696a339fe31..32309379c98251820cf2603bd9c75949659ba032 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/Recording.js >+++ b/Source/WebInspectorUI/UserInterface/Models/Recording.js >@@ -23,10 +23,12 @@ > * THE POSSIBILITY OF SUCH DAMAGE. > */ > >-WI.Recording = class Recording >+WI.Recording = class Recording extends WI.Object > { > constructor(version, type, initialState, frames, data) > { >+ super(); >+ > this._version = version; > this._type = type; > this._initialState = initialState; >@@ -35,37 +37,14 @@ WI.Recording = class Recording > this._displayName = WI.UIString("Recording"); > > this._swizzle = []; >+ this._actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions)); > this._visualActionIndexes = []; > this._source = null; > >- let actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions)); >- this._actions = Promise.all(actions.map((action) => action.swizzle(this))).then(() => { >- actions.forEach((action, index) => { >- if (!action.valid) >- return; >- >- let prototype = null; >- if (this._type === WI.Recording.Type.Canvas2D) >- prototype = CanvasRenderingContext2D.prototype; >- else if (this._type === WI.Recording.Type.CanvasWebGL) >- prototype = WebGLRenderingContext.prototype; >- >- if (prototype) { >- let validName = action.name in prototype; >- let validFunction = !action.isFunction || typeof prototype[action.name] === "function"; >- if (!validName || !validFunction) { >- action.markInvalid(); >- >- WI.Recording.synthesizeError(WI.UIString("â%sâ is invalid.").format(this._name)); >- } >- } >- >- if (action.isVisual) >- this._visualActionIndexes.push(index); >- }); >- >- return actions; >- }); >+ this._swizzleTask = null; >+ this._applyTask = null; >+ this._processContext = null; >+ this._processPromise = null; > } > > static fromPayload(payload, frames) >@@ -177,13 +156,39 @@ WI.Recording = class Recording > get initialState() { return this._initialState; } > get frames() { return this._frames; } > get data() { return this._data; } >- get visualActionIndexes() { return this._visualActionIndexes; } >- > get actions() { return this._actions; } >+ get visualActionIndexes() { return this._visualActionIndexes; } > > get source() { return this._source; } > set source(source) { this._source = source; } > >+ process() >+ { >+ if (!this._processPromise) { >+ this._processPromise = new WI.WrappedPromise; >+ >+ function* createIteratorForActions() { >+ // Create a single object to avoid using extra memory. >+ let item = { >+ action: null, >+ index: 0, >+ }; >+ >+ for (let i = 0; i < this._actions.length; ++i) { >+ item.action = this._actions[i]; >+ item.index = i; >+ yield item; >+ } >+ } >+ >+ this._swizzleTask = new WI.YieldableTask(this, createIteratorForActions.call(this)); >+ this._applyTask = new WI.YieldableTask(this, createIteratorForActions.call(this)); >+ >+ this._swizzleTask.start(); >+ } >+ return this._processPromise.promise; >+ } >+ > createDisplayName(suggestedName) > { > let recordingNameSet; >@@ -300,6 +305,27 @@ WI.Recording = class Recording > return this._swizzle[index][type]; > } > >+ createContext() >+ { >+ let createCanvasContext = (type) => { >+ let canvas = document.createElement("canvas"); >+ if ("width" in this._initialState.attributes) >+ canvas.width = this._initialState.attributes.width; >+ if ("height" in this._initialState.attributes) >+ canvas.height = this._initialState.attributes.height; >+ return canvas.getContext(type, ...this._initialState.parameters); >+ }; >+ >+ if (this._type === WI.Recording.Type.Canvas2D) >+ return createCanvasContext("2d"); >+ >+ if (this._type === WI.Recording.Type.CanvasWebGL) >+ return createCanvasContext("webgl"); >+ >+ console.error("Unknown recording type", this._type); >+ return null; >+ } >+ > toJSON() > { > let initialState = {}; >@@ -318,6 +344,99 @@ WI.Recording = class Recording > data: this._data, > }; > } >+ >+ // YieldableTask delegate >+ >+ async yieldableTaskWillProcessItem(task, item) >+ { >+ 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; >+ >+ this._processContext = this.createContext(); >+ >+ if (this._type === WI.Recording.Type.Canvas2D) { >+ let initialContent = await WI.ImageUtilities.promisifyLoad(this._initialState.content); >+ this._processContext.drawImage(initialContent, 0, 0); >+ >+ for (let [key, value] of Object.entries(this._initialState.attributes)) { >+ switch (key) { >+ case "setTransform": >+ value = [await this.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; >+ break; >+ >+ case "fillStyle": >+ case "strokeStyle": >+ let [gradient, pattern, string] = await Promise.all([ >+ this.swizzle(value, WI.Recording.Swizzle.CanvasGradient), >+ this.swizzle(value, WI.Recording.Swizzle.CanvasPattern), >+ this.swizzle(value, WI.Recording.Swizzle.String), >+ ]); >+ if (gradient && !pattern) >+ value = gradient; >+ else if (pattern && !gradient) >+ value = pattern; >+ else >+ value = string; >+ break; >+ >+ case "direction": >+ case "font": >+ case "globalCompositeOperation": >+ case "imageSmoothingEnabled": >+ case "imageSmoothingQuality": >+ case "lineCap": >+ case "lineJoin": >+ case "shadowColor": >+ case "textAlign": >+ case "textBaseline": >+ value = await this.swizzle(value, WI.Recording.Swizzle.String); >+ break; >+ >+ case "setPath": >+ value = [await this.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; >+ break; >+ } >+ >+ if (value === undefined || (Array.isArray(value) && value.includes(undefined))) >+ continue; >+ >+ try { >+ if (WI.RecordingAction.isFunctionForType(this._type, key)) >+ this._processContext[key](...value); >+ else >+ this._processContext[key] = value; >+ } catch { } >+ } >+ } >+ >+ this._applyTask.start(); >+ } else if (task === this._applyTask) { >+ this._applyTask = null; >+ this._processContext = null; >+ this._processPromise.resolve(); >+ } >+ } >+}; >+ >+WI.Recording.Event = { >+ ProcessedActionApply: "recording-processed-action-apply", >+ ProcessedActionSwizzle: "recording-processed-action-swizzle", > }; > > WI.Recording._importedRecordingNameSet = new Set; >diff --git a/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js b/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js >index 27db60f4d1a42babd748c338d16cd3643a5a3ee8..9f9776fa4db2eea84337438c1755c8100fb7bc57 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js >+++ b/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js >@@ -41,12 +41,12 @@ WI.RecordingAction = class RecordingAction extends WI.Object > this._snapshot = ""; > > this._valid = true; >- this._swizzledPromise = null; >- > this._isFunction = false; > this._isGetter = false; > this._isVisual = false; > this._hasVisibleEffect = undefined; >+ >+ this._state = null; > this._stateModifiers = new Set; > } > >@@ -97,31 +97,19 @@ WI.RecordingAction = class RecordingAction extends WI.Object > get isGetter() { return this._isGetter; } > get isVisual() { return this._isVisual; } > get hasVisibleEffect() { return this._hasVisibleEffect; } >- get stateModifiers() { return this._stateModifiers; } >- > get state() { return this._state; } >- set state(state) { this._state = state; } >- >- markInvalid() >- { >- let wasValid = this._valid; >- this._valid = false; >- >- if (wasValid) >- this.dispatchEventToListeners(WI.RecordingAction.Event.ValidityChanged); >- } >- >- swizzle(recording) >- { >- if (!this._swizzledPromise) >- this._swizzledPromise = this._swizzle(recording); >- return this._swizzledPromise; >- } >+ get stateModifiers() { return this._stateModifiers; } > >- apply(context, options = {}) >+ process(recording, context) > { >- if (!this.valid) >+ 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) { >+ let contentBefore = recording.visualActionIndexes.length ? recording.visualActionIndexes.lastValue.snapshot : recording.initialState.content; >+ this._hasVisibleEffect = this._snapshot !== contentBefore; >+ } > return; >+ } > > function getContent() { > if (context instanceof CanvasRenderingContext2D) { >@@ -143,45 +131,56 @@ WI.RecordingAction = class RecordingAction extends WI.Object > } > > let contentBefore = null; >- let shouldCheckForChange = this._isVisual && this._hasVisibleEffect === undefined; >- if (shouldCheckForChange) >+ if (this._valid && this._isVisual) > contentBefore = getContent(); > >- try { >- let name = options.nameOverride || this._name; >- if (this.isFunction) >- context[name](...this._parameters); >- else { >- if (this.isGetter) >- context[name]; >- else >- context[name] = this._parameters[0]; >- } >- >- if (shouldCheckForChange) { >- this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); >- if (!this._hasVisibleEffect) >- this.dispatchEventToListeners(WI.RecordingAction.Event.HasVisibleEffectChanged); >- } >- } catch { >- this.markInvalid(); >- >- WI.Recording.synthesizeError(WI.UIString("â%sâ threw an error.").format(this._name)); >+ this.apply(context); >+ >+ if (this._valid && this._isVisual) >+ this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); >+ >+ if (recording.type === WI.Recording.Type.Canvas2D) { >+ let matrix = context.getTransform(); >+ >+ this._state = { >+ currentX: context.currentX, >+ currentY: context.currentY, >+ direction: context.direction, >+ fillStyle: context.fillStyle, >+ font: context.font, >+ globalAlpha: context.globalAlpha, >+ globalCompositeOperation: context.globalCompositeOperation, >+ imageSmoothingEnabled: context.imageSmoothingEnabled, >+ imageSmoothingQuality: context.imageSmoothingQuality, >+ lineCap: context.lineCap, >+ lineDash: context.getLineDash(), >+ lineDashOffset: context.lineDashOffset, >+ lineJoin: context.lineJoin, >+ lineWidth: context.lineWidth, >+ miterLimit: context.miterLimit, >+ shadowBlur: context.shadowBlur, >+ shadowColor: context.shadowColor, >+ shadowOffsetX: context.shadowOffsetX, >+ shadowOffsetY: context.shadowOffsetY, >+ strokeStyle: context.strokeStyle, >+ textAlign: context.textAlign, >+ textBaseline: context.textBaseline, >+ transform: [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f], >+ webkitImageSmoothingEnabled: context.webkitImageSmoothingEnabled, >+ webkitLineDash: context.webkitLineDash, >+ webkitLineDashOffset: context.webkitLineDashOffset, >+ }; >+ >+ if (WI.ImageUtilities.supportsCanvasPathDebugging()) >+ this._state.setPath = [context.getPath()]; > } > } > >- toJSON() >+ async swizzle(recording) > { >- let json = [this._payloadName, this._payloadParameters, this._payloadSwizzleTypes, this._payloadTrace]; >- if (this._payloadSnapshot >= 0) >- json.push(this._payloadSnapshot); >- return json; >- } >- >- // Private >+ if (!this._valid) >+ return; > >- async _swizzle(recording) >- { > let swizzleParameter = (item, index) => { > return recording.swizzle(item, this._payloadSwizzleTypes[index]); > }; >@@ -215,6 +214,30 @@ WI.RecordingAction = class RecordingAction extends WI.Object > if (this._payloadSnapshot >= 0) > this._snapshot = snapshot; > >+ this._isFunction = WI.RecordingAction.isFunctionForType(recording.type, this._name); >+ this._isGetter = !this._isFunction && !this._parameters.length; >+ >+ let visualNames = WI.RecordingAction._visualNames[recording.type]; >+ this._isVisual = visualNames ? visualNames.has(this._name) : false; >+ >+ if (this._valid) { >+ let prototype = null; >+ if (recording.type === WI.Recording.Type.Canvas2D) >+ prototype = CanvasRenderingContext2D.prototype; >+ else if (recording.type === WI.Recording.Type.CanvasWebGL) >+ prototype = WebGLRenderingContext.prototype; >+ >+ if (prototype) { >+ let validName = name in prototype; >+ let validFunction = !this._isFunction || typeof prototype[name] === "function"; >+ if (!validName || !validFunction) { >+ this.markInvalid(); >+ >+ WI.Recording.synthesizeError(WI.UIString("â%sâ is invalid.").format(name)); >+ } >+ } >+ } >+ > if (this._valid) { > let parametersSpecified = this._parameters.every((parameter) => parameter !== undefined); > let parametersCanBeSwizzled = this._payloadSwizzleTypes.every((swizzleType) => swizzleType !== WI.Recording.Swizzle.None); >@@ -222,21 +245,48 @@ WI.RecordingAction = class RecordingAction extends WI.Object > this.markInvalid(); > } > >- this._isFunction = WI.RecordingAction.isFunctionForType(recording.type, this._name); >- this._isGetter = !this._isFunction && !this._parameters.length; >+ if (this._valid) { >+ let stateModifiers = WI.RecordingAction._stateModifiers[recording.type]; >+ if (stateModifiers) { >+ this._stateModifiers.add(this._name); >+ let modifiedByAction = stateModifiers[this._name] || []; >+ for (let item of modifiedByAction) >+ this._stateModifiers.add(item); >+ } >+ } >+ } > >- let visualNames = WI.RecordingAction._visualNames[recording.type]; >- this._isVisual = visualNames ? visualNames.has(this._name) : false; >+ apply(context, options = {}) >+ { >+ if (!this.valid) >+ return; > >- this._stateModifiers = new Set([this._name]); >- let stateModifiers = WI.RecordingAction._stateModifiers[recording.type]; >- if (stateModifiers) { >- let modifiedByAction = stateModifiers[this._name] || []; >- for (let item of modifiedByAction) >- this._stateModifiers.add(item); >+ try { >+ let name = options.nameOverride || this._name; >+ if (this.isFunction) >+ context[name](...this._parameters); >+ else { >+ if (this.isGetter) >+ context[name]; >+ else >+ context[name] = this._parameters[0]; >+ } >+ } catch { >+ this.markInvalid(); >+ >+ WI.Recording.synthesizeError(WI.UIString("â%sâ threw an error.").format(this._name)); > } > } > >+ markInvalid() >+ { >+ let wasValid = this._valid; >+ this._valid = false; >+ >+ if (wasValid) >+ this.dispatchEventToListeners(WI.RecordingAction.Event.ValidityChanged); >+ } >+ > getColorParameters() > { > switch (this._name) { >@@ -277,6 +327,14 @@ WI.RecordingAction = class RecordingAction extends WI.Object > > return []; > } >+ >+ toJSON() >+ { >+ let json = [this._payloadName, this._payloadParameters, this._payloadSwizzleTypes, this._payloadTrace]; >+ if (this._payloadSnapshot >= 0) >+ json.push(this._payloadSnapshot); >+ return json; >+ } > }; > > WI.RecordingAction.Event = { >@@ -310,6 +368,7 @@ WI.RecordingAction._functionNames = { > "fillText", > "getImageData", > "getLineDash", >+ "getPath", > "isPointInPath", > "isPointInPath", > "isPointInStroke", >@@ -333,6 +392,7 @@ WI.RecordingAction._functionNames = { > "setLineJoin", > "setLineWidth", > "setMiterLimit", >+ "setPath", > "setShadow", > "setStrokeColor", > "setTransform", >diff --git a/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js b/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js >index 8bc518e63733048ce84d6a6a9938139a3414bfb2..d5498542246f8c77cdce91282b644751ca164f0c 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js >+++ b/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js >@@ -32,6 +32,5 @@ WI.RecordingInitialStateAction = class RecordingInitialStateAction extends WI.Re > this._name = WI.UIString("Initial State"); > > this._valid = false; >- this._swizzledPromise = Promise.resolve(); > } > }; >diff --git a/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css b/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css >index 3ea897f62ba6fd5f998ad4cefaa15cbf7f874ab8..cab3dbc1f92ed909d9d0d3e967ee9a2df0c0a9cd 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css >@@ -71,3 +71,7 @@ > .sidebar > .panel.navigation.canvas > .content > .recording-content > .tree-outline .item.folder-icon > .status { > line-height: 16px; > } >+ >+.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner { >+ margin: 16px auto; >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js >index 440dd6e822a49fdc983f54ad0a086405a39fc9f6..67fc08d2bd17127d9d05a26a63dbd64ef11c412b 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js >@@ -52,20 +52,23 @@ WI.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPan > this._recordingNavigationBar.element.classList.add("hidden"); > this.contentView.addSubview(this._recordingNavigationBar); > >- let recordingContent = this.contentView.element.appendChild(document.createElement("div")); >- recordingContent.className = "recording-content"; >+ this._recordingContentContainer = this.contentView.element.appendChild(document.createElement("div")); >+ this._recordingContentContainer.className = "recording-content"; > > this._recordingTreeOutline = this.contentTreeOutline; >- recordingContent.appendChild(this._recordingTreeOutline.element); >+ this._recordingContentContainer.appendChild(this._recordingTreeOutline.element); > > this._recordingTreeOutline.customIndent = true; >- this._recordingTreeOutline.registerScrollVirtualizer(recordingContent, 20); >+ this._recordingTreeOutline.registerScrollVirtualizer(this._recordingContentContainer, 20); > > this._canvasTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeOutlineSelectionDidChange, this); > this._recordingTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeOutlineSelectionDidChange, this); > > 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; > } > > // Public >@@ -108,7 +111,7 @@ WI.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPan > > set action(action) > { >- if (!this._recording) >+ if (!this._recording || this._recordingProcessPromise) > return; > > let selectedTreeElement = this._recordingTreeOutline.selectedTreeElement; >@@ -286,7 +289,8 @@ WI.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPan > console.assert(this._recording, "Missing recording for action tree element.", treeElement); > this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] = treeElement.representedObject; > >- let recordingContentView = this.contentBrowser.showContentViewForRepresentedObject(this._recording); >+ const onlyExisting = true; >+ let recordingContentView = this.contentBrowser.contentViewForRepresentedObject(this._recording, onlyExisting); > if (recordingContentView) > recordingContentView.updateActionIndex(treeElement.index); > } >@@ -327,16 +331,28 @@ WI.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPan > if (!this._recording) > return; > >+ if (!this._recordingProcessSpinner) { >+ this._recordingProcessSpinner = new WI.IndeterminateProgressSpinner; >+ this._recordingContentContainer.appendChild(this._recordingProcessSpinner.element); >+ } >+ >+ this.contentBrowser.showContentViewForRepresentedObject(this._recording); >+ > let recording = this._recording; > >- this._recording.actions.then((actions) => { >- if (recording !== this._recording) >+ let promise = this._recording.process().then(() => { >+ if (recording !== this._recording || promise !== this._recordingProcessPromise) > return; > >- this._recordingTreeOutline.element.dataset.indent = Number.countDigits(actions.length); >+ if (this._recordingProcessSpinner) { >+ this._recordingProcessSpinner.element.remove(); >+ this._recordingProcessSpinner = null; >+ } >+ >+ this._recordingTreeOutline.element.dataset.indent = Number.countDigits(this._recording.actions.length); > >- if (actions[0] instanceof WI.RecordingInitialStateAction) >- this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(actions[0], 0, this._recording.type)); >+ if (this._recording.actions[0] instanceof WI.RecordingInitialStateAction) >+ this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type)); > > let cumulativeActionIndex = 1; > this._recording.frames.forEach((frame, frameIndex) => { >@@ -366,8 +382,12 @@ WI.CanvasSidebarPanel = class CanvasSidebarPanel extends WI.NavigationSidebarPan > scopeBarItem.selected = true; > } > >- this.action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] || actions[0]; >+ this.action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] || this._recording.actions[0]; >+ >+ this._recordingProcessPromise = null; > }); >+ >+ this._recordingProcessPromise = promise; > } > > _updateRecordNavigationItem() >diff --git a/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js >index 13353a39d8eb24b61117d09550063e30e3e03cc2..68d18f0568ef5b2824cf57b375aa1161d1525a32 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js >+++ b/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js >@@ -39,7 +39,6 @@ WI.RecordingActionTreeElement = class RecordingActionTreeElement extends WI.Gene > this._copyText = copyText; > > this.representedObject.addEventListener(WI.RecordingAction.Event.ValidityChanged, this._handleValidityChanged, this); >- this.representedObject.addEventListener(WI.RecordingAction.Event.HasVisibleEffectChanged, this._handleHasVisibleEffectChanged, this); > } > > // Static >@@ -399,6 +398,13 @@ WI.RecordingActionTreeElement = class RecordingActionTreeElement extends WI.Gene > super.onattach(); > > this.element.dataset.index = this._index.toLocaleString(); >+ >+ if (this.representedObject.valid && this.representedObject.isVisual && !this.representedObject.hasVisibleEffect) { >+ this.addClassName("no-visible-effect"); >+ >+ const title = WI.UIString("This action causes no visual change"); >+ this.status = WI.ImageUtilities.useSVGSymbol("Images/Warning.svg", "warning", title); >+ } > } > > populateContextMenu(contextMenu, event) >@@ -430,14 +436,6 @@ WI.RecordingActionTreeElement = class RecordingActionTreeElement extends WI.Gene > { > this.addClassName("invalid"); > } >- >- _handleHasVisibleEffectChanged(event) >- { >- this.addClassName("no-visible-effect"); >- >- this.status = WI.ImageUtilities.useSVGSymbol("Images/Warning.svg", "warning"); >- this.status.title = WI.UIString("This action causes no visual change"); >- } > }; > > WI.RecordingActionTreeElement._memoizedActionClassNames = new Map; >diff --git a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css >index d224b3822b1660a27439f24d266f8ed3b640bb36..7c9b4aa9a8c307391c6bf0c630cdf46a9caa34cb 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css >@@ -67,6 +67,9 @@ > flex: 1; > justify-content: center; > align-items: center; >+ position: relative; >+ width: -webkit-fill-available; >+ height: -webkit-fill-available; > } > > .content-view:not(.tab).recording :matches(img, canvas) { >diff --git a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >index f0ea4653f2e54400cbc7a6fab07d02bcfec74ad6..e9f8b0b95b6f1f32e70c30dd2ff9b04c32db8653 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >@@ -42,7 +42,7 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > let isCanvas2D = this.representedObject.type === WI.Recording.Type.Canvas2D; > let isCanvasWebGL = this.representedObject.type === WI.Recording.Type.CanvasWebGL; > if (isCanvas2D || isCanvasWebGL) { >- if (isCanvas2D && WI.RecordingContentView.supportsCanvasPathDebugging()) { >+ if (isCanvas2D && WI.ImageUtilities.supportsCanvasPathDebugging()) { > this._pathContext = null; > > this._showPathButtonNavigationItem = new WI.ActivateButtonNavigationItem("show-path", WI.UIString("Show Path"), WI.UIString("Hide Path"), "Images/Path.svg", 16, 16); >@@ -61,15 +61,13 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High; > this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this._exportRecording(); }); > } >+ >+ this._processing = true; >+ this._processMessageTextView = null; > } > > // Static > >- static supportsCanvasPathDebugging() >- { >- return "currentX" in CanvasRenderingContext2D.prototype && "currentY" in CanvasRenderingContext2D.prototype; >- } >- > static _actionModifiesPath(recordingAction) > { > switch (recordingAction.name) { >@@ -99,7 +97,7 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > return []; > > let navigationItems = [this._exportButtonNavigationItem, new WI.DividerNavigationItem]; >- if (isCanvas2D && WI.RecordingContentView.supportsCanvasPathDebugging()) >+ if (isCanvas2D && WI.ImageUtilities.supportsCanvasPathDebugging()) > navigationItems.push(this._showPathButtonNavigationItem); > > navigationItems.push(this._showGridButtonNavigationItem); >@@ -119,19 +117,25 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > if (this._index === index) > return; > >- this.representedObject.actions.then((actions) => { >- console.assert(index >= 0 && index < actions.length); >- if (index < 0 || index >= actions.length) >- return; >+ console.assert(index >= 0 && index < this.representedObject.actions.length); >+ if (index < 0 || index >= this.representedObject.actions.length) >+ return; > >- this._index = index; >- this._updateSliderValue(); >+ this._index = index; > >- if (this.representedObject.type === WI.Recording.Type.Canvas2D) >- this._throttler._generateContentCanvas2D(index, actions); >- else if (this.representedObject.type === WI.Recording.Type.CanvasWebGL) >- this._throttler._generateContentCanvasWebGL(index, actions); >- }); >+ if (this._processing) >+ return; >+ >+ this._updateSliderValue(); >+ >+ if (this.representedObject.type === WI.Recording.Type.Canvas2D) >+ this._throttler._generateContentCanvas2D(index); >+ else if (this.representedObject.type === WI.Recording.Type.CanvasWebGL) >+ this._throttler._generateContentCanvasWebGL(index); >+ >+ this._action = this.representedObject.actions[this._index]; >+ >+ this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); > } > > shown() >@@ -188,10 +192,24 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > this._sliderElement.min = 0; > this._sliderElement.max = 0; > >- this.representedObject.actions.then(() => { >+ 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); >+ } > }); > } > >@@ -214,14 +232,14 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > }); > } > >- async _generateContentCanvas2D(index, actions) >+ _generateContentCanvas2D(index) > { > let imageLoad = (event) => { > // Loading took too long and the current action index has already changed. > if (index !== this._index) > return; > >- this._generateContentCanvas2D(index, actions); >+ this._generateContentCanvas2D(index); > }; > > let initialState = this.representedObject.initialState; >@@ -235,9 +253,11 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > let snapshotIndex = Math.floor(index / WI.RecordingContentView.SnapshotInterval); > let snapshot = this._snapshots[snapshotIndex]; > >- let showCanvasPath = WI.RecordingContentView.supportsCanvasPathDebugging() && WI.settings.showCanvasPath.value; >+ let showCanvasPath = WI.ImageUtilities.supportsCanvasPathDebugging() && WI.settings.showCanvasPath.value; > let indexOfLastBeginPathAction = Infinity; > >+ let actions = this.representedObject.actions; >+ > let applyActions = (from, to, callback) => { > let saveCount = 0; > snapshot.context.save(); >@@ -331,43 +351,9 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > } else if (this._pathContext) > this._pathContext.canvas.remove(); > >- let state = { >- currentX: snapshot.context.currentX, >- currentY: snapshot.context.currentY, >- direction: snapshot.context.direction, >- fillStyle: snapshot.context.fillStyle, >- font: snapshot.context.font, >- globalAlpha: snapshot.context.globalAlpha, >- globalCompositeOperation: snapshot.context.globalCompositeOperation, >- imageSmoothingEnabled: snapshot.context.imageSmoothingEnabled, >- imageSmoothingQuality: snapshot.context.imageSmoothingQuality, >- lineCap: snapshot.context.lineCap, >- lineDash: snapshot.context.getLineDash(), >- lineDashOffset: snapshot.context.lineDashOffset, >- lineJoin: snapshot.context.lineJoin, >- lineWidth: snapshot.context.lineWidth, >- miterLimit: snapshot.context.miterLimit, >- shadowBlur: snapshot.context.shadowBlur, >- shadowColor: snapshot.context.shadowColor, >- shadowOffsetX: snapshot.context.shadowOffsetX, >- shadowOffsetY: snapshot.context.shadowOffsetY, >- strokeStyle: snapshot.context.strokeStyle, >- textAlign: snapshot.context.textAlign, >- textBaseline: snapshot.context.textBaseline, >- transform: snapshot.context.getTransform(), >- webkitImageSmoothingEnabled: snapshot.context.webkitImageSmoothingEnabled, >- webkitLineDash: snapshot.context.webkitLineDash, >- webkitLineDashOffset: snapshot.context.webkitLineDashOffset, >- }; >- >- if (WI.RecordingContentView.supportsCanvasPathDebugging()) >- state.setPath = [snapshot.context.getPath()]; >- > snapshot.context.restore(); > while (saveCount-- > 0) > snapshot.context.restore(); >- >- return state; > }; > > if (!snapshot) { >@@ -376,12 +362,8 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > while (snapshot.index && actions[snapshot.index].name !== "beginPath") > --snapshot.index; > >- snapshot.element = document.createElement("canvas"); >- snapshot.context = snapshot.element.getContext("2d", ...initialState.parameters); >- if ("width" in initialState.attributes) >- snapshot.element.width = initialState.attributes.width; >- if ("height" in initialState.attributes) >- snapshot.element.height = initialState.attributes.height; >+ snapshot.context = this.representedObject.createContext(); >+ snapshot.element = snapshot.context.canvas; > > let lastSnapshotIndex = snapshotIndex; > while (--lastSnapshotIndex >= 0) { >@@ -392,58 +374,16 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > let startIndex = 0; > if (lastSnapshotIndex < 0) { > snapshot.content = this._initialContent; >- snapshot.state = {}; >- >- for (let key in initialState.attributes) { >- let value = initialState.attributes[key]; >- >- switch (key) { >- case "setTransform": >- value = [await this.representedObject.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; >- break; >- >- case "fillStyle": >- case "strokeStyle": >- if (Array.isArray(value)) { >- let canvasStyle = await this.representedObject.swizzle(value[0], WI.Recording.Swizzle.String); >- if (canvasStyle.includes("gradient")) >- value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.CanvasGradient); >- else if (canvasStyle === "pattern") >- value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.CanvasPattern); >- } else >- value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.String); >- break; >- >- case "direction": >- case "font": >- case "globalCompositeOperation": >- case "imageSmoothingEnabled": >- case "imageSmoothingQuality": >- case "lineCap": >- case "lineJoin": >- case "shadowColor": >- case "textAlign": >- case "textBaseline": >- value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.String); >- break; >- >- case "setPath": >- value = [await this.representedObject.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; >- break; >- } >- >- if (value === undefined || (Array.isArray(value) && value.includes(undefined))) >- continue; >- >- snapshot.state[key] = value; >- } >+ snapshot.state = actions[0].state; > } else { > snapshot.content = this._snapshots[lastSnapshotIndex].content; > snapshot.state = this._snapshots[lastSnapshotIndex].state; > startIndex = this._snapshots[lastSnapshotIndex].index; > } > >- snapshot.state = applyActions(startIndex, snapshot.index - 1); >+ applyActions(startIndex, snapshot.index - 1); >+ if (snapshot.index > 0) >+ snapshot.state = actions[snapshot.index - 1].state; > > snapshot.content = new Image; > snapshot.content.src = snapshot.element.toDataURL(); >@@ -459,27 +399,20 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > --indexOfLastBeginPathAction; > } > >- this._action = actions[this._index]; >- >- let state = applyActions(snapshot.index, this._index); >- console.assert(!this._action.state || Object.shallowEqual(this._action.state, state)); >- if (!this._action.state) >- this._action.state = state; >- >- this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); >+ applyActions(snapshot.index, this._index); > > this._previewContainer.appendChild(snapshot.element); > this._updateImageGrid(); > } > >- async _generateContentCanvasWebGL(index, actions) >+ _generateContentCanvasWebGL(index) > { > let imageLoad = (event) => { > // Loading took too long and the current action index has already changed. > if (index !== this._index) > return; > >- this._generateContentCanvasWebGL(index, actions); >+ this._generateContentCanvasWebGL(index); > }; > > let initialState = this.representedObject.initialState; >@@ -490,6 +423,8 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > return; > } > >+ let actions = this.representedObject.actions; >+ > let visualIndex = index; > while (!actions[visualIndex].isVisual && !(actions[visualIndex] instanceof WI.RecordingInitialStateAction)) > visualIndex--; >@@ -513,17 +448,14 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > > this._updateImageGrid(); > } >- >- this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); > } > > _updateCanvasPath() > { > let activated = WI.settings.showCanvasPath.value; > if (this._showPathButtonNavigationItem.activated !== activated) { >- this.representedObject.actions.then((actions) => { >- this._generateContentCanvas2D(this._index, actions); >- }); >+ if (!this._processing) >+ this._generateContentCanvas2D(this._index); > } > > this._showPathButtonNavigationItem.activated = activated; >@@ -555,6 +487,18 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > 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; >@@ -579,6 +523,16 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > > this.updateActionIndex(index); > } >+ >+ _handleRecordingProcessedActionSwizzle(event) >+ { >+ this._updateProcessProgress(WI.UIString("Loading Recording"), event.data.index); >+ } >+ >+ _handleRecordingProcessedActionApply(event) >+ { >+ this._updateProcessProgress(WI.UIString("Processing Recording"), event.data.index); >+ } > }; > > WI.RecordingContentView.SnapshotInterval = 5000; >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index f31bd0d3c44da357fc543fcd404158d29f87f791..35561b9f52af3e9dbe0e3ba36511d16ba6b989f5 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,12 @@ >+2018-05-01 Devin Rousso <webkit@devinrousso.com> >+ >+ Web Inspector: Canvas tab: determine hasVisibleEffect for all actions immediately after recording is added >+ https://bugs.webkit.org/show_bug.cgi?id=182995 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * inspector/canvas/resources/recording-utilities.js: >+ > 2018-04-30 Jiewen Tan <jiewen_tan@apple.com> > > Unreviewed test gardening >diff --git a/LayoutTests/inspector/canvas/resources/recording-utilities.js b/LayoutTests/inspector/canvas/resources/recording-utilities.js >index 80c48a340e0b53cf33a85e92773f7dd82cce9568..4b35ce1c8e5c66f1053ea9d62e88b81e00cc2b52 100644 >--- a/LayoutTests/inspector/canvas/resources/recording-utilities.js >+++ b/LayoutTests/inspector/canvas/resources/recording-utilities.js >@@ -124,7 +124,7 @@ TestPage.registerInitializer(() => { > 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(() => { >+ return Promise.all(recording.actions.map((action) => action.swizzle(recording))).then(() => { > logRecording(recording, type); > }); > }).then(resolve, reject);
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 182995
:
334342
|
334343
|
334420
|
335763
|
339191
|
339193
|
339228