-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cherry-pick ade36a3. rdar://problem/108845985
HTMLCanvasElement is orphaned causing a HTMLDocument leak on YouTube video pages https://bugs.webkit.org/show_bug.cgi?id=256262 rdar://108845985 Reviewed by Darin Adler. CanvasRenderingContext2DBase has a State stack for setting up drawing state. Two of the fields - strokeStyle and fillStyle - each hold CanvasStyle objects. These objects may be set with colors or images or gradients. In the case of a CanvasGradient, the gradient has a strong reference to the context which creates a reference cycle. CanvasRenderingContext2DBase -> State -> CanvasStyle -> CanvasGradient -> CanvasRenderingContext(2DBase) What makes this cycle dangerous is the CanvasRenderingContext will increase the ref count of an HTMLCanvasElement which, being a Node, will increment the referencingNodeCount of a Document object. So if a gradient is set on the context's fillStyle or strokeStyle we can cause a Document leak if the state is never cleared like on YouTube video pages. This patch changes the CanvasGradient object to hold a weak reference to the CanvasRenderingContext instead of a strong reference which breaks the cycle. * LayoutTests/fast/canvas Add tests to verify the now-decoupled lifetimes of gradient and context do not cause crashes. * LayoutTests/fast/canvas/canvas-gradient-can-outlive-context-expected.txt: Added. * LayoutTests/fast/canvas/canvas-gradient-can-outlive-context.html: Added. * LayoutTests/fast/canvas/canvas-state-stack-gradient-expected.txt: Added. * LayoutTests/fast/canvas/canvas-state-stack-gradient.html: Added. * LayoutTests/http/tests/canvas Add a test to verify that setting the style of a 2D canvas context does not leak Documents. * LayoutTests/http/tests/canvas/ctx.2d-canvas-style-no-document-leak-expected.txt: Added. * LayoutTests/http/tests/canvas/ctx.2d-canvas-style-no-document-leak.html: Added. * LayoutTests/http/tests/canvas/resources/background.png: Added. * LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-color.html: Added. * LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-gradient.html: Added. * LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-pattern.html: Added. * Source/WebCore/html/canvas/CanvasGradient.cpp: (WebCore::CanvasGradient::addColorStop): * Source/WebCore/html/canvas/CanvasGradient.h: * Source/WebCore/html/canvas/CanvasStyle.cpp: (WebCore::parseColor): Since the gradient can outlive the context that created it, we need to be able to parse colors without having a CSSParserContext reference. This overload calls CSSParser::parseColorWithoutContext in that case. * Source/WebCore/html/canvas/CanvasStyle.h: (WebCore::CanvasStyle::canvasGradient const): Canonical link: https://commits.webkit.org/263774@main Identifier: 259548.807@safari-7615-branch
- Loading branch information
Showing
14 changed files
with
285 additions
and
2 deletions.
There are no files selected for viewing
9 changes: 9 additions & 0 deletions
9
LayoutTests/fast/canvas/canvas-gradient-can-outlive-context-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Test that a gradient can outlive a context. Test passes if it doesn't crash. | ||
|
||
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". | ||
|
||
|
||
PASS successfullyParsed is true | ||
|
||
TEST COMPLETE | ||
|
52 changes: 52 additions & 0 deletions
52
LayoutTests/fast/canvas/canvas-gradient-can-outlive-context.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<!DOCTYPE html> | ||
<body> | ||
<script src='../../resources/js-test.js'></script> | ||
<script> | ||
description("Test that a gradient can outlive a context. Test passes if it doesn't crash."); | ||
jsTestIsAsync = true; | ||
var gradient; | ||
window.onload = function() { | ||
let context = document.createElement('canvas').getContext('2d'); | ||
gradient = context.createLinearGradient(0, 0, 100, 100); | ||
gradient.addColorStop(0, 'red'); | ||
gradient.addColorStop(0.25, 'white'); | ||
gradient.addColorStop(0.67, 'white'); | ||
gradient.addColorStop(1, 'aquamarine'); | ||
context.scale(2,2); | ||
context.beginPath(); | ||
context.rect(0,0,75,75); | ||
context.clip(); | ||
context.fillStyle = gradient; | ||
context.fillRect(0,0,30,30); | ||
context.fillRect(50,50,30,30); | ||
context.save(); | ||
context.scale(0.5,0.5); | ||
context.beginPath(); | ||
context.rect(0,0,5,5); | ||
context.clip(); | ||
requestAnimationFrame(() => forceGCThenModifyGradient()); | ||
}; | ||
|
||
function forceGCThenModifyGradient() { | ||
count = 0; | ||
handle = setInterval(() => { | ||
gc(); | ||
count++; | ||
if (count > 500) { | ||
clearInterval(handle); | ||
modifyGradient(); | ||
} | ||
}, 10); | ||
} | ||
|
||
// Create a new context but use the old gradient. | ||
function modifyGradient() { | ||
let context = document.createElement('canvas').getContext('2d'); | ||
gradient.addColorStop(0.75, 'aquamarine'); | ||
gradient.addColorStop(0.8, 'white'); | ||
gradient.addColorStop(0.9, 'orange'); | ||
if (window.testRunner) | ||
finishJSTest("PASS"); | ||
} | ||
</script> | ||
</body> |
9 changes: 9 additions & 0 deletions
9
LayoutTests/fast/canvas/canvas-state-stack-gradient-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Test a gradient stored on the state stack does not get garbage collected. Test passes if it does not crash. | ||
|
||
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". | ||
|
||
|
||
PASS successfullyParsed is true | ||
|
||
TEST COMPLETE | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<!DOCTYPE html> | ||
<body> | ||
<script src='../../resources/js-test.js'></script> | ||
<script> | ||
description("Test a gradient stored on the state stack does not get garbage collected. Test passes if it does not crash."); | ||
jsTestIsAsync = true; | ||
window.onload = function() { | ||
let context = document.getElementById('c').getContext('2d'); | ||
if (true) { | ||
let gradient = context.createLinearGradient(0, 0, 75, 75); | ||
gradient.addColorStop(0, 'red'); | ||
gradient.addColorStop(0.33, 'white'); | ||
gradient.addColorStop(0.67, 'white'); | ||
gradient.addColorStop(1, 'blue'); | ||
context.scale(2,2); | ||
context.beginPath(); | ||
context.rect(0,0,75,75); | ||
context.clip(); | ||
context.fillStyle = gradient; | ||
context.fillRect(0,0,30,30); | ||
context.fillRect(50,50,30,30); | ||
context.save(); | ||
} | ||
context.fillStyle = 'green'; | ||
context.scale(0.5,0.5); | ||
context.beginPath(); | ||
context.rect(0,0,5,5); | ||
context.clip(); | ||
requestAnimationFrame(() => forceGC(context)); | ||
}; | ||
|
||
function forceGC(ctx) { | ||
count = 0; | ||
handle = setInterval(() => { | ||
gc(); | ||
count++; | ||
if (count > 500) { | ||
clearInterval(handle); | ||
drawSecondFrame(ctx); | ||
} | ||
}, 10); | ||
} | ||
|
||
function drawSecondFrame(ctx) { | ||
ctx.restore(); | ||
let gradient = ctx.fillStyle; | ||
// colors are type string, gradients and patterns are objects | ||
if (typeof gradient !== typeof {}) | ||
testFailed("Should have restored a gradient from the state stack."); | ||
|
||
ctx.fillRect(0,0,30,30); | ||
ctx.fillRect(75,75,30,30); | ||
if (window.testRunner) { | ||
finishJSTest("PASS"); | ||
} | ||
} | ||
</script> | ||
<canvas id="c" width="300" height="300"></canvas> | ||
</body> |
13 changes: 13 additions & 0 deletions
13
LayoutTests/http/tests/canvas/ctx.2d-canvas-style-no-document-leak-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Tests that canvas context fillStyle does not leak documents. | ||
|
||
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". | ||
|
||
|
||
PASS internals.isDocumentAlive(frameDocumentID) is true | ||
PASS internals.isDocumentAlive(frameDocumentID) is true | ||
PASS internals.isDocumentAlive(frameDocumentID) is true | ||
PASS The iframe document didn't leak. | ||
PASS successfullyParsed is true | ||
|
||
TEST COMPLETE | ||
|
58 changes: 58 additions & 0 deletions
58
LayoutTests/http/tests/canvas/ctx.2d-canvas-style-no-document-leak.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<iframe id="iframe"></iframe> | ||
<script src="../resources/js-test-pre.js"></script> | ||
<script> | ||
description("Tests that canvas context fillStyle does not leak documents."); | ||
|
||
jsTestIsAsync = true; | ||
frameDocumentID = 0; | ||
checkCount = 0; | ||
|
||
function iframeLoaded(frameDocument) { | ||
frameDocumentID = internals.documentIdentifier(frameDocument); | ||
shouldBeTrue("internals.isDocumentAlive(frameDocumentID)"); | ||
|
||
iframe.addEventListener("load", () => { | ||
handle = setInterval(() => { | ||
gc(); | ||
if (!internals.isDocumentAlive(frameDocumentID)) { | ||
clearInterval(handle); | ||
testPassed("The iframe document didn't leak."); | ||
finishJSTest(); | ||
} | ||
checkCount++; | ||
if (checkCount > 500) { | ||
clearInterval(handle); | ||
testFailed("The iframe document leaked."); | ||
finishJSTest(); | ||
} | ||
}, 10); | ||
}, {once: true}); | ||
|
||
iframe.src = "about:blank"; | ||
} | ||
|
||
function test() { | ||
["color", "gradient", "pattern"].forEach(variant => { | ||
window.addEventListener("message", (message) => { | ||
if (message.data === "frameLoaded") | ||
iframeLoaded(iframe.contentWindow.document); | ||
}, {once: true}); | ||
|
||
iframe.src = `resources/ctx.2d-fillStyle-${variant}.html`; | ||
}); | ||
} | ||
|
||
onload = () => { | ||
if (!window.testRunner || !window.internals) | ||
testFailed("Test requires internals."); | ||
|
||
test(); | ||
}; | ||
|
||
</script> | ||
<script src="../resources/js-test-post.js"></script> | ||
</body> | ||
</html> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions
19
LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-color.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<script> | ||
onload = () => { | ||
var canvas = document.createElement('canvas'); | ||
canvas.setAttribute('width', '640'); | ||
canvas.setAttribute('height', '480'); | ||
var ctx = canvas.getContext('2d'); | ||
ctx.fillStyle = "aquamarine"; | ||
ctx.fillRect(50, 50, 100, 100); | ||
|
||
document.body.style.backgroundImage = `url(${canvas.toDataURL()})`; | ||
|
||
parent.postMessage("frameLoaded"); | ||
}; | ||
</script> | ||
</body> | ||
</html> |
25 changes: 25 additions & 0 deletions
25
LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-gradient.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<script> | ||
onload = () => { | ||
var canvas = document.createElement('canvas'); | ||
canvas.setAttribute('width', '640'); | ||
canvas.setAttribute('height', '480'); | ||
var ctx = canvas.getContext('2d'); | ||
var gradient = ctx.createLinearGradient(0, 0, 400, 400); | ||
|
||
gradient.addColorStop(0, 'red'); | ||
gradient.addColorStop(0.5, 'green'); | ||
gradient.addColorStop(1, 'blue'); | ||
|
||
ctx.fillStyle = gradient; | ||
ctx.fillRect(50, 50, 100, 100); | ||
|
||
document.body.style.backgroundImage = `url(${canvas.toDataURL()})`; | ||
|
||
parent.postMessage("frameLoaded"); | ||
}; | ||
</script> | ||
</body> | ||
</html> |
23 changes: 23 additions & 0 deletions
23
LayoutTests/http/tests/canvas/resources/ctx.2d-fillStyle-pattern.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<script> | ||
onload = () => { | ||
var img = new Image(); | ||
img.onload = () => { | ||
var canvas = document.createElement('canvas'); | ||
canvas.setAttribute('width', '640'); | ||
canvas.setAttribute('height', '480'); | ||
var ctx = canvas.getContext('2d'); | ||
ctx.fillStyle = ctx.createPattern(img, "repeat"); | ||
ctx.fillRect(50, 50, 100, 100); | ||
|
||
document.body.style.backgroundImage = `url(${canvas.toDataURL()})`; | ||
|
||
parent.postMessage("frameLoaded"); | ||
}; | ||
img.src = "background.png"; | ||
}; | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters