Changeset 237198 in webkit
- Timestamp:
- Oct 16, 2018 12:23:21 PM (6 years ago)
- Location:
- trunk
- Files:
-
- 20 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r237192 r237198 1 2018-10-16 Devin Rousso <drousso@apple.com> 2 3 Web Inspector: Canvas: capture previously saved states and add them to the recording payload 4 https://bugs.webkit.org/show_bug.cgi?id=190473 5 6 Reviewed by Joseph Pecoraro. 7 8 * inspector/canvas/recording-2d-expected.txt: 9 * inspector/canvas/recording-2d.html: 10 * inspector/canvas/resources/recording-utilities.js: 11 (TestPage.registerInitializer): 12 (TestPage.registerInitializer.async.logRecording): Added. 13 (TestPage.registerInitializer.logRecording): Deleted. 14 * inspector/model/recording-expected.txt: 15 * inspector/model/recording.html: 16 1 17 2018-10-16 Justin Michaud <justin_michaud@apple.com> 2 18 -
trunk/LayoutTests/inspector/canvas/recording-2d-expected.txt
r236008 r237198 8 8 width: 2 9 9 height: 2 10 setTransform: [1,0,0,1,0,0] 10 current state: 11 setTransform: [[1,0,0,1,0,0]] 11 12 globalAlpha: 1 12 globalCompositeOperation: 013 globalCompositeOperation: "source-over" 13 14 lineWidth: 1 14 lineCap: 115 lineJoin: 215 lineCap: "butt" 16 lineJoin: "miter" 16 17 miterLimit: 10 17 18 shadowOffsetX: 0 18 19 shadowOffsetY: 0 19 20 shadowBlur: 0 20 shadowColor: 321 shadowColor: "rgba(0, 0, 0, 0)" 21 22 setLineDash: [[]] 22 23 lineDashOffset: 0 23 font: 424 textAlign: 525 textBaseline: 626 direction: 727 strokeStyle: 828 fillStyle: 824 font: "10px sans-serif" 25 textAlign: "start" 26 textBaseline: "alphabetic" 27 direction: "ltr" 28 strokeStyle: "#000000" 29 fillStyle: "#000000" 29 30 imageSmoothingEnabled: true 30 imageSmoothingQuality: 931 setPath: [ 10]31 imageSmoothingQuality: "low" 32 setPath: [{}] 32 33 parameters: 33 34 content: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAAtJREFUCB1jYEAHAAASAAGAFMrMAAAAAElFTkSuQmCC" … … 66 67 width: 2 67 68 height: 2 68 setTransform: [1,0,0,1,0,0] 69 current state: 70 setTransform: [[1,0,0,1,0,0]] 69 71 globalAlpha: 1 70 globalCompositeOperation: 072 globalCompositeOperation: "source-over" 71 73 lineWidth: 1 72 lineCap: 173 lineJoin: 274 lineCap: "butt" 75 lineJoin: "miter" 74 76 miterLimit: 10 75 77 shadowOffsetX: 0 76 78 shadowOffsetY: 0 77 79 shadowBlur: 0 78 shadowColor: 380 shadowColor: "rgba(0, 0, 0, 0)" 79 81 setLineDash: [[]] 80 82 lineDashOffset: 0 81 font: 482 textAlign: 583 textBaseline: 684 direction: 785 strokeStyle: 886 fillStyle: 883 font: "10px sans-serif" 84 textAlign: "start" 85 textBaseline: "alphabetic" 86 direction: "ltr" 87 strokeStyle: "#000000" 88 fillStyle: "#000000" 87 89 imageSmoothingEnabled: true 88 imageSmoothingQuality: 989 setPath: [ 10]90 imageSmoothingQuality: "low" 91 setPath: [{}] 90 92 parameters: 91 93 content: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAAtJREFUCB1jYEAHAAASAAGAFMrMAAAAAElFTkSuQmCC" … … 1072 1074 width: 2 1073 1075 height: 2 1074 setTransform: [1,0,0,1,0,0] 1076 current state: 1077 setTransform: [[1,0,0,1,0,0]] 1075 1078 globalAlpha: 1 1076 globalCompositeOperation: 01079 globalCompositeOperation: "source-over" 1077 1080 lineWidth: 1 1078 lineCap: 11079 lineJoin: 21081 lineCap: "butt" 1082 lineJoin: "miter" 1080 1083 miterLimit: 10 1081 1084 shadowOffsetX: 0 1082 1085 shadowOffsetY: 0 1083 1086 shadowBlur: 0 1084 shadowColor: 31087 shadowColor: "rgba(0, 0, 0, 0)" 1085 1088 setLineDash: [[]] 1086 1089 lineDashOffset: 0 1087 font: 41088 textAlign: 51089 textBaseline: 61090 direction: 71091 strokeStyle: 81092 fillStyle: 81090 font: "10px sans-serif" 1091 textAlign: "start" 1092 textBaseline: "alphabetic" 1093 direction: "ltr" 1094 strokeStyle: "#000000" 1095 fillStyle: "#000000" 1093 1096 imageSmoothingEnabled: true 1094 imageSmoothingQuality: 91095 setPath: [ 10]1097 imageSmoothingQuality: "low" 1098 setPath: [{}] 1096 1099 parameters: 1097 1100 content: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAAtJREFUCB1jYEAHAAASAAGAFMrMAAAAAElFTkSuQmCC" … … 1123 1126 PASS: The parameter should be null. 1124 1127 1128 -- Running test case: Canvas.recording2D.ExistingSaves 1129 PASS: There should be 4 existing states. 1130 PASS: State 0 should match expected fillStyle value. 1131 PASS: State 1 should match expected fillStyle value. 1132 PASS: State 2 should match expected fillStyle value. 1133 PASS: State 3 should match expected fillStyle value. 1134 1125 1135 -- Running test case: Canvas.recording2D.NoActions 1126 1136 PASS: A recording should have been started and stopped once. -
trunk/LayoutTests/inspector/canvas/recording-2d.html
r237010 r237198 43 43 44 44 ctx.save(); 45 c tx.save(); // This matches the `restore` call in `performActions`.45 cancelActions(); 46 46 47 47 runTest(); … … 55 55 56 56 let timeoutID = NaN; 57 let restoreCalled = false;57 let saveCount = 1; 58 58 59 59 function cancelActions() { 60 if (!isNaN(timeoutID)) {60 for (let i = 0; i < saveCount; ++i) 61 61 ctx.restore(); 62 if (!restoreCalled) 63 ctx.restore(); 64 } 62 ctx.restore(); // Ensures the state is reset between test cases. 65 63 66 64 clearTimeout(timeoutID); 67 65 timeoutID = NaN; 68 66 69 ctx.save(); 70 ctx.save(); 67 ctx.save(); // Ensures the state is reset between test cases. 68 ctx.save(); // This matches the `restore` call in `performActions`. 69 saveCount = 1; 70 71 71 ctx.resetTransform(); 72 72 ctx.beginPath(); 73 73 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 74 75 restoreCalled = false;76 74 } 77 75 … … 258 256 () => { 259 257 ctx.restore(); 260 261 restoreCalled = true; 258 --saveCount; 262 259 }, 263 260 () => { … … 266 263 () => { 267 264 ctx.save(); 265 ++saveCount; 268 266 }, 269 267 () => { … … 409 407 } 410 408 409 function performSavePreActions() { 410 cancelActions(); 411 ctx.restore(); 412 ctx.restore(); 413 414 function saveFillStyle(value) { 415 ctx.save(); 416 ctx.fillStyle = value; 417 } 418 419 saveFillStyle("#ff0000"); 420 saveFillStyle("#00ff00"); 421 saveFillStyle("#0000ff"); 422 } 423 424 function performSavePostActions() { 425 ctx.fill(); 426 } 427 411 428 function performNaNActions() { 412 429 ctx.globalAlpha = NaN; … … 484 501 485 502 suite.addTestCase({ 503 name: "Canvas.recording2D.ExistingSaves", 504 description: "Check that existing save calls are sent to the frontend.", 505 test(resolve, reject) { 506 let canvas = getCanvas(WI.Canvas.ContextType.Canvas2D); 507 if (!canvas) { 508 reject("Missing 2D canvas."); 509 return; 510 } 511 512 async function logStates(recording) { 513 async function compare(index, expected) { 514 let swizzledState = await recording._swizzleState(recording.initialState.states[index]); 515 InspectorTest.expectEqual(swizzledState["fillStyle"], expected, `State ${index} should match expected fillStyle value.`) 516 } 517 518 await compare(0, "#000000"); 519 await compare(1, "#ff0000"); 520 await compare(2, "#00ff00"); 521 await compare(3, "#0000ff"); 522 } 523 524 canvas.awaitEvent(WI.Canvas.Event.RecordingStopped) 525 .then((event) => { 526 let {recording} = event.data; 527 528 InspectorTest.expectEqual(recording.initialState.states.length, 4, "There should be 4 existing states."); 529 530 logStates(recording) 531 .then(resolve, reject); 532 }); 533 534 canvas.awaitEvent(WI.Canvas.Event.RecordingStarted) 535 .then((event) => { 536 InspectorTest.evaluateInPage(`performSavePostActions()`).catch(reject); 537 }); 538 539 InspectorTest.evaluateInPage(`performSavePreActions()`) 540 .then(() => { 541 const singleFrame = true; 542 CanvasAgent.startRecording(canvas.identifier, singleFrame).catch(reject); 543 }, reject); 544 }, 545 }); 546 547 suite.addTestCase({ 486 548 name: "Canvas.recording2D.NoActions", 487 549 description: "Check that a canvas is still able to be recorded after stopping a recording with no actions.", -
trunk/LayoutTests/inspector/canvas/resources/recording-utilities.js
r237010 r237198 5 5 if (typeof value === "string") 6 6 value = sanitizeURL(value); 7 else if (Array.isArray(value) && value[0] instanceof DOMMatrix) 8 value[0] = [value[0].a, value[0].b, value[0].c, value[0].d, value[0].e, value[0].f]; 7 9 InspectorTest.log(indent + key + ": " + JSON.stringify(value)); 8 10 } 9 11 } 10 12 11 function logRecording(recording) {13 async function logRecording(recording) { 12 14 InspectorTest.log("initialState:"); 13 15 14 16 InspectorTest.log(" attributes:"); 15 17 log(recording.initialState.attributes, " "); 18 19 let currentState = recording.initialState.states.lastValue; 20 if (currentState) { 21 InspectorTest.log(" current state:"); 22 let swizzledState = await recording._swizzleState(currentState); 23 log(swizzledState, " "); 24 } 16 25 17 26 InspectorTest.log(" parameters:"); … … 93 102 CanvasAgent.stopRecording(canvas.identifier).catch(reject); 94 103 else { 95 InspectorTest.evaluateInPage(`cancelActions()`).catch(reject); 96 97 if (swizzled) 98 resolve(); 104 InspectorTest.evaluateInPage(`cancelActions()`) 105 .then(() => { 106 if (swizzled) 107 resolve(); 108 }, reject); 99 109 } 100 110 }); … … 125 135 swizzled = true; 126 136 127 logRecording(recording, type); 128 129 if (lastFrame) { 130 InspectorTest.evaluateInPage(`cancelActions()`) 131 .then(resolve, reject); 132 } 137 logRecording(recording, type) 138 .then(() => { 139 if (lastFrame) { 140 InspectorTest.evaluateInPage(`cancelActions()`) 141 .then(resolve, reject); 142 } 143 }, reject); 133 144 }); 134 145 }); -
trunk/LayoutTests/inspector/model/recording-expected.txt
r224389 r237198 38 38 "test": "test" 39 39 }, 40 "states": [ 41 { 42 "test": "test" 43 } 44 ], 40 45 "parameters": [ 41 46 "test" … … 61 66 "test": "test" 62 67 }, 68 "states": [ 69 { 70 "test": "test" 71 } 72 ], 63 73 "parameters": [ 64 74 "test" … … 93 103 "test": "test" 94 104 }, 105 "states": [ 106 { 107 "test": "test" 108 } 109 ], 95 110 "parameters": [ 96 111 "test" … … 125 140 "test": "test" 126 141 }, 142 "states": [ 143 { 144 "test": "test" 145 } 146 ], 127 147 "parameters": [ 128 148 "test" -
trunk/LayoutTests/inspector/model/recording.html
r224389 r237198 49 49 initialState: { 50 50 attributes: null, 51 states: null, 51 52 parameters: null, 52 53 content: null, … … 65 66 test: "test", 66 67 }, 68 states: [ 69 { 70 test: "test", 71 }, 72 ], 67 73 parameters: ["test"], 68 74 content: "test", … … 87 93 test: "test", 88 94 }, 95 states: [ 96 { 97 test: "test", 98 }, 99 ], 89 100 parameters: ["test"], 90 101 content: "test", … … 109 120 test: "test", 110 121 }, 122 states: [ 123 { 124 test: "test", 125 }, 126 ], 111 127 parameters: ["test"], 112 128 content: "test", … … 138 154 test: "test", 139 155 }, 156 states: [ 157 { 158 test: "test", 159 }, 160 ], 140 161 parameters: ["test"], 141 162 content: "test", -
trunk/Source/JavaScriptCore/ChangeLog
r237197 r237198 1 2018-10-16 Devin Rousso <drousso@apple.com> 2 3 Web Inspector: Canvas: capture previously saved states and add them to the recording payload 4 https://bugs.webkit.org/show_bug.cgi?id=190473 5 6 Reviewed by Joseph Pecoraro. 7 8 * inspector/protocol/Recording.json: 9 Add `states` key to `InitialState` object. 10 1 11 2018-10-16 Keith Miller <keith_miller@apple.com> 2 12 -
trunk/Source/JavaScriptCore/inspector/protocol/Recording.json
r237010 r237198 20 20 "properties": [ 21 21 { "name": "attributes", "type": "object", "optional": true, "description": "Key-value map for each attribute of the state." }, 22 { "name": "states", "type": "array", "items": { "type": "object" }, "optional": true, "description": "Array of saved states of the context." }, 22 23 { "name": "parameters", "type": "array", "items": { "type": "any" }, "optional": true, "description": "Array of values that were used to construct the recorded object." }, 23 24 { "name": "content", "type": "string", "optional": true, "description": "Current content at the start of the recording." } -
trunk/Source/WebCore/ChangeLog
r237192 r237198 1 2018-10-16 Devin Rousso <drousso@apple.com> 2 3 Web Inspector: Canvas: capture previously saved states and add them to the recording payload 4 https://bugs.webkit.org/show_bug.cgi?id=190473 5 6 Reviewed by Joseph Pecoraro. 7 8 Updated existing tests: inspector/canvas/recording-2d.html 9 inspector/model/recording.html 10 11 Instead of sending a single object of the current state of the context, send an array of 12 objects, one for each restore point. 13 14 * html/canvas/CanvasRenderingContext2DBase.h: 15 * html/canvas/CanvasRenderingContext2DBase.cpp: 16 (WebCore::CanvasRenderingContext2DBase::stateStack): Added. 17 18 * inspector/InspectorCanvas.h: 19 * inspector/InspectorCanvas.cpp: 20 (WebCore::InspectorCanvas::stringIndexForKey): Added. 21 (WebCore::InspectorCanvas::buildInitialState): 22 1 23 2018-10-16 Justin Michaud <justin_michaud@apple.com> 2 24 -
trunk/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp
r234610 r237198 2064 2064 } 2065 2065 2066 const Vector<CanvasRenderingContext2DBase::State, 1>& CanvasRenderingContext2DBase::stateStack() 2067 { 2068 realizeSaves(); 2069 return m_stateStack; 2070 } 2071 2066 2072 void CanvasRenderingContext2DBase::paintRenderingResultsToCanvas() 2067 2073 { -
trunk/Source/WebCore/html/canvas/CanvasRenderingContext2DBase.h
r227050 r237198 275 275 276 276 const State& state() const { return m_stateStack.last(); } 277 const Vector<State, 1>& stateStack(); 277 278 278 279 protected: -
trunk/Source/WebCore/inspector/InspectorCanvas.cpp
r236954 r237198 407 407 } 408 408 409 String InspectorCanvas::stringIndexForKey(const String& key) 410 { 411 return String::number(indexForData(key)); 412 } 413 409 414 static Ref<JSON::ArrayOf<double>> buildArrayForAffineTransform(const AffineTransform& affineTransform) 410 415 { … … 429 434 Ref<Inspector::Protocol::Recording::InitialState> InspectorCanvas::buildInitialState() 430 435 { 431 auto initialState = Inspector::Protocol::Recording::InitialState::create().release(); 432 433 auto attributes = JSON::Object::create(); 434 attributes->setInteger("width"_s, m_context.canvasBase().width()); 435 attributes->setInteger("height"_s, m_context.canvasBase().height()); 436 437 auto parameters = JSON::ArrayOf<JSON::Value>::create(); 436 auto initialStatePayload = Inspector::Protocol::Recording::InitialState::create().release(); 437 438 auto attributesPayload = JSON::Object::create(); 439 attributesPayload->setInteger("width"_s, m_context.canvasBase().width()); 440 attributesPayload->setInteger("height"_s, m_context.canvasBase().height()); 441 442 auto statesPayload = JSON::ArrayOf<JSON::Object>::create(); 443 444 auto parametersPayload = JSON::ArrayOf<JSON::Value>::create(); 438 445 439 446 if (is<CanvasRenderingContext2D>(m_context)) { 440 447 auto& context2d = downcast<CanvasRenderingContext2D>(m_context); 441 auto& state = context2d.state(); 442 443 attributes->setArray("setTransform"_s, buildArrayForAffineTransform(state.transform)); 444 attributes->setDouble("globalAlpha"_s, context2d.globalAlpha()); 445 attributes->setInteger("globalCompositeOperation"_s, indexForData(context2d.globalCompositeOperation())); 446 attributes->setDouble("lineWidth"_s, context2d.lineWidth()); 447 attributes->setInteger("lineCap"_s, indexForData(convertEnumerationToString(context2d.lineCap()))); 448 attributes->setInteger("lineJoin"_s, indexForData(convertEnumerationToString(context2d.lineJoin()))); 449 attributes->setDouble("miterLimit"_s, context2d.miterLimit()); 450 attributes->setDouble("shadowOffsetX"_s, context2d.shadowOffsetX()); 451 attributes->setDouble("shadowOffsetY"_s, context2d.shadowOffsetY()); 452 attributes->setDouble("shadowBlur"_s, context2d.shadowBlur()); 453 attributes->setInteger("shadowColor"_s, indexForData(context2d.shadowColor())); 454 455 // The parameter to `setLineDash` is itself an array, so we need to wrap the parameters 456 // list in an array to allow spreading. 457 auto setLineDash = JSON::ArrayOf<JSON::Value>::create(); 458 setLineDash->addItem(buildArrayForVector(state.lineDash)); 459 attributes->setArray("setLineDash"_s, WTFMove(setLineDash)); 460 461 attributes->setDouble("lineDashOffset"_s, context2d.lineDashOffset()); 462 attributes->setInteger("font"_s, indexForData(context2d.font())); 463 attributes->setInteger("textAlign"_s, indexForData(convertEnumerationToString(context2d.textAlign()))); 464 attributes->setInteger("textBaseline"_s, indexForData(convertEnumerationToString(context2d.textBaseline()))); 465 attributes->setInteger("direction"_s, indexForData(convertEnumerationToString(context2d.direction()))); 466 467 int strokeStyleIndex; 468 if (auto canvasGradient = state.strokeStyle.canvasGradient()) 469 strokeStyleIndex = indexForData(canvasGradient.get()); 470 else if (auto canvasPattern = state.strokeStyle.canvasPattern()) 471 strokeStyleIndex = indexForData(canvasPattern.get()); 472 else 473 strokeStyleIndex = indexForData(state.strokeStyle.color()); 474 attributes->setInteger("strokeStyle"_s, strokeStyleIndex); 475 476 int fillStyleIndex; 477 if (auto canvasGradient = state.fillStyle.canvasGradient()) 478 fillStyleIndex = indexForData(canvasGradient.get()); 479 else if (auto canvasPattern = state.fillStyle.canvasPattern()) 480 fillStyleIndex = indexForData(canvasPattern.get()); 481 else 482 fillStyleIndex = indexForData(state.fillStyle.color()); 483 attributes->setInteger("fillStyle"_s, fillStyleIndex); 484 485 attributes->setBoolean("imageSmoothingEnabled"_s, context2d.imageSmoothingEnabled()); 486 attributes->setInteger("imageSmoothingQuality"_s, indexForData(convertEnumerationToString(context2d.imageSmoothingQuality()))); 487 488 auto setPath = JSON::ArrayOf<JSON::Value>::create(); 489 setPath->addItem(indexForData(buildStringFromPath(context2d.getPath()->path()))); 490 attributes->setArray("setPath"_s, WTFMove(setPath)); 448 for (auto& state : context2d.stateStack()) { 449 RefPtr<JSON::Object> statePayload = JSON::Object::create(); 450 451 statePayload->setArray(stringIndexForKey("setTransform"_s), buildArrayForAffineTransform(state.transform)); 452 statePayload->setDouble(stringIndexForKey("globalAlpha"_s), context2d.globalAlpha()); 453 statePayload->setInteger(stringIndexForKey("globalCompositeOperation"_s), indexForData(context2d.globalCompositeOperation())); 454 statePayload->setDouble(stringIndexForKey("lineWidth"_s), context2d.lineWidth()); 455 statePayload->setInteger(stringIndexForKey("lineCap"_s), indexForData(convertEnumerationToString(context2d.lineCap()))); 456 statePayload->setInteger(stringIndexForKey("lineJoin"_s), indexForData(convertEnumerationToString(context2d.lineJoin()))); 457 statePayload->setDouble(stringIndexForKey("miterLimit"_s), context2d.miterLimit()); 458 statePayload->setDouble(stringIndexForKey("shadowOffsetX"_s), context2d.shadowOffsetX()); 459 statePayload->setDouble(stringIndexForKey("shadowOffsetY"_s), context2d.shadowOffsetY()); 460 statePayload->setDouble(stringIndexForKey("shadowBlur"_s), context2d.shadowBlur()); 461 statePayload->setInteger(stringIndexForKey("shadowColor"_s), indexForData(context2d.shadowColor())); 462 463 // The parameter to `setLineDash` is itself an array, so we need to wrap the parameters 464 // list in an array to allow spreading. 465 auto setLineDash = JSON::ArrayOf<JSON::Value>::create(); 466 setLineDash->addItem(buildArrayForVector(state.lineDash)); 467 statePayload->setArray(stringIndexForKey("setLineDash"_s), WTFMove(setLineDash)); 468 469 statePayload->setDouble(stringIndexForKey("lineDashOffset"_s), context2d.lineDashOffset()); 470 statePayload->setInteger(stringIndexForKey("font"_s), indexForData(context2d.font())); 471 statePayload->setInteger(stringIndexForKey("textAlign"_s), indexForData(convertEnumerationToString(context2d.textAlign()))); 472 statePayload->setInteger(stringIndexForKey("textBaseline"_s), indexForData(convertEnumerationToString(context2d.textBaseline()))); 473 statePayload->setInteger(stringIndexForKey("direction"_s), indexForData(convertEnumerationToString(context2d.direction()))); 474 475 int strokeStyleIndex; 476 if (auto canvasGradient = state.strokeStyle.canvasGradient()) 477 strokeStyleIndex = indexForData(canvasGradient.get()); 478 else if (auto canvasPattern = state.strokeStyle.canvasPattern()) 479 strokeStyleIndex = indexForData(canvasPattern.get()); 480 else 481 strokeStyleIndex = indexForData(state.strokeStyle.color()); 482 statePayload->setInteger(stringIndexForKey("strokeStyle"_s), strokeStyleIndex); 483 484 int fillStyleIndex; 485 if (auto canvasGradient = state.fillStyle.canvasGradient()) 486 fillStyleIndex = indexForData(canvasGradient.get()); 487 else if (auto canvasPattern = state.fillStyle.canvasPattern()) 488 fillStyleIndex = indexForData(canvasPattern.get()); 489 else 490 fillStyleIndex = indexForData(state.fillStyle.color()); 491 statePayload->setInteger(stringIndexForKey("fillStyle"_s), fillStyleIndex); 492 493 statePayload->setBoolean(stringIndexForKey("imageSmoothingEnabled"_s), context2d.imageSmoothingEnabled()); 494 statePayload->setInteger(stringIndexForKey("imageSmoothingQuality"_s), indexForData(convertEnumerationToString(context2d.imageSmoothingQuality()))); 495 496 auto setPath = JSON::ArrayOf<JSON::Value>::create(); 497 setPath->addItem(indexForData(buildStringFromPath(context2d.getPath()->path()))); 498 statePayload->setArray(stringIndexForKey("setPath"_s), WTFMove(setPath)); 499 500 statesPayload->addItem(WTFMove(statePayload)); 501 } 491 502 } 492 503 #if ENABLE(WEBGL) 493 504 else if (is<WebGLRenderingContextBase>(m_context)) { 494 505 WebGLRenderingContextBase& contextWebGLBase = downcast<WebGLRenderingContextBase>(m_context); 495 if (std::optional<WebGLContextAttributes> attributes = contextWebGLBase.getContextAttributes()) {496 RefPtr<JSON::Object> contextAttributes= JSON::Object::create();497 contextAttributes->setBoolean("alpha"_s, attributes->alpha);498 contextAttributes->setBoolean("depth"_s, attributes->depth);499 contextAttributes->setBoolean("stencil"_s, attributes->stencil);500 contextAttributes->setBoolean("antialias"_s, attributes->antialias);501 contextAttributes->setBoolean("premultipliedAlpha"_s, attributes->premultipliedAlpha);502 contextAttributes->setBoolean("preserveDrawingBuffer"_s, attributes->preserveDrawingBuffer);503 contextAttributes->setBoolean("failIfMajorPerformanceCaveat"_s, attributes->failIfMajorPerformanceCaveat);504 parameters ->addItem(WTFMove(contextAttributes));506 if (std::optional<WebGLContextAttributes> webGLContextAttributes = contextWebGLBase.getContextAttributes()) { 507 RefPtr<JSON::Object> webGLContextAttributesPayload = JSON::Object::create(); 508 webGLContextAttributesPayload->setBoolean("alpha"_s, webGLContextAttributes->alpha); 509 webGLContextAttributesPayload->setBoolean("depth"_s, webGLContextAttributes->depth); 510 webGLContextAttributesPayload->setBoolean("stencil"_s, webGLContextAttributes->stencil); 511 webGLContextAttributesPayload->setBoolean("antialias"_s, webGLContextAttributes->antialias); 512 webGLContextAttributesPayload->setBoolean("premultipliedAlpha"_s, webGLContextAttributes->premultipliedAlpha); 513 webGLContextAttributesPayload->setBoolean("preserveDrawingBuffer"_s, webGLContextAttributes->preserveDrawingBuffer); 514 webGLContextAttributesPayload->setBoolean("failIfMajorPerformanceCaveat"_s, webGLContextAttributes->failIfMajorPerformanceCaveat); 515 parametersPayload->addItem(WTFMove(webGLContextAttributesPayload)); 505 516 } 506 517 } 507 518 #endif 508 519 509 initialState->setAttributes(WTFMove(attributes)); 510 511 if (parameters->length()) 512 initialState->setParameters(WTFMove(parameters)); 513 514 initialState->setContent(getCanvasContentAsDataURL()); 515 516 return initialState; 520 initialStatePayload->setAttributes(WTFMove(attributesPayload)); 521 522 if (statesPayload->length()) 523 initialStatePayload->setStates(WTFMove(statesPayload)); 524 525 if (parametersPayload->length()) 526 initialStatePayload->setParameters(WTFMove(parametersPayload)); 527 528 initialStatePayload->setContent(getCanvasContentAsDataURL()); 529 530 return initialStatePayload; 517 531 } 518 532 -
trunk/Source/WebCore/inspector/InspectorCanvas.h
r237010 r237198 97 97 98 98 int indexForData(DuplicateDataVariant); 99 String stringIndexForKey(const String&); 99 100 Ref<Inspector::Protocol::Recording::InitialState> buildInitialState(); 100 101 Ref<JSON::ArrayOf<JSON::Value>> buildAction(const String&, Vector<RecordCanvasActionVariant>&& = { }); -
trunk/Source/WebInspectorUI/ChangeLog
r237196 r237198 1 2018-10-16 Devin Rousso <drousso@apple.com> 2 3 Web Inspector: Canvas: capture previously saved states and add them to the recording payload 4 https://bugs.webkit.org/show_bug.cgi?id=190473 5 6 Reviewed by Joseph Pecoraro. 7 8 Instead of sending a single object of the current state of the context, send an array of 9 objects, one for each restore point. When replaying, recreate each restore point before 10 applying the selected action(s). 11 12 * UserInterface/Models/Recording.js: 13 (WI.Recording): 14 (WI.Recording.fromPayload): 15 (WI.Recording.prototype.toJSON): 16 (WI.Recording.prototype.async._process): 17 (WI.Recording.prototype.async._swizzleState): Added. 18 * UserInterface/Models/RecordingAction.js: 19 (WI.RecordingAction): 20 (WI.RecordingAction.deriveCurrentState): Added. 21 (WI.RecordingAction.prototype.get states): Added. 22 (WI.RecordingAction.prototype.process): 23 (WI.RecordingAction.prototype.get state): Deleted. 24 Drive-by: when `process`ing, also check to see if any values in the current state changed 25 outside of those expected in `_stateModifiers` (e.g. `restore` may modify some state values). 26 27 * UserInterface/Views/RecordingContentView.js: 28 (WI.RecordingContentView.prototype._generateContentCanvas2D): 29 30 * UserInterface/Views/RecordingStateDetailsSidebarPanel.js: 31 (WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D): 32 Default to showing the most recent (current) state. 33 34 * UserInterface/Views/CanvasTabContentView.js: 35 (WI.CanvasTabContentView.prototype.initialLayout): Added. 36 (WI.CanvasTabContentView.prototype._addCanvas): 37 (WI.CanvasTabContentView.prototype._removeCanvas): 38 (WI.CanvasTabContentView.prototype._addRecording): Added. 39 (WI.CanvasTabContentView.prototype._recordingImportedOrStopped): 40 (WI.CanvasTabContentView.prototype._recordingAdded): Deleted. 41 * UserInterface/Controllers/CanvasManager.js: 42 (WI.CanvasManager): 43 (WI.CanvasManager.prototype.get importedRecordings): Added. 44 (WI.CanvasManager.prototype.importRecording): 45 Drive-by: store imported recordings on `WI.CanvasManager` so that if the Canvas tab is 46 closed we can still show the list of imported recordings. 47 1 48 2018-10-16 Devin Rousso <drousso@apple.com> 2 49 -
trunk/Source/WebInspectorUI/UserInterface/Controllers/CanvasManager.js
r237090 r237198 34 34 this._canvasIdentifierMap = new Map; 35 35 this._shaderProgramIdentifierMap = new Map; 36 this._importedRecordings = new Set; 36 37 37 38 if (window.CanvasAgent) … … 40 41 41 42 // Public 43 44 get importedRecordings() { return this._importedRecordings; } 42 45 43 46 get canvases() … … 76 79 recording.createDisplayName(filename); 77 80 81 this._importedRecordings.add(recording); 82 78 83 this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingImported, {recording}); 79 84 }); -
trunk/Source/WebInspectorUI/UserInterface/Models/Recording.js
r237010 r237198 43 43 44 44 this._processContext = null; 45 this._processStates = []; 45 46 this._processing = false; 46 47 } … … 74 75 if (typeof payload.initialState.attributes !== "object" || payload.initialState.attributes === null) 75 76 payload.initialState.attributes = {}; 77 if (!Array.isArray(payload.initialState.states) || payload.initialState.states.some((item) => typeof item !== "object" || item === null)) { 78 payload.initialState.states = []; 79 80 // COMPATIBILITY (iOS 12.0): Recording.InitialState.states did not exist yet 81 if (!isEmptyObject(payload.initialState.attributes)) { 82 let {width, height, ...state} = payload.initialState.attributes; 83 if (!isEmptyObject(state)) 84 payload.initialState.states.push(state); 85 } 86 } 76 87 if (!Array.isArray(payload.initialState.parameters)) 77 88 payload.initialState.parameters = []; … … 338 349 if (!isEmptyObject(this._initialState.attributes)) 339 350 initialState.attributes = this._initialState.attributes; 351 if (this._initialState.states.length) 352 initialState.states = this._initialState.states; 340 353 if (this._initialState.parameters.length) 341 354 initialState.parameters = this._initialState.parameters; … … 363 376 this._processContext.drawImage(initialContent, 0, 0); 364 377 365 for (let [key, value] of Object.entries(this._initialState.attributes)) { 366 switch (key) { 367 case "setTransform": 368 value = [await this.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; 369 break; 370 371 case "fillStyle": 372 case "strokeStyle": 373 let [gradient, pattern, string] = await Promise.all([ 374 this.swizzle(value, WI.Recording.Swizzle.CanvasGradient), 375 this.swizzle(value, WI.Recording.Swizzle.CanvasPattern), 376 this.swizzle(value, WI.Recording.Swizzle.String), 377 ]); 378 if (gradient && !pattern) 379 value = gradient; 380 else if (pattern && !gradient) 381 value = pattern; 378 for (let state of this._initialState.states) { 379 let swizzledState = await this._swizzleState(state); 380 for (let [key, value] of Object.entries(swizzledState)) { 381 try { 382 if (WI.RecordingAction.isFunctionForType(this._type, key)) 383 this._processContext[key](...value); 382 384 else 383 value = string; 384 break; 385 386 case "direction": 387 case "font": 388 case "globalCompositeOperation": 389 case "imageSmoothingEnabled": 390 case "imageSmoothingQuality": 391 case "lineCap": 392 case "lineJoin": 393 case "shadowColor": 394 case "textAlign": 395 case "textBaseline": 396 value = await this.swizzle(value, WI.Recording.Swizzle.String); 397 break; 398 399 case "setPath": 400 value = [await this.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; 401 break; 385 this._processContext[key] = value; 386 } catch { } 402 387 } 403 388 404 if (value === undefined || (Array.isArray(value) && value.includes(undefined))) 405 continue; 406 407 try { 408 if (WI.RecordingAction.isFunctionForType(this._type, key)) 409 this._processContext[key](...value); 410 else 411 this._processContext[key] = value; 412 } catch { } 389 // The last state represents the current state, which should not be saved. 390 if (state !== this._initialState.states.lastValue) { 391 this._processContext.save(); 392 this._processStates.push(WI.RecordingAction.deriveCurrentState(this._type, this._processContext)); 393 } 413 394 } 414 395 } … … 418 399 // Since it is not associated with a WI.RecordingFrame, it has to manually process(). 419 400 if (!this._actions[0].ready) { 420 this._actions[0].process(this, this._processContext );401 this._actions[0].process(this, this._processContext, this._processStates); 421 402 this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action: this._actions[0], index: 0}); 422 403 } … … 426 407 427 408 let cumulativeActionIndex = 0; 409 let lastAction = this._actions[cumulativeActionIndex]; 428 410 for (let frameIndex = 0; frameIndex < this._frames.length; ++frameIndex) { 429 411 let frame = this._frames[frameIndex]; … … 431 413 if (frame.actions.lastValue.ready) { 432 414 cumulativeActionIndex += frame.actions.length; 415 lastAction = frame.actions.lastValue; 433 416 continue; 434 417 } … … 438 421 439 422 let action = frame.actions[actionIndex]; 440 if (action.ready) 423 if (action.ready) { 424 lastAction = action; 441 425 continue; 426 } 442 427 443 428 await action.swizzle(this); 444 429 445 action.process(this, this._processContext );430 action.process(this, this._processContext, this._processStates, {lastAction}); 446 431 447 432 if (action.isVisual) … … 458 443 startTime = Date.now(); 459 444 } 445 446 lastAction = action; 460 447 461 448 if (!this._processing) … … 469 456 this._processContext = null; 470 457 this._processing = false; 458 } 459 460 async _swizzleState(state) 461 { 462 let swizzledState = {}; 463 464 for (let [key, value] of Object.entries(state)) { 465 // COMPATIBILITY (iOS 12.0): Recording.InitialState.states did not exist yet 466 let keyIndex = parseInt(key); 467 if (!isNaN(keyIndex)) 468 key = await this.swizzle(keyIndex, WI.Recording.Swizzle.String); 469 470 switch (key) { 471 case "setTransform": 472 value = [await this.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; 473 break; 474 475 case "fillStyle": 476 case "strokeStyle": 477 let [gradient, pattern, string] = await Promise.all([ 478 this.swizzle(value, WI.Recording.Swizzle.CanvasGradient), 479 this.swizzle(value, WI.Recording.Swizzle.CanvasPattern), 480 this.swizzle(value, WI.Recording.Swizzle.String), 481 ]); 482 if (gradient && !pattern) 483 value = gradient; 484 else if (pattern && !gradient) 485 value = pattern; 486 else 487 value = string; 488 break; 489 490 case "direction": 491 case "font": 492 case "globalCompositeOperation": 493 case "imageSmoothingQuality": 494 case "lineCap": 495 case "lineJoin": 496 case "shadowColor": 497 case "textAlign": 498 case "textBaseline": 499 value = await this.swizzle(value, WI.Recording.Swizzle.String); 500 break; 501 502 case "globalAlpha": 503 case "lineWidth": 504 case "miterLimit": 505 case "shadowOffsetX": 506 case "shadowOffsetY": 507 case "shadowBlur": 508 case "lineDashOffset": 509 value = await this.swizzle(value, WI.Recording.Swizzle.Number); 510 break; 511 512 case "setPath": 513 value = [await this.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; 514 break; 515 } 516 517 if (value === undefined || (Array.isArray(value) && value.includes(undefined))) 518 continue; 519 520 swizzledState[key] = value; 521 } 522 523 return swizzledState; 471 524 } 472 525 }; -
trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js
r236715 r237198 47 47 this._hasVisibleEffect = undefined; 48 48 49 this._state = null;49 this._states = []; 50 50 this._stateModifiers = new Set; 51 51 … … 131 131 } 132 132 133 static _prototypeForType(type) 134 { 135 if (type === WI.Recording.Type.Canvas2D) 136 return CanvasRenderingContext2D.prototype; 137 if (type === WI.Recording.Type.CanvasBitmapRenderer) 138 return ImageBitmapRenderingContext.prototype; 139 if (type === WI.Recording.Type.CanvasWebGL) 140 return WebGLRenderingContext.prototype; 141 return null; 142 } 143 144 // Public 145 146 get name() { return this._name; } 147 get parameters() { return this._parameters; } 148 get swizzleTypes() { return this._payloadSwizzleTypes; } 149 get trace() { return this._trace; } 150 get snapshot() { return this._snapshot; } 151 get valid() { return this._valid; } 152 get isFunction() { return this._isFunction; } 153 get isGetter() { return this._isGetter; } 154 get isVisual() { return this._isVisual; } 155 get hasVisibleEffect() { return this._hasVisibleEffect; } 156 get state() { return this._state; } 157 get stateModifiers() { return this._stateModifiers; } 158 159 get ready() 160 { 161 return this._swizzled && this._processed; 162 } 163 164 process(recording, context) 165 { 166 console.assert(this._swizzled, "You must swizzle() before you can process()."); 167 console.assert(!this._processed, "You should only process() once."); 168 169 this._processed = true; 170 171 if (recording.type === WI.Recording.Type.CanvasWebGL) { 172 // We add each RecordingAction to the list of visualActionIndexes after it is processed. 173 if (this._valid && this._isVisual) { 174 let contentBefore = recording.visualActionIndexes.length ? recording.actions[recording.visualActionIndexes.lastValue].snapshot : recording.initialState.content; 175 this._hasVisibleEffect = this._snapshot !== contentBefore; 176 } 177 return; 178 } 179 180 function getContent() { 181 if (context instanceof CanvasRenderingContext2D) 182 return context.getImageData(0, 0, context.canvas.width, context.canvas.height).data; 183 184 if (context instanceof WebGLRenderingContext || context instanceof WebGL2RenderingContext) { 185 let pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4); 186 context.readPixels(0, 0, context.canvas.width, context.canvas.height, context.RGBA, context.UNSIGNED_BYTE, pixels); 187 return pixels; 188 } 189 190 if (context.canvas instanceof HTMLCanvasElement) 191 return [context.canvas.toDataURL()]; 192 193 console.assert("Unknown context type", context); 194 return []; 195 } 196 197 let contentBefore = null; 198 let shouldCheckHasVisualEffect = this._valid && this._isVisual; 199 if (shouldCheckHasVisualEffect) 200 contentBefore = getContent(); 201 202 this.apply(context); 203 204 if (shouldCheckHasVisualEffect) 205 this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); 206 207 if (recording.type === WI.Recording.Type.Canvas2D) { 133 static deriveCurrentState(type, context) 134 { 135 if (type === WI.Recording.Type.Canvas2D) { 208 136 let matrix = context.getTransform(); 209 137 210 this._state = {138 let state = { 211 139 currentX: context.currentX, 212 140 currentY: context.currentY, … … 238 166 239 167 if (WI.ImageUtilities.supportsCanvasPathDebugging()) 240 this._state.setPath = [context.getPath()]; 241 } 242 } 243 244 async swizzle(recording) 168 state.setPath = [context.getPath()]; 169 170 return state; 171 } 172 173 return null; 174 } 175 176 static _prototypeForType(type) 177 { 178 if (type === WI.Recording.Type.Canvas2D) 179 return CanvasRenderingContext2D.prototype; 180 if (type === WI.Recording.Type.CanvasBitmapRenderer) 181 return ImageBitmapRenderingContext.prototype; 182 if (type === WI.Recording.Type.CanvasWebGL) 183 return WebGLRenderingContext.prototype; 184 return null; 185 } 186 187 // Public 188 189 get name() { return this._name; } 190 get parameters() { return this._parameters; } 191 get swizzleTypes() { return this._payloadSwizzleTypes; } 192 get trace() { return this._trace; } 193 get snapshot() { return this._snapshot; } 194 get valid() { return this._valid; } 195 get isFunction() { return this._isFunction; } 196 get isGetter() { return this._isGetter; } 197 get isVisual() { return this._isVisual; } 198 get hasVisibleEffect() { return this._hasVisibleEffect; } 199 get states() { return this._states; } 200 get stateModifiers() { return this._stateModifiers; } 201 202 get ready() 203 { 204 return this._swizzled && this._processed; 205 } 206 207 process(recording, context, states, {lastAction} = {}) 208 { 209 console.assert(this._swizzled, "You must swizzle() before you can process()."); 210 console.assert(!this._processed, "You should only process() once."); 211 212 this._processed = true; 213 214 if (recording.type === WI.Recording.Type.CanvasWebGL) { 215 // We add each RecordingAction to the list of visualActionIndexes after it is processed. 216 if (this._valid && this._isVisual) { 217 let contentBefore = recording.visualActionIndexes.length ? recording.actions[recording.visualActionIndexes.lastValue].snapshot : recording.initialState.content; 218 this._hasVisibleEffect = this._snapshot !== contentBefore; 219 } 220 return; 221 } 222 223 function getContent() { 224 if (context instanceof CanvasRenderingContext2D) 225 return context.getImageData(0, 0, context.canvas.width, context.canvas.height).data; 226 227 if (context instanceof WebGLRenderingContext || context instanceof WebGL2RenderingContext) { 228 let pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4); 229 context.readPixels(0, 0, context.canvas.width, context.canvas.height, context.RGBA, context.UNSIGNED_BYTE, pixels); 230 return pixels; 231 } 232 233 if (context.canvas instanceof HTMLCanvasElement) 234 return [context.canvas.toDataURL()]; 235 236 console.assert("Unknown context type", context); 237 return []; 238 } 239 240 let contentBefore = null; 241 let shouldCheckHasVisualEffect = this._valid && this._isVisual; 242 if (shouldCheckHasVisualEffect) 243 contentBefore = getContent(); 244 245 this.apply(context); 246 247 if (shouldCheckHasVisualEffect) 248 this._hasVisibleEffect = !Array.shallowEqual(contentBefore, getContent()); 249 250 if (recording.type === WI.Recording.Type.Canvas2D) { 251 let currentState = WI.RecordingAction.deriveCurrentState(recording.type, context); 252 console.assert(currentState); 253 254 if (this.name === "save") 255 states.push(currentState); 256 else if (this.name === "restore") 257 states.pop(); 258 259 this._states = states.slice(); 260 this._states.push(currentState); 261 262 if (lastAction) { 263 let lastState = lastAction.states.lastValue; 264 for (let key in currentState) { 265 if (!(key in lastState) || (currentState[key] !== lastState[key] && !Object.shallowEqual(currentState[key], lastState[key]))) 266 this._stateModifiers.add(key); 267 } 268 } 269 } 270 } 271 272 async swizzle(recording, lastAction) 245 273 { 246 274 console.assert(!this._swizzled, "You should only swizzle() once."); -
trunk/Source/WebInspectorUI/UserInterface/Views/CanvasTabContentView.js
r237090 r237198 119 119 // Protected 120 120 121 initialLayout() 122 { 123 super.initialLayout(); 124 125 const options = { 126 suppressShowRecording: true, 127 }; 128 129 for (let recording of WI.canvasManager.importedRecordings) 130 this._addRecording(recording, options); 131 } 132 121 133 attached() 122 134 { … … 157 169 158 170 for (let recording of canvas.recordingCollection) 159 this._ recordingAdded(recording, {suppressShowRecording: true});171 this._addRecording(recording, {suppressShowRecording: true}); 160 172 } 161 173 … … 172 184 173 185 for (let recording of canvas.recordingCollection) 174 this._ recordingAdded(recording, options);186 this._addRecording(recording, options); 175 187 176 188 let currentContentView = this.contentBrowser.currentContentView; … … 183 195 } 184 196 185 _handleCanvasAdded(event) 186 { 187 this._addCanvas(event.data.canvas); 188 } 189 190 _handleCanvasRemoved(event) 191 { 192 this._removeCanvas(event.data.canvas); 193 } 194 195 _canvasTreeOutlineSelectionDidChange(event) 196 { 197 let selectedElement = event.data.selectedElement; 198 if (!selectedElement) 199 return; 200 201 let representedObject = selectedElement.representedObject; 202 if (!this.canShowRepresentedObject(representedObject)) { 203 console.assert(false, "Unexpected representedObject.", representedObject); 204 return; 205 } 206 207 this.showRepresentedObject(representedObject); 208 } 209 210 _recordingImportedOrStopped(event) 211 { 212 let recording = event.data.recording; 213 if (!recording) 214 return; 215 216 this._recordingAdded(recording, { 217 suppressShowRecording: event.data.fromConsole || this.contentBrowser.currentRepresentedObjects.some((representedObject) => representedObject instanceof WI.Recording), 218 }); 219 } 220 221 _recordingAdded(recording, options = {}) 197 _addRecording(recording, options = {}) 222 198 { 223 199 if (!recording.source) { … … 232 208 } 233 209 210 _handleCanvasAdded(event) 211 { 212 this._addCanvas(event.data.canvas); 213 } 214 215 _handleCanvasRemoved(event) 216 { 217 this._removeCanvas(event.data.canvas); 218 } 219 220 _canvasTreeOutlineSelectionDidChange(event) 221 { 222 let selectedElement = event.data.selectedElement; 223 if (!selectedElement) 224 return; 225 226 let representedObject = selectedElement.representedObject; 227 if (!this.canShowRepresentedObject(representedObject)) { 228 console.assert(false, "Unexpected representedObject.", representedObject); 229 return; 230 } 231 232 this.showRepresentedObject(representedObject); 233 } 234 235 _recordingImportedOrStopped(event) 236 { 237 let recording = event.data.recording; 238 if (!recording) 239 return; 240 241 this._addRecording(recording, { 242 suppressShowRecording: event.data.fromConsole || this.contentBrowser.currentRepresentedObjects.some((representedObject) => representedObject instanceof WI.Recording), 243 }); 244 } 245 234 246 _handleSpace(event) 235 247 { -
trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js
r236539 r237198 247 247 } 248 248 249 for (let name in snapshot.state) { 250 if (!(name in snapshot.context)) 251 continue; 252 253 // Skip internal state used for path debugging. 254 if (name === "currentX" || name === "currentY") 255 continue; 256 257 try { 258 if (WI.RecordingAction.isFunctionForType(this.representedObject.type, name)) 259 snapshot.context[name](...snapshot.state[name]); 260 else 261 snapshot.context[name] = snapshot.state[name]; 262 } catch { 263 delete snapshot.state[name]; 249 for (let state of snapshot.states) { 250 for (let name in state) { 251 if (!(name in snapshot.context)) 252 continue; 253 254 // Skip internal state used for path debugging. 255 if (name === "currentX" || name === "currentY") 256 continue; 257 258 try { 259 if (WI.RecordingAction.isFunctionForType(this.representedObject.type, name)) 260 snapshot.context[name](...state[name]); 261 else 262 snapshot.context[name] = state[name]; 263 } catch { 264 delete state[name]; 265 } 264 266 } 267 268 ++saveCount; 269 snapshot.context.save(); 265 270 } 266 271 … … 292 297 if (!saveCount) // Only attempt to restore if save has been called. 293 298 continue; 294 --saveCount;295 299 } 296 300 … … 354 358 if (lastSnapshotIndex < 0) { 355 359 snapshot.content = this._initialContent; 356 snapshot.state = actions[0].state;360 snapshot.states = actions[0].states; 357 361 } else { 358 362 snapshot.content = this._snapshots[lastSnapshotIndex].content; 359 snapshot.state = this._snapshots[lastSnapshotIndex].state;363 snapshot.states = this._snapshots[lastSnapshotIndex].states; 360 364 startIndex = this._snapshots[lastSnapshotIndex].index; 361 365 } … … 363 367 applyActions(startIndex, snapshot.index - 1); 364 368 if (snapshot.index > 0) 365 snapshot.state = actions[snapshot.index - 1].state;369 snapshot.states = actions[snapshot.index - 1].states; 366 370 367 371 snapshot.content = new Image; -
trunk/Source/WebInspectorUI/UserInterface/Views/RecordingStateDetailsSidebarPanel.js
r237196 r237198 97 97 this._dataGrid.removeChildren(); 98 98 99 console.assert(action.state); 100 if (!action.state) 99 let currentState = action.states.lastValue; 100 console.assert(currentState); 101 if (!currentState) 101 102 return; 102 103 … … 114 115 } 115 116 116 for (let name in action.state) {117 for (let name in currentState) { 117 118 // Skip internal state used for path debugging. 118 119 if (name === "setPath") 119 120 continue; 120 121 121 let value = action.state[name];122 let value = currentState[name]; 122 123 if (typeof value === "object") { 123 124 let isGradient = value instanceof CanvasGradient;
Note: See TracChangeset
for help on using the changeset viewer.