Changeset 231218 in webkit
- Timestamp:
- May 1, 2018 4:37:59 PM (6 years ago)
- Location:
- trunk
- Files:
-
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r231203 r231218 1 2018-05-01 Devin Rousso <webkit@devinrousso.com> 2 3 Web Inspector: Canvas tab: determine hasVisibleEffect for all actions immediately after recording is added 4 https://bugs.webkit.org/show_bug.cgi?id=182995 5 6 Reviewed by Matt Baker. 7 8 * inspector/canvas/resources/recording-utilities.js: 9 1 10 2018-05-01 Ryan Haddad <ryanhaddad@apple.com> 2 11 -
trunk/LayoutTests/inspector/canvas/resources/recording-utilities.js
r225892 r231218 125 125 InspectorTest.assert(recording.frames.length === frameCount, `Recording should have ${frameCount} frames.`) 126 126 127 return recording.actions.then(() => {127 return Promise.all(recording.actions.map((action) => action.swizzle(recording))).then(() => { 128 128 logRecording(recording, type); 129 129 }); -
trunk/Source/WebInspectorUI/ChangeLog
r231081 r231218 1 2018-05-01 Devin Rousso <webkit@devinrousso.com> 2 3 Web Inspector: Canvas tab: determine hasVisibleEffect for all actions immediately after recording is added 4 https://bugs.webkit.org/show_bug.cgi?id=182995 5 6 Reviewed by Matt Baker. 7 8 Previously, we'd swizzle the entirety of the `WI.Recording` in one, which would usually 9 freeze the UI, especially for larger recordings. This patch uses `WI.YieldableTask` to split 10 the work and allow the rest of the UI to still be usable while `WI.Recording` are processing. 11 Additionally, since we no longer have to worry about hangs, we can do more work upfront, 12 such as calculating `hasVisibleEffect` and the current state of 2D canvases. 13 14 These changes require that all uses of `WI.Recording` call `process()` before attempting to 15 use any `frames`/`actions`/`initialState`, as they will have their original payload values 16 and will have not been swizzled or applied. 17 18 * Localizations/en.lproj/localizedStrings.js: 19 20 * UserInterface/Models/Recording.js: 21 (WI.Recording): 22 (WI.Recording.prototype.process): 23 (WI.Recording.prototype.createContext): Added. 24 (WI.Recording.prototype.async yieldableTaskWillProcessItem): Added. 25 (WI.Recording.prototype.async yieldableTaskDidFinish): Added. 26 27 * UserInterface/Models/RecordingAction.js: 28 (WI.RecordingAction): 29 (WI.RecordingAction.prototype.process): Added. 30 (WI.RecordingAction.prototype.async swizzle): Added. 31 (WI.RecordingAction.prototype.apply): 32 (WI.RecordingAction.prototype.toJSON): 33 (WI.RecordingAction.prototype.set state): Deleted. 34 (WI.RecordingAction.prototype.swizzle): Deleted. 35 (WI.RecordingAction.prototype.apply.getContent): Deleted. 36 (WI.RecordingAction.prototype.async _swizzle): Deleted. 37 * UserInterface/Models/RecordingInitialStateAction.js: 38 (WI.RecordingInitialStateAction): 39 40 * UserInterface/Views/CanvasSidebarPanel.js: 41 (WI.CanvasSidebarPanel): 42 (WI.CanvasSidebarPanel.prototype.set action): 43 (WI.CanvasSidebarPanel.prototype._treeOutlineSelectionDidChange): 44 (WI.CanvasSidebarPanel.prototype._recordingChanged): 45 46 * UserInterface/Views/CanvasSidebarPanel.css: 47 (.sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner): 48 49 * UserInterface/Views/RecordingActionTreeElement.js: 50 (WI.RecordingActionTreeElement): 51 (WI.RecordingActionTreeElement.prototype.onattach): 52 (WI.RecordingActionTreeElement.prototype._handleHasVisibleEffectChanged): Deleted. 53 54 * UserInterface/Views/RecordingContentView.js: 55 (WI.RecordingContentView): 56 (WI.RecordingContentView.prototype.get navigationItems): 57 (WI.RecordingContentView.prototype.updateActionIndex): 58 (WI.RecordingContentView.prototype.initialLayout): 59 (WI.RecordingContentView.prototype._generateContentCanvas2D): Added. 60 (WI.RecordingContentView.prototype._generateContentCanvasWebGL): Added. 61 (WI.RecordingContentView.prototype._updateCanvasPath): 62 (WI.RecordingContentView.prototype._updateProcessProgress): Added. 63 (WI.RecordingContentView.prototype._handleRecordingProcessedActionSwizzle): Added. 64 (WI.RecordingContentView.prototype._handleRecordingProcessedActionApply): Added. 65 (WI.RecordingContentView.supportsCanvasPathDebugging): Deleted. 66 (WI.RecordingContentView.prototype.async _generateContentCanvas2D): Deleted. 67 (WI.RecordingContentView.prototype.async _generateContentCanvasWebGL): Deleted. 68 69 * UserInterface/Views/RecordingContentView.css: 70 (.content-view:not(.tab).recording > .preview-container): 71 72 * UserInterface/Base/ImageUtilities.js: 73 (WI.ImageUtilities.supportsCanvasPathDebugging): 74 1 75 2018-04-26 Jer Noble <jer.noble@apple.com> 2 76 -
trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
r230228 r231218 563 563 localizedStrings["Load \u2014 %s"] = "Load \u2014 %s"; 564 564 localizedStrings["Load cancelled"] = "Load cancelled"; 565 localizedStrings["Loading Recording"] = "Loading Recording"; 565 566 localizedStrings["Local File"] = "Local File"; 566 567 localizedStrings["Local Storage"] = "Local Storage"; … … 724 725 localizedStrings["Probes"] = "Probes"; 725 726 localizedStrings["Processing Instruction"] = "Processing Instruction"; 727 localizedStrings["Processing Recording"] = "Processing Recording"; 726 728 localizedStrings["Program %d"] = "Program %d"; 727 729 localizedStrings["Properties"] = "Properties"; -
trunk/Source/WebInspectorUI/UserInterface/Base/ImageUtilities.js
r225884 r231218 127 127 return image; 128 128 } 129 130 static supportsCanvasPathDebugging() 131 { 132 return "getPath" in CanvasRenderingContext2D.prototype 133 && "setPath" in CanvasRenderingContext2D.prototype 134 && "currentX" in CanvasRenderingContext2D.prototype 135 && "currentY" in CanvasRenderingContext2D.prototype; 136 } 129 137 }; 130 138 -
trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js
r225884 r231218 24 24 */ 25 25 26 WI.Recording = class Recording 26 WI.Recording = class Recording extends WI.Object 27 27 { 28 28 constructor(version, type, initialState, frames, data) 29 29 { 30 super(); 31 30 32 this._version = version; 31 33 this._type = type; … … 36 38 37 39 this._swizzle = []; 40 this._actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions)); 38 41 this._visualActionIndexes = []; 39 42 this._source = null; 40 43 41 let actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions)); 42 this._actions = Promise.all(actions.map((action) => action.swizzle(this))).then(() => { 43 actions.forEach((action, index) => { 44 if (!action.valid) 45 return; 46 47 let prototype = null; 48 if (this._type === WI.Recording.Type.Canvas2D) 49 prototype = CanvasRenderingContext2D.prototype; 50 else if (this._type === WI.Recording.Type.CanvasWebGL) 51 prototype = WebGLRenderingContext.prototype; 52 53 if (prototype) { 54 let validName = action.name in prototype; 55 let validFunction = !action.isFunction || typeof prototype[action.name] === "function"; 56 if (!validName || !validFunction) { 57 action.markInvalid(); 58 59 WI.Recording.synthesizeError(WI.UIString("“%s” is invalid.").format(this._name)); 60 } 61 } 62 63 if (action.isVisual) 64 this._visualActionIndexes.push(index); 65 }); 66 67 return actions; 68 }); 44 this._swizzleTask = null; 45 this._applyTask = null; 46 this._processContext = null; 47 this._processPromise = null; 69 48 } 70 49 … … 178 157 get frames() { return this._frames; } 179 158 get data() { return this._data; } 159 get actions() { return this._actions; } 180 160 get visualActionIndexes() { return this._visualActionIndexes; } 181 182 get actions() { return this._actions; }183 161 184 162 get source() { return this._source; } 185 163 set source(source) { this._source = source; } 164 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; 177 } 186 178 187 179 createDisplayName(suggestedName) … … 301 293 } 302 294 295 createContext() 296 { 297 let createCanvasContext = (type) => { 298 let canvas = document.createElement("canvas"); 299 if ("width" in this._initialState.attributes) 300 canvas.width = this._initialState.attributes.width; 301 if ("height" in this._initialState.attributes) 302 canvas.height = this._initialState.attributes.height; 303 return canvas.getContext(type, ...this._initialState.parameters); 304 }; 305 306 if (this._type === WI.Recording.Type.Canvas2D) 307 return createCanvasContext("2d"); 308 309 if (this._type === WI.Recording.Type.CanvasWebGL) 310 return createCanvasContext("webgl"); 311 312 console.error("Unknown recording type", this._type); 313 return null; 314 } 315 303 316 toJSON() 304 317 { … … 319 332 }; 320 333 } 334 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 358 this._processContext = this.createContext(); 359 360 if (this._type === WI.Recording.Type.Canvas2D) { 361 let initialContent = await WI.ImageUtilities.promisifyLoad(this._initialState.content); 362 this._processContext.drawImage(initialContent, 0, 0); 363 364 for (let [key, value] of Object.entries(this._initialState.attributes)) { 365 switch (key) { 366 case "setTransform": 367 value = [await this.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; 368 break; 369 370 case "fillStyle": 371 case "strokeStyle": 372 let [gradient, pattern, string] = await Promise.all([ 373 this.swizzle(value, WI.Recording.Swizzle.CanvasGradient), 374 this.swizzle(value, WI.Recording.Swizzle.CanvasPattern), 375 this.swizzle(value, WI.Recording.Swizzle.String), 376 ]); 377 if (gradient && !pattern) 378 value = gradient; 379 else if (pattern && !gradient) 380 value = pattern; 381 else 382 value = string; 383 break; 384 385 case "direction": 386 case "font": 387 case "globalCompositeOperation": 388 case "imageSmoothingEnabled": 389 case "imageSmoothingQuality": 390 case "lineCap": 391 case "lineJoin": 392 case "shadowColor": 393 case "textAlign": 394 case "textBaseline": 395 value = await this.swizzle(value, WI.Recording.Swizzle.String); 396 break; 397 398 case "setPath": 399 value = [await this.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; 400 break; 401 } 402 403 if (value === undefined || (Array.isArray(value) && value.includes(undefined))) 404 continue; 405 406 try { 407 if (WI.RecordingAction.isFunctionForType(this._type, key)) 408 this._processContext[key](...value); 409 else 410 this._processContext[key] = value; 411 } catch { } 412 } 413 } 414 415 this._applyTask.start(); 416 } else if (task === this._applyTask) { 417 this._applyTask = null; 418 this._processContext = null; 419 this._processPromise.resolve(); 420 } 421 } 422 }; 423 424 WI.Recording.Event = { 425 ProcessedActionApply: "recording-processed-action-apply", 426 ProcessedActionSwizzle: "recording-processed-action-swizzle", 321 427 }; 322 428 -
trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js
r228301 r231218 42 42 43 43 this._valid = true; 44 this._swizzledPromise = null;45 46 44 this._isFunction = false; 47 45 this._isGetter = false; 48 46 this._isVisual = false; 49 47 this._hasVisibleEffect = undefined; 48 49 this._state = null; 50 50 this._stateModifiers = new Set; 51 51 } … … 98 98 get isVisual() { return this._isVisual; } 99 99 get hasVisibleEffect() { return this._hasVisibleEffect; } 100 get state() { return this._state; } 100 101 get stateModifiers() { return this._stateModifiers; } 101 102 102 get state() { return this._state; } 103 set state(state) { this._state = state; } 104 105 markInvalid() 106 { 107 let wasValid = this._valid; 108 this._valid = false; 109 110 if (wasValid) 111 this.dispatchEventToListeners(WI.RecordingAction.Event.ValidityChanged); 112 } 113 114 swizzle(recording) 115 { 116 if (!this._swizzledPromise) 117 this._swizzledPromise = this._swizzle(recording); 118 return this._swizzledPromise; 119 } 120 121 apply(context, options = {}) 122 { 123 if (!this.valid) 103 process(recording, context) 104 { 105 if (recording.type === WI.Recording.Type.CanvasWebGL) { 106 // We add each RecordingAction to the list of visualActionIndexes after it is processed. 107 if (this._valid && this._isVisual) { 108 let contentBefore = recording.visualActionIndexes.length ? recording.visualActionIndexes.lastValue.snapshot : recording.initialState.content; 109 this._hasVisibleEffect = this._snapshot !== contentBefore; 110 } 124 111 return; 112 } 125 113 126 114 function getContent() { … … 144 132 145 133 let contentBefore = null; 146 let shouldCheckForChange = this._isVisual && this._hasVisibleEffect === undefined; 147 if (shouldCheckForChange) 134 if (this._valid && this._isVisual) 148 135 contentBefore = getContent(); 149 136 150 try { 151 let name = options.nameOverride || this._name; 152 if (this.isFunction) 153 context[name](...this._parameters); 154 else { 155 if (this.isGetter) 156 context[name]; 157 else 158 context[name] = this._parameters[0]; 159 } 160 161 if (shouldCheckForChange) { 162 this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); 163 if (!this._hasVisibleEffect) 164 this.dispatchEventToListeners(WI.RecordingAction.Event.HasVisibleEffectChanged); 165 } 166 } catch { 167 this.markInvalid(); 168 169 WI.Recording.synthesizeError(WI.UIString("“%s” threw an error.").format(this._name)); 170 } 171 } 172 173 toJSON() 174 { 175 let json = [this._payloadName, this._payloadParameters, this._payloadSwizzleTypes, this._payloadTrace]; 176 if (this._payloadSnapshot >= 0) 177 json.push(this._payloadSnapshot); 178 return json; 179 } 180 181 // Private 182 183 async _swizzle(recording) 184 { 137 this.apply(context); 138 139 if (this._valid && this._isVisual) 140 this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); 141 142 if (recording.type === WI.Recording.Type.Canvas2D) { 143 let matrix = context.getTransform(); 144 145 this._state = { 146 currentX: context.currentX, 147 currentY: context.currentY, 148 direction: context.direction, 149 fillStyle: context.fillStyle, 150 font: context.font, 151 globalAlpha: context.globalAlpha, 152 globalCompositeOperation: context.globalCompositeOperation, 153 imageSmoothingEnabled: context.imageSmoothingEnabled, 154 imageSmoothingQuality: context.imageSmoothingQuality, 155 lineCap: context.lineCap, 156 lineDash: context.getLineDash(), 157 lineDashOffset: context.lineDashOffset, 158 lineJoin: context.lineJoin, 159 lineWidth: context.lineWidth, 160 miterLimit: context.miterLimit, 161 shadowBlur: context.shadowBlur, 162 shadowColor: context.shadowColor, 163 shadowOffsetX: context.shadowOffsetX, 164 shadowOffsetY: context.shadowOffsetY, 165 strokeStyle: context.strokeStyle, 166 textAlign: context.textAlign, 167 textBaseline: context.textBaseline, 168 transform: [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f], 169 webkitImageSmoothingEnabled: context.webkitImageSmoothingEnabled, 170 webkitLineDash: context.webkitLineDash, 171 webkitLineDashOffset: context.webkitLineDashOffset, 172 }; 173 174 if (WI.ImageUtilities.supportsCanvasPathDebugging()) 175 this._state.setPath = [context.getPath()]; 176 } 177 } 178 179 async swizzle(recording) 180 { 181 if (!this._valid) 182 return; 183 185 184 let swizzleParameter = (item, index) => { 186 185 return recording.swizzle(item, this._payloadSwizzleTypes[index]); … … 216 215 this._snapshot = snapshot; 217 216 217 this._isFunction = WI.RecordingAction.isFunctionForType(recording.type, this._name); 218 this._isGetter = !this._isFunction && !this._parameters.length; 219 220 let visualNames = WI.RecordingAction._visualNames[recording.type]; 221 this._isVisual = visualNames ? visualNames.has(this._name) : false; 222 223 if (this._valid) { 224 let prototype = null; 225 if (recording.type === WI.Recording.Type.Canvas2D) 226 prototype = CanvasRenderingContext2D.prototype; 227 else if (recording.type === WI.Recording.Type.CanvasWebGL) 228 prototype = WebGLRenderingContext.prototype; 229 230 if (prototype) { 231 let validName = name in prototype; 232 let validFunction = !this._isFunction || typeof prototype[name] === "function"; 233 if (!validName || !validFunction) { 234 this.markInvalid(); 235 236 WI.Recording.synthesizeError(WI.UIString("“%s” is invalid.").format(name)); 237 } 238 } 239 } 240 218 241 if (this._valid) { 219 242 let parametersSpecified = this._parameters.every((parameter) => parameter !== undefined); … … 223 246 } 224 247 225 this._isFunction = WI.RecordingAction.isFunctionForType(recording.type, this._name); 226 this._isGetter = !this._isFunction && !this._parameters.length; 227 228 let visualNames = WI.RecordingAction._visualNames[recording.type]; 229 this._isVisual = visualNames ? visualNames.has(this._name) : false; 230 231 this._stateModifiers = new Set([this._name]); 232 let stateModifiers = WI.RecordingAction._stateModifiers[recording.type]; 233 if (stateModifiers) { 234 let modifiedByAction = stateModifiers[this._name] || []; 235 for (let item of modifiedByAction) 236 this._stateModifiers.add(item); 237 } 248 if (this._valid) { 249 let stateModifiers = WI.RecordingAction._stateModifiers[recording.type]; 250 if (stateModifiers) { 251 this._stateModifiers.add(this._name); 252 let modifiedByAction = stateModifiers[this._name] || []; 253 for (let item of modifiedByAction) 254 this._stateModifiers.add(item); 255 } 256 } 257 } 258 259 apply(context, options = {}) 260 { 261 if (!this.valid) 262 return; 263 264 try { 265 let name = options.nameOverride || this._name; 266 if (this.isFunction) 267 context[name](...this._parameters); 268 else { 269 if (this.isGetter) 270 context[name]; 271 else 272 context[name] = this._parameters[0]; 273 } 274 } catch { 275 this.markInvalid(); 276 277 WI.Recording.synthesizeError(WI.UIString("“%s” threw an error.").format(this._name)); 278 } 279 } 280 281 markInvalid() 282 { 283 if (!this._valid) 284 return; 285 286 this._valid = false; 287 288 this.dispatchEventToListeners(WI.RecordingAction.Event.ValidityChanged); 238 289 } 239 290 … … 277 328 278 329 return []; 330 } 331 332 toJSON() 333 { 334 let json = [this._payloadName, this._payloadParameters, this._payloadSwizzleTypes, this._payloadTrace]; 335 if (this._payloadSnapshot >= 0) 336 json.push(this._payloadSnapshot); 337 return json; 279 338 } 280 339 }; … … 311 370 "getImageData", 312 371 "getLineDash", 372 "getPath", 313 373 "isPointInPath", 314 374 "isPointInPath", … … 334 394 "setLineWidth", 335 395 "setMiterLimit", 396 "setPath", 336 397 "setShadow", 337 398 "setStrokeColor", -
trunk/Source/WebInspectorUI/UserInterface/Models/RecordingInitialStateAction.js
r222057 r231218 33 33 34 34 this._valid = false; 35 this._swizzledPromise = Promise.resolve();36 35 } 37 36 }; -
trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.css
r229377 r231218 72 72 line-height: 16px; 73 73 } 74 75 .sidebar > .panel.navigation.canvas > .content > .recording-content > .indeterminate-progress-spinner { 76 margin: 16px auto; 77 } -
trunk/Source/WebInspectorUI/UserInterface/Views/CanvasSidebarPanel.js
r229377 r231218 53 53 this.contentView.addSubview(this._recordingNavigationBar); 54 54 55 let recordingContent= this.contentView.element.appendChild(document.createElement("div"));56 recordingContent.className = "recording-content";55 this._recordingContentContainer = this.contentView.element.appendChild(document.createElement("div")); 56 this._recordingContentContainer.className = "recording-content"; 57 57 58 58 this._recordingTreeOutline = this.contentTreeOutline; 59 recordingContent.appendChild(this._recordingTreeOutline.element);59 this._recordingContentContainer.appendChild(this._recordingTreeOutline.element); 60 60 61 61 this._recordingTreeOutline.customIndent = true; 62 this._recordingTreeOutline.registerScrollVirtualizer( recordingContent, 20);62 this._recordingTreeOutline.registerScrollVirtualizer(this._recordingContentContainer, 20); 63 63 64 64 this._canvasTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeOutlineSelectionDidChange, this); … … 67 67 WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStarted, this._updateRecordNavigationItem, this); 68 68 WI.canvasManager.addEventListener(WI.CanvasManager.Event.RecordingStopped, this._updateRecordNavigationItem, this); 69 70 this._recordingProcessPromise = null; 71 this._recordingProcessSpinner = null; 69 72 } 70 73 … … 109 112 set action(action) 110 113 { 111 if (!this._recording )114 if (!this._recording || this._recordingProcessPromise) 112 115 return; 113 116 … … 287 290 this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] = treeElement.representedObject; 288 291 289 let recordingContentView = this.contentBrowser.showContentViewForRepresentedObject(this._recording); 292 const onlyExisting = true; 293 let recordingContentView = this.contentBrowser.contentViewForRepresentedObject(this._recording, onlyExisting); 290 294 if (recordingContentView) 291 295 recordingContentView.updateActionIndex(treeElement.index); … … 328 332 return; 329 333 334 if (!this._recordingProcessSpinner) { 335 this._recordingProcessSpinner = new WI.IndeterminateProgressSpinner; 336 this._recordingContentContainer.appendChild(this._recordingProcessSpinner.element); 337 } 338 339 this.contentBrowser.showContentViewForRepresentedObject(this._recording); 340 330 341 let recording = this._recording; 331 342 332 this._recording.actions.then((actions) => {333 if (recording !== this._recording )343 let promise = this._recording.process().then(() => { 344 if (recording !== this._recording || promise !== this._recordingProcessPromise) 334 345 return; 335 346 336 this._recordingTreeOutline.element.dataset.indent = Number.countDigits(actions.length); 337 338 if (actions[0] instanceof WI.RecordingInitialStateAction) 339 this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(actions[0], 0, this._recording.type)); 347 if (this._recordingProcessSpinner) { 348 this._recordingProcessSpinner.element.remove(); 349 this._recordingProcessSpinner = null; 350 } 351 352 this._recordingTreeOutline.element.dataset.indent = Number.countDigits(this._recording.actions.length); 353 354 if (this._recording.actions[0] instanceof WI.RecordingInitialStateAction) 355 this._recordingTreeOutline.appendChild(new WI.RecordingActionTreeElement(this._recording.actions[0], 0, this._recording.type)); 340 356 341 357 let cumulativeActionIndex = 1; … … 367 383 } 368 384 369 this.action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] || actions[0]; 385 this.action = this._recording[WI.CanvasSidebarPanel.SelectedActionSymbol] || this._recording.actions[0]; 386 387 this._recordingProcessPromise = null; 370 388 }); 389 390 this._recordingProcessPromise = promise; 371 391 } 372 392 -
trunk/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js
r225884 r231218 40 40 41 41 this.representedObject.addEventListener(WI.RecordingAction.Event.ValidityChanged, this._handleValidityChanged, this); 42 this.representedObject.addEventListener(WI.RecordingAction.Event.HasVisibleEffectChanged, this._handleHasVisibleEffectChanged, this);43 42 } 44 43 … … 400 399 401 400 this.element.dataset.index = this._index.toLocaleString(); 401 402 if (this.representedObject.valid && this.representedObject.isVisual && !this.representedObject.hasVisibleEffect) { 403 this.addClassName("no-visible-effect"); 404 405 const title = WI.UIString("This action causes no visual change"); 406 this.status = WI.ImageUtilities.useSVGSymbol("Images/Warning.svg", "warning", title); 407 } 402 408 } 403 409 … … 431 437 this.addClassName("invalid"); 432 438 } 433 434 _handleHasVisibleEffectChanged(event)435 {436 this.addClassName("no-visible-effect");437 438 this.status = WI.ImageUtilities.useSVGSymbol("Images/Warning.svg", "warning");439 this.status.title = WI.UIString("This action causes no visual change");440 }441 439 }; 442 440 -
trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.css
r228301 r231218 68 68 justify-content: center; 69 69 align-items: center; 70 position: relative; 71 width: -webkit-fill-available; 72 height: -webkit-fill-available; 70 73 } 71 74 -
trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js
r228301 r231218 43 43 let isCanvasWebGL = this.representedObject.type === WI.Recording.Type.CanvasWebGL; 44 44 if (isCanvas2D || isCanvasWebGL) { 45 if (isCanvas2D && WI. RecordingContentView.supportsCanvasPathDebugging()) {45 if (isCanvas2D && WI.ImageUtilities.supportsCanvasPathDebugging()) { 46 46 this._pathContext = null; 47 47 … … 62 62 this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this._exportRecording(); }); 63 63 } 64 65 this._processing = true; 66 this._processMessageTextView = null; 64 67 } 65 68 66 69 // Static 67 68 static supportsCanvasPathDebugging()69 {70 return "currentX" in CanvasRenderingContext2D.prototype && "currentY" in CanvasRenderingContext2D.prototype;71 }72 70 73 71 static _actionModifiesPath(recordingAction) … … 100 98 101 99 let navigationItems = [this._exportButtonNavigationItem, new WI.DividerNavigationItem]; 102 if (isCanvas2D && WI. RecordingContentView.supportsCanvasPathDebugging())100 if (isCanvas2D && WI.ImageUtilities.supportsCanvasPathDebugging()) 103 101 navigationItems.push(this._showPathButtonNavigationItem); 104 102 … … 120 118 return; 121 119 122 this.representedObject.actions.then((actions) => { 123 console.assert(index >= 0 && index < actions.length); 124 if (index < 0 || index >= actions.length) 125 return; 126 127 this._index = index; 128 this._updateSliderValue(); 129 130 if (this.representedObject.type === WI.Recording.Type.Canvas2D) 131 this._throttler._generateContentCanvas2D(index, actions); 132 else if (this.representedObject.type === WI.Recording.Type.CanvasWebGL) 133 this._throttler._generateContentCanvasWebGL(index, actions); 134 }); 120 console.assert(index >= 0 && index < this.representedObject.actions.length); 121 if (index < 0 || index >= this.representedObject.actions.length) 122 return; 123 124 this._index = index; 125 126 if (this._processing) 127 return; 128 129 this._updateSliderValue(); 130 131 if (this.representedObject.type === WI.Recording.Type.Canvas2D) 132 this._throttler._generateContentCanvas2D(index); 133 else if (this.representedObject.type === WI.Recording.Type.CanvasWebGL) 134 this._throttler._generateContentCanvasWebGL(index); 135 136 this._action = this.representedObject.actions[this._index]; 137 138 this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); 135 139 } 136 140 … … 189 193 this._sliderElement.max = 0; 190 194 191 this.representedObject.actions.then(() => { 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 192 202 sliderContainer.classList.remove("hidden"); 193 203 this._sliderElement.max = this.representedObject.visualActionIndexes.length; 194 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 } 195 213 }); 196 214 } … … 215 233 } 216 234 217 async _generateContentCanvas2D(index, actions)235 _generateContentCanvas2D(index) 218 236 { 219 237 let imageLoad = (event) => { … … 222 240 return; 223 241 224 this._generateContentCanvas2D(index , actions);242 this._generateContentCanvas2D(index); 225 243 }; 226 244 … … 236 254 let snapshot = this._snapshots[snapshotIndex]; 237 255 238 let showCanvasPath = WI. RecordingContentView.supportsCanvasPathDebugging() && WI.settings.showCanvasPath.value;256 let showCanvasPath = WI.ImageUtilities.supportsCanvasPathDebugging() && WI.settings.showCanvasPath.value; 239 257 let indexOfLastBeginPathAction = Infinity; 258 259 let actions = this.representedObject.actions; 240 260 241 261 let applyActions = (from, to, callback) => { … … 332 352 this._pathContext.canvas.remove(); 333 353 334 let state = {335 currentX: snapshot.context.currentX,336 currentY: snapshot.context.currentY,337 direction: snapshot.context.direction,338 fillStyle: snapshot.context.fillStyle,339 font: snapshot.context.font,340 globalAlpha: snapshot.context.globalAlpha,341 globalCompositeOperation: snapshot.context.globalCompositeOperation,342 imageSmoothingEnabled: snapshot.context.imageSmoothingEnabled,343 imageSmoothingQuality: snapshot.context.imageSmoothingQuality,344 lineCap: snapshot.context.lineCap,345 lineDash: snapshot.context.getLineDash(),346 lineDashOffset: snapshot.context.lineDashOffset,347 lineJoin: snapshot.context.lineJoin,348 lineWidth: snapshot.context.lineWidth,349 miterLimit: snapshot.context.miterLimit,350 shadowBlur: snapshot.context.shadowBlur,351 shadowColor: snapshot.context.shadowColor,352 shadowOffsetX: snapshot.context.shadowOffsetX,353 shadowOffsetY: snapshot.context.shadowOffsetY,354 strokeStyle: snapshot.context.strokeStyle,355 textAlign: snapshot.context.textAlign,356 textBaseline: snapshot.context.textBaseline,357 transform: snapshot.context.getTransform(),358 webkitImageSmoothingEnabled: snapshot.context.webkitImageSmoothingEnabled,359 webkitLineDash: snapshot.context.webkitLineDash,360 webkitLineDashOffset: snapshot.context.webkitLineDashOffset,361 };362 363 if (WI.RecordingContentView.supportsCanvasPathDebugging())364 state.setPath = [snapshot.context.getPath()];365 366 354 snapshot.context.restore(); 367 355 while (saveCount-- > 0) 368 356 snapshot.context.restore(); 369 370 return state;371 357 }; 372 358 … … 377 363 --snapshot.index; 378 364 379 snapshot.element = document.createElement("canvas"); 380 snapshot.context = snapshot.element.getContext("2d", ...initialState.parameters); 381 if ("width" in initialState.attributes) 382 snapshot.element.width = initialState.attributes.width; 383 if ("height" in initialState.attributes) 384 snapshot.element.height = initialState.attributes.height; 365 snapshot.context = this.representedObject.createContext(); 366 snapshot.element = snapshot.context.canvas; 385 367 386 368 let lastSnapshotIndex = snapshotIndex; … … 393 375 if (lastSnapshotIndex < 0) { 394 376 snapshot.content = this._initialContent; 395 snapshot.state = {}; 396 397 for (let key in initialState.attributes) { 398 let value = initialState.attributes[key]; 399 400 switch (key) { 401 case "setTransform": 402 value = [await this.representedObject.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; 403 break; 404 405 case "fillStyle": 406 case "strokeStyle": 407 if (Array.isArray(value)) { 408 let canvasStyle = await this.representedObject.swizzle(value[0], WI.Recording.Swizzle.String); 409 if (canvasStyle.includes("gradient")) 410 value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.CanvasGradient); 411 else if (canvasStyle === "pattern") 412 value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.CanvasPattern); 413 } else 414 value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.String); 415 break; 416 417 case "direction": 418 case "font": 419 case "globalCompositeOperation": 420 case "imageSmoothingEnabled": 421 case "imageSmoothingQuality": 422 case "lineCap": 423 case "lineJoin": 424 case "shadowColor": 425 case "textAlign": 426 case "textBaseline": 427 value = await this.representedObject.swizzle(value, WI.Recording.Swizzle.String); 428 break; 429 430 case "setPath": 431 value = [await this.representedObject.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; 432 break; 433 } 434 435 if (value === undefined || (Array.isArray(value) && value.includes(undefined))) 436 continue; 437 438 snapshot.state[key] = value; 439 } 377 snapshot.state = actions[0].state; 440 378 } else { 441 379 snapshot.content = this._snapshots[lastSnapshotIndex].content; … … 444 382 } 445 383 446 snapshot.state = applyActions(startIndex, snapshot.index - 1); 384 applyActions(startIndex, snapshot.index - 1); 385 if (snapshot.index > 0) 386 snapshot.state = actions[snapshot.index - 1].state; 447 387 448 388 snapshot.content = new Image; … … 460 400 } 461 401 462 this._action = actions[this._index]; 463 464 let state = applyActions(snapshot.index, this._index); 465 console.assert(!this._action.state || Object.shallowEqual(this._action.state, state)); 466 if (!this._action.state) 467 this._action.state = state; 468 469 this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); 402 applyActions(snapshot.index, this._index); 470 403 471 404 this._previewContainer.appendChild(snapshot.element); … … 473 406 } 474 407 475 async _generateContentCanvasWebGL(index, actions)408 _generateContentCanvasWebGL(index) 476 409 { 477 410 let imageLoad = (event) => { … … 480 413 return; 481 414 482 this._generateContentCanvasWebGL(index , actions);415 this._generateContentCanvasWebGL(index); 483 416 }; 484 417 … … 490 423 return; 491 424 } 425 426 let actions = this.representedObject.actions; 492 427 493 428 let visualIndex = index; … … 514 449 this._updateImageGrid(); 515 450 } 516 517 this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange);518 451 } 519 452 … … 521 454 { 522 455 let activated = WI.settings.showCanvasPath.value; 523 if (this._showPathButtonNavigationItem.activated !== activated) { 524 this.representedObject.actions.then((actions) => { 525 this._generateContentCanvas2D(this._index, actions); 526 }); 527 } 456 457 if (this._showPathButtonNavigationItem.activated !== activated && !this._processing) 458 this._generateContentCanvas2D(this._index); 528 459 529 460 this._showPathButtonNavigationItem.activated = activated; … … 556 487 } 557 488 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 558 501 _showPathButtonClicked(event) 559 502 { … … 580 523 this.updateActionIndex(index); 581 524 } 525 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); 534 } 582 535 }; 583 536
Note: See TracChangeset
for help on using the changeset viewer.