blob: a412029af66ea224705bcf99f6e02e5da3727b78 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script src="resources/recording-utilities.js"></script>
<script>
let ctx = null;
// 2x2 red square
let image = document.createElement("img");
image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABNJREFUCB1j/M/AAEQMDEwgAgQAHxcCAmtAm/sAAAAASUVORK5CYII=";
// Invalid video
let video = document.createElement("video");
// Blank canvas
let canvas = document.createElement("canvas");
canvas.width = 2;
canvas.height = 2;
let linearGradient = null;
let radialGradient = null;
let pattern = null;
let path12 = new Path2D("M 1 2");
let path34 = new Path2D("M 3 4");
let imageData14 = new ImageData(1, 4);
let imageData23 = new ImageData(2, 3);
let bitmap = null;
async function load() {
ctx = canvas.getContext("2d");
linearGradient = ctx.createLinearGradient(1, 2, 3, 4);
radialGradient = ctx.createRadialGradient(1, 2, 3, 4, 5, 6);
pattern = ctx.createPattern(image, "no-repeat");
bitmap = await createImageBitmap(image);
ctx.save();
cancelActions();
runTest();
}
function ignoreException(func){
try {
func();
} catch (e) { }
}
let requestAnimationFrameId = NaN;
let saveCount = 1;
function cancelActions() {
for (let i = 0; i < saveCount; ++i)
ctx.restore();
ctx.restore(); // Ensures the state is reset between test cases.
cancelAnimationFrame(requestAnimationFrameId);
requestAnimationFrameId = NaN;
ctx.save(); // Ensures the state is reset between test cases.
ctx.save(); // This matches the `restore` call in `performActions`.
saveCount = 1;
ctx.resetTransform();
ctx.beginPath();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
function performActions() {
let frames = [
() => {
ignoreException(() => ctx.arc(1, 2, 3, 4, 5));
ignoreException(() => ctx.arc(6, 7, 8, 9, 10, true));
},
() => {
ignoreException(() => ctx.arcTo(1, 2, 3, 4, 5));
},
() => {
ctx.beginPath();
},
() => {
ctx.bezierCurveTo(1, 2, 3, 4, 5, 6);
},
() => {
ctx.clearRect(1, 2, 3, 4);
},
() => {
ctx.clearShadow();
},
() => {
ctx.clip();
ctx.clip("evenodd");
ctx.clip(path12);
ctx.clip(path34, "evenodd");
},
() => {
ctx.closePath();
},
() => {
ignoreException(() => ctx.createImageData(imageData14));
ignoreException(() => ctx.createImageData(2, 3));
},
() => {
ignoreException(() => ctx.createLinearGradient(1, 2, 3, 4));
},
() => {
ignoreException(() => ctx.createPattern(image, "testA"));
ignoreException(() => ctx.createPattern(video, "testB"));
ignoreException(() => ctx.createPattern(canvas, "testC"));
ignoreException(() => ctx.createPattern(bitmap, "testD"));
},
() => {
ignoreException(() => ctx.createRadialGradient(1, 2, 3, 4, 5, 6));
},
() => {
ctx.direction;
ctx.direction = "test";
},
() => {
ctx.drawFocusIfNeeded(document.body);
ctx.drawFocusIfNeeded(path12, document.body);
},
() => {
ignoreException(() => ctx.drawImage(image, 11, 12));
ignoreException(() => ctx.drawImage(image, 13, 14, 15, 16));
ignoreException(() => ctx.drawImage(image, 17, 18, 19, 110, 111, 112, 113, 114));
ignoreException(() => ctx.drawImage(video, 21, 22));
ignoreException(() => ctx.drawImage(video, 23, 24, 25, 26));
ignoreException(() => ctx.drawImage(video, 27, 28, 29, 210, 211, 212, 213, 214));
ignoreException(() => ctx.drawImage(canvas, 31, 32));
ignoreException(() => ctx.drawImage(canvas, 33, 34, 35, 36));
ignoreException(() => ctx.drawImage(canvas, 37, 38, 39, 310, 311, 312, 313, 314));
ignoreException(() => ctx.drawImage(bitmap, 41, 42));
ignoreException(() => ctx.drawImage(bitmap, 43, 44, 45, 46));
ignoreException(() => ctx.drawImage(bitmap, 47, 48, 49, 410, 411, 412, 413, 414));
},
() => {
ctx.drawImageFromRect(image, 1, 2, 3, 4, 5, 6, 7, 8)
ctx.drawImageFromRect(image, 9, 10, 11, 12, 13, 14, 15, 16, "test");
},
() => {
ignoreException(() => ctx.ellipse(1, 2, 3, 4, 5, 6, 7));
ignoreException(() => ctx.ellipse(8, 9, 10, 11, 12, 13, 14, true));
},
() => {
ctx.fill();
ctx.fill("evenodd");
ctx.fill(path12);
ctx.fill(path34, "evenodd");
},
() => {
ctx.fillRect(1, 2, 3, 4);
},
() => {
ctx.fillStyle;
ctx.fillStyle = "test";
ctx.fillStyle = linearGradient;
ctx.fillStyle = radialGradient;
ctx.fillStyle = pattern;
},
() => {
ctx.fillText("testA", 1, 2);
ctx.fillText("testB", 3, 4, 5);
},
() => {
ctx.font;
ctx.font = "test";
},
() => {
ignoreException(() => ctx.getImageData(1, 2, 3, 4));
},
() => {
ctx.getLineDash();
},
() => {
ctx.getTransform();
},
() => {
ctx.globalAlpha;
ctx.globalAlpha = 0;
},
() => {
ctx.globalCompositeOperation;
ctx.globalCompositeOperation = "test";
},
() => {
ctx.imageSmoothingEnabled;
ctx.imageSmoothingEnabled = true;
},
() => {
ctx.imageSmoothingQuality;
ctx.imageSmoothingQuality = "low";
},
() => {
ctx.isPointInPath(path12, 5, 6);
ctx.isPointInPath(path34, 7, 8, "evenodd");
ctx.isPointInPath(9, 10);
ctx.isPointInPath(11, 12, "evenodd");
},
() => {
ctx.isPointInStroke(path12, 3, 4);
ctx.isPointInStroke(5, 6);
},
() => {
ctx.lineCap;
ctx.lineCap = "test";
},
() => {
ctx.lineDashOffset;
ctx.lineDashOffset = 1;
},
() => {
ctx.lineJoin;
ctx.lineJoin = "test";
},
() => {
ctx.lineTo(1, 2);
},
() => {
ctx.lineWidth;
ctx.lineWidth = 1;
},
() => {
ctx.measureText("test");
},
() => {
ctx.miterLimit;
ctx.miterLimit = 1;
},
() => {
ctx.moveTo(1, 2);
},
() => {
ctx.putImageData(imageData14, 5, 6);
ctx.putImageData(imageData23, 7, 8, 9, 10, 11, 12);
},
() => {
ctx.quadraticCurveTo(1, 2, 3, 4);
},
() => {
ctx.rect(1, 2, 3, 4);
},
() => {
ctx.resetTransform();
},
() => {
ctx.restore();
--saveCount;
},
() => {
ctx.rotate(1);
},
() => {
ctx.save();
++saveCount;
},
() => {
ctx.scale(1, 2);
},
() => {
ctx.setAlpha();
ctx.setAlpha(1);
},
() => {
ctx.setCompositeOperation();
ctx.setCompositeOperation("test");
},
() => {
ctx.setFillColor("testA");
ctx.setFillColor("testB", 1);
ctx.setFillColor(2);
ctx.setFillColor(3, 4);
ctx.setFillColor(5, 6, 7, 8);
ctx.setFillColor(9, 10, 11, 12, 13);
},
() => {
ctx.setLineCap();
ctx.setLineCap("test");
},
() => {
ctx.setLineDash([1, 2]);
},
() => {
ctx.setLineJoin();
ctx.setLineJoin("test");
},
() => {
ctx.setLineWidth();
ctx.setLineWidth(1);
},
() => {
ctx.setMiterLimit();
ctx.setMiterLimit(1);
},
() => {
ctx.setShadow(1, 2, 3);
ctx.setShadow(4, 5, 6, "test", 7);
ctx.setShadow(8, 9, 10, 11);
ctx.setShadow(12, 13, 14, 15, 16);
ctx.setShadow(17, 18, 19, 20, 21, 22, 23);
ctx.setShadow(24, 25, 26, 27, 28, 29, 30, 31);
},
() => {
ctx.setStrokeColor("testA");
ctx.setStrokeColor("testB", 1);
ctx.setStrokeColor(2);
ctx.setStrokeColor(3, 4);
ctx.setStrokeColor(5, 6, 7, 8);
ctx.setStrokeColor(9, 10, 11, 12, 13);
},
() => {
ctx.setTransform(1, 2, 3, 4, 5, 6);
ignoreException(() => ctx.setTransform());
ignoreException(() => ctx.setTransform(new DOMMatrix([7, 8, 9, 10, 11, 12])));
},
() => {
ctx.shadowBlur;
ctx.shadowBlur = 1;
},
() => {
ctx.shadowColor;
ctx.shadowColor = "test";
},
() => {
ctx.shadowOffsetX;
ctx.shadowOffsetX = 1;
},
() => {
ctx.shadowOffsetY;
ctx.shadowOffsetY = 1;
},
() => {
ctx.stroke();
ctx.stroke(path12);
},
() => {
ctx.strokeRect(1, 2, 3, 4);
},
() => {
ctx.strokeStyle;
ctx.strokeStyle = "test";
ctx.strokeStyle = linearGradient;
ctx.strokeStyle = radialGradient;
ctx.strokeStyle = pattern;
},
() => {
ctx.strokeText("testA", 1, 2);
ctx.strokeText("testB", 3, 4, 5);
},
() => {
ctx.textAlign;
ctx.textAlign = "test";
},
() => {
ctx.textBaseline;
ctx.textBaseline = "test";
},
() => {
ctx.transform(1, 2, 3, 4, 5, 6);
},
() => {
ctx.translate(1, 2);
},
() => {
ctx.webkitImageSmoothingEnabled;
ctx.webkitImageSmoothingEnabled = true;
},
() => {
ctx.webkitLineDash;
ctx.webkitLineDash = [1, 2];
},
() => {
ctx.webkitLineDashOffset;
ctx.webkitLineDashOffset = 1;
},
() => {
ctx.canvas.width;
ctx.canvas.width = 2;
},
() => {
ctx.canvas.height;
ctx.canvas.height = 2;
},
() => {
TestPage.dispatchEventToFrontend("LastFrame");
},
];
let index = 0;
function executeFrameFunction() {
frames[index++]();
if (index < frames.length)
requestAnimationFrameId = requestAnimationFrame(executeFrameFunction);
};
executeFrameFunction();
}
function performConsoleActions() {
console.record(ctx, {name: "TEST"});
ctx.fill();
console.recordEnd(ctx);
ctx.stroke();
}
function performSavePreActions() {
cancelActions();
ctx.restore();
ctx.restore();
function saveFillStyle(value) {
ctx.save();
ctx.fillStyle = value;
}
saveFillStyle("#ff0000");
saveFillStyle("#00ff00");
saveFillStyle("#0000ff");
}
function performSavePostActions() {
ctx.fill();
}
function performNaNActions() {
ctx.globalAlpha = NaN;
}
function test() {
let suite = InspectorTest.createAsyncSuite("Canvas.recording2D");
suite.addTestCase({
name: "Canvas.recording2D.singleFrame",
description: "Check that the recording is stopped after a single frame.",
test(resolve, reject) {
startRecording(WI.Canvas.ContextType.Canvas2D, resolve, reject, {frameCount: 1});
},
timeout: -1,
});
suite.addTestCase({
name: "Canvas.recording2D.multipleFrames",
description: "Check that recording data is serialized correctly for multiple frames.",
test(resolve, reject) {
startRecording(WI.Canvas.ContextType.Canvas2D, resolve, reject);
},
timeout: -1,
});
suite.addTestCase({
name: "Canvas.recording2D.memoryLimit",
description: "Check that the recording is stopped when it reaches the memory limit.",
test(resolve, reject) {
startRecording(WI.Canvas.ContextType.Canvas2D, resolve, reject, {memoryLimit: 10});
},
timeout: -1,
});
suite.addTestCase({
name: "Canvas.recording2D.Console",
description: "Check that a recording can be triggered by console.record().",
test(resolve, reject) {
consoleRecord(WI.Canvas.ContextType.Canvas2D, resolve, reject);
},
});
suite.addTestCase({
name: "Canvas.recording2D.ActionParameterNaN",
description: "Check that NaN is converted into the proper value for serialization.",
test(resolve, reject) {
let canvas = getCanvas(WI.Canvas.ContextType.Canvas2D);
if (!canvas) {
reject("Missing 2D canvas.");
return;
}
canvas.awaitEvent(WI.Canvas.Event.RecordingStopped)
.then((event) => {
let recording = event.data.recording.toJSON();
let frames = recording.frames;
InspectorTest.expectEqual(frames.length, 1, "The recording should have 1 frame.");
let actions = frames[0].actions;
InspectorTest.expectEqual(actions.length, 1, "The first frame should have 1 action.");
InspectorTest.expectEqual(actions[0][1].length, 1, "The action should have 1 parameter.");
InspectorTest.expectEqual(actions[0][1][0], null, "The parameter should be null.");
})
.then(resolve, reject);
canvas.awaitEvent(WI.Canvas.Event.RecordingStarted)
.then((event) => {
InspectorTest.evaluateInPage(`performNaNActions()`);
});
const frameCount = 1;
CanvasAgent.startRecording(canvas.identifier, frameCount)
.catch(reject);
},
});
suite.addTestCase({
name: "Canvas.recording2D.ExistingSaves",
description: "Check that existing save calls are sent to the frontend.",
test(resolve, reject) {
let canvas = getCanvas(WI.Canvas.ContextType.Canvas2D);
if (!canvas) {
reject("Missing 2D canvas.");
return;
}
async function logStates(recording) {
async function compare(index, expected) {
let state = await WI.RecordingState.swizzleInitialState(recording, recording.initialState.states[index]);
InspectorTest.expectEqual(state.get("fillStyle"), expected, `State ${index} should match expected fillStyle value.`)
}
await compare(0, "#000000");
await compare(1, "#ff0000");
await compare(2, "#00ff00");
await compare(3, "#0000ff");
}
canvas.awaitEvent(WI.Canvas.Event.RecordingStopped)
.then((event) => {
let {recording} = event.data;
InspectorTest.expectEqual(recording.initialState.states.length, 4, "There should be 4 existing states.");
logStates(recording)
.then(resolve, reject);
});
canvas.awaitEvent(WI.Canvas.Event.RecordingStarted)
.then((event) => {
InspectorTest.evaluateInPage(`performSavePostActions()`).catch(reject);
});
InspectorTest.evaluateInPage(`performSavePreActions()`)
.then(() => {
const frameCount = 1;
CanvasAgent.startRecording(canvas.identifier, frameCount).catch(reject);
}, reject);
},
});
suite.addTestCase({
name: "Canvas.recording2D.NoActions",
description: "Check that a canvas is still able to be recorded after stopping a recording with no actions.",
test(resolve, reject) {
let canvas = getCanvas(WI.Canvas.ContextType.Canvas2D);
if (!canvas) {
reject("Missing 2D canvas.");
return;
}
let eventCount = 0;
function handleRecordingStopped(event) {
InspectorTest.assert(!event.data.recording, "The recording payload should be null.");
++eventCount;
if (eventCount == 1) {
InspectorTest.pass("A recording should have been started and stopped once.");
canvas.startRecording();
canvas.stopRecording();
} else if (eventCount >= 2) {
InspectorTest.pass("A recording should have been started and stopped twice.");
canvas.removeEventListener(WI.Canvas.Event.RecordingStopped, handleRecordingStopped);
resolve();
}
}
canvas.addEventListener(WI.Canvas.Event.RecordingStopped, handleRecordingStopped);
canvas.startRecording();
canvas.stopRecording();
},
});
suite.runTestCasesAndFinish();
}
</script>
</head>
<body onload="load()">
<p>Test that CanvasManager is able to record actions made to 2D canvas contexts.</p>
</body>
</html>