mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Bug 1910297. Handle repaint propagation to the root if the root is a table. r=layout-reviewers,emilio
The background image is associate to the table frame, but the table wrapper frame is the primary frame, thus IsPrimaryFrameOfRootOrBodyElement returns false and we don't propagate to the root when we should. So I changed IsPrimaryFrameOfRootOrBodyElement to handle this case and renamed it. I checked the other root element frame types, only table frames had this issue. Differential Revision: https://phabricator.services.mozilla.com/D217923
This commit is contained in:
parent
8d68bd2063
commit
2398448e0b
144
image/test/animated_image_test_list.js
Normal file
144
image/test/animated_image_test_list.js
Normal file
@ -0,0 +1,144 @@
|
||||
// This is shared by image/test/mochitest/test_animated_css_image.html
|
||||
// and image/test/browser/browser_animated_css_image.js
|
||||
// Make sure any referenced files/images exist in both of those directories.
|
||||
|
||||
const kTests = [
|
||||
// Sanity test: background-image on a regular element.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1627585: content: url()
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div::before {
|
||||
content: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1627585: content: url() (on an element directly)
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
content: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1625571: background propagated to canvas.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
body {
|
||||
background-image: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.documentElement;
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1910297: background propagated to canvas with display: table
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
html {
|
||||
display: table;
|
||||
background-image: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.documentElement;
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1719375: CSS animation in SVG image.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url(animated1.svg);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1730834: stopped window.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<body onload="window.stop(); document.querySelector('div').style.backgroundImage = 'url(animated1.gif)';">
|
||||
<div></div>
|
||||
</body>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1731138: Animated mask
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
mask-clip: border-box;
|
||||
mask-size: 100% 100%;
|
||||
mask-image: url(animatedMask.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
];
|
BIN
image/test/browser/animated1.gif
Normal file
BIN
image/test/browser/animated1.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
12
image/test/browser/animated1.svg
Normal file
12
image/test/browser/animated1.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="animation: colorAnim 1s steps(2) infinite alternate"
|
||||
width="40" height="40">
|
||||
<style>
|
||||
@keyframes colorAnim {
|
||||
from { background-color: green }
|
||||
to { background-color: blue }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 319 B |
BIN
image/test/browser/animatedMask.gif
Normal file
BIN
image/test/browser/animatedMask.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
@ -28,3 +28,14 @@ skip-if = ["true"] # Bug 987616
|
||||
["browser_offscreen_image_in_out_of_process_iframe.js"]
|
||||
https_first_disabled = true
|
||||
support-files = ["empty.html"]
|
||||
|
||||
["browser_animated_css_image.js"]
|
||||
support-files = [
|
||||
"!/gfx/layers/apz/test/mochitest/apz_test_utils.js",
|
||||
"!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
|
||||
"animated1.gif",
|
||||
"animated1.svg",
|
||||
"animatedMask.gif",
|
||||
"helper_animated_css_image.html",
|
||||
"../animated_image_test_list.js",
|
||||
]
|
||||
|
188
image/test/browser/browser_animated_css_image.js
Normal file
188
image/test/browser/browser_animated_css_image.js
Normal file
@ -0,0 +1,188 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/*
|
||||
* This test duplicates image/test/mochitest/test_animated_css_image.html, so keep them in sync.
|
||||
* This is because we need a browser-chrome test in order to test invalidation (the getSnapshot method here
|
||||
* uses the same path as painting to the screen, whereas test_animated_css_image.html is doing a
|
||||
* separate paint to a surface), but browser-chrome isn't run on android, so test_animated_css_image.html
|
||||
* gets us android coverage.
|
||||
*/
|
||||
|
||||
/* This test is based on
|
||||
https://searchfox.org/mozilla-central/rev/25d26b0a62cc5bb4aa3bb90a11f3b0b7c52859c4/gfx/layers/apz/test/mochitest/browser_test_position_sticky.js
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js",
|
||||
this
|
||||
);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
|
||||
this
|
||||
);
|
||||
|
||||
// this contains the kTests array
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/image/test/browser/animated_image_test_list.js",
|
||||
this
|
||||
);
|
||||
|
||||
async function assertAnimates(thehtml) {
|
||||
function httpURL(sfilename) {
|
||||
let chromeURL = getRootDirectory(gTestPath) + sfilename;
|
||||
return chromeURL.replace(
|
||||
"chrome://mochitests/content/",
|
||||
"http://mochi.test:8888/"
|
||||
);
|
||||
}
|
||||
|
||||
const url = httpURL("helper_animated_css_image.html");
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
|
||||
|
||||
const { rect } = await SpecialPowers.spawn(
|
||||
tab.linkedBrowser,
|
||||
[],
|
||||
async () => {
|
||||
let rect = content.document.documentElement.getBoundingClientRect();
|
||||
rect.x += content.window.mozInnerScreenX;
|
||||
rect.y += content.window.mozInnerScreenY;
|
||||
|
||||
return {
|
||||
rect,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
let blankSnapshot = await getSnapshot({
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
|
||||
const kNumRetries = 600;
|
||||
|
||||
info("testing: " + thehtml);
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [thehtml], async thehtml => {
|
||||
const theiframe = content.document.getElementById("iframe");
|
||||
let load = new Promise(resolve => {
|
||||
theiframe.addEventListener("load", resolve, { once: true });
|
||||
});
|
||||
theiframe.srcdoc = thehtml;
|
||||
await load;
|
||||
|
||||
// give time for content/test load handlers to run before we do anything
|
||||
await new Promise(resolve => {
|
||||
content.window.requestAnimationFrame(() => {
|
||||
content.window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
// make sure we are flushed and rendered.
|
||||
content.document.documentElement.getBoundingClientRect();
|
||||
|
||||
await new Promise(resolve => {
|
||||
content.window.requestAnimationFrame(() => {
|
||||
content.window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let initial = await getSnapshot({
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
|
||||
{
|
||||
// One test (bug 1730834) loads an image as the background of a div in the
|
||||
// load handler, so there's no good way to wait for it to be loaded and
|
||||
// rendered except to poll.
|
||||
let equal = initial == blankSnapshot;
|
||||
for (let i = 0; i < kNumRetries; ++i) {
|
||||
if (!equal) {
|
||||
break;
|
||||
}
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
|
||||
await new Promise(resolve => {
|
||||
content.window.requestAnimationFrame(() => {
|
||||
content.window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
initial = await getSnapshot({
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
equal = initial == blankSnapshot;
|
||||
}
|
||||
ok(!equal, "Initial snapshot shouldn't be blank");
|
||||
}
|
||||
|
||||
async function checkFrames() {
|
||||
let foundDifferent = false;
|
||||
let foundInitialAgain = false;
|
||||
for (let i = 0; i < kNumRetries; ++i) {
|
||||
let current = await getSnapshot({
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
|
||||
let equal = initial == current;
|
||||
if (!foundDifferent && !equal) {
|
||||
ok(true, `Found different image after ${i} retries`);
|
||||
foundDifferent = true;
|
||||
}
|
||||
|
||||
// Ensure that we go back to the initial state (animated1.gif) is an
|
||||
// infinite gif.
|
||||
if (foundDifferent && equal) {
|
||||
ok(true, `Found same image again after ${i} retries`);
|
||||
|
||||
foundInitialAgain = true;
|
||||
break;
|
||||
}
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
|
||||
await new Promise(resolve => {
|
||||
content.window.requestAnimationFrame(() => {
|
||||
content.window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ok(
|
||||
foundDifferent && foundInitialAgain,
|
||||
`Should've found a different snapshot and then an equal one, after ${kNumRetries} retries`
|
||||
);
|
||||
}
|
||||
|
||||
for (let j = 0; j < 5; j++) {
|
||||
await checkFrames();
|
||||
}
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
||||
add_task(async () => {
|
||||
// kTests is defined in the imported animated_image_test_list.js so it can
|
||||
// be shared between tests.
|
||||
// eslint-disable-next-line no-undef
|
||||
for (let { html } of kTests) {
|
||||
await assertAnimates(html);
|
||||
}
|
||||
});
|
6
image/test/browser/helper_animated_css_image.html
Normal file
6
image/test/browser/helper_animated_css_image.html
Normal file
@ -0,0 +1,6 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
scrolling=no is just paranoia to ensure that we don't get invalidations
|
||||
due to scrollbars
|
||||
-->
|
||||
<iframe scrolling="no" id="iframe"></iframe>
|
@ -118,6 +118,9 @@ skip-if = [
|
||||
]
|
||||
|
||||
["test_animated_css_image.html"]
|
||||
support-files = [
|
||||
"../animated_image_test_list.js"
|
||||
]
|
||||
|
||||
["test_animated_gif.html"]
|
||||
support-files = ["child.html"]
|
||||
|
@ -6,9 +6,20 @@
|
||||
due to scrollbars
|
||||
-->
|
||||
<iframe scrolling="no" id="iframe"></iframe>
|
||||
<!-- this contains the kTests array -->
|
||||
<script src="animated_image_test_list.js"></script>
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
/*
|
||||
* This test duplicates image/test/browser/browser_animated_css_image.js, so keep them in sync.
|
||||
* This is because we need a browser-chrome test in order to test invalidation (the getSnapshot method there
|
||||
* uses the same path as painting to the screen, whereas here we are doing a
|
||||
* separate paint to a surface), but browser-chrome isn't run on android, so test_animated_css_image.html
|
||||
* gets us android coverage.
|
||||
*/
|
||||
|
||||
|
||||
// We hit an optimized path in WebRender that doesn't cause a repaint on the
|
||||
// main thread:
|
||||
//
|
||||
@ -86,135 +97,13 @@ async function assertAnimates(html, getExpectedRepaintedElement) {
|
||||
ok(foundDifferent && foundInitialAgain, `Should've found a different snapshot and then an equal one, after ${kNumRetries} retries`);
|
||||
}
|
||||
|
||||
const kTests = [
|
||||
// Sanity test: background-image on a regular element.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1627585: content: url()
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div::before {
|
||||
content: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1627585: content: url() (on an element directly)
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
content: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1625571: background propagated to canvas.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
body {
|
||||
background-image: url(animated1.gif);
|
||||
}
|
||||
</style>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.documentElement;
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1719375: CSS animation in SVG image.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url(animated1.svg);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1730834: stopped window.
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<body onload="window.stop(); document.querySelector('div').style.backgroundImage = 'url(animated1.gif)';">
|
||||
<div></div>
|
||||
</body>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
|
||||
// bug 1731138: Animated mask
|
||||
{
|
||||
html: `
|
||||
<!doctype html>
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: lime;
|
||||
mask-clip: border-box;
|
||||
mask-size: 100% 100%;
|
||||
mask-image: url(animatedMask.gif);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`,
|
||||
element(doc) {
|
||||
return doc.querySelector("div");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onload = async function() {
|
||||
// First snapshot the blank window.
|
||||
blankSnapshot = await snapshotWindow(iframe.contentWindow);
|
||||
|
||||
// kTests is defined in the imported animated_image_test_list.js so it can
|
||||
// be shared between tests.
|
||||
// eslint-disable-next-line no-undef
|
||||
for (let { html, element } of kTests)
|
||||
await assertAnimates(html, element);
|
||||
|
||||
|
@ -1306,7 +1306,7 @@ static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
|
||||
// the html element, we propagate the repaint change hint to the
|
||||
// viewport. This is necessary for background and scrollbar colors
|
||||
// propagation.
|
||||
if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
|
||||
if (aFrame->ShouldPropagateRepaintsToRoot()) {
|
||||
nsIFrame* rootFrame = aPresShell->GetRootFrame();
|
||||
MOZ_ASSERT(rootFrame, "No root frame?");
|
||||
DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
|
||||
|
@ -2686,7 +2686,8 @@ void nsCSSFrameConstructor::SetUpDocElementContainingBlock(
|
||||
ScrollContainerFrame (if needed)
|
||||
nsCanvasFrame [abs-cb]
|
||||
root element frame (nsBlockFrame, SVGOuterSVGFrame,
|
||||
nsTableWrapperFrame, nsPlaceholderFrame)
|
||||
nsTableWrapperFrame, nsPlaceholderFrame,
|
||||
nsFlexContainerFrame, nsGridContainerFrame)
|
||||
|
||||
Print presentation, non-XUL
|
||||
|
||||
@ -2698,7 +2699,9 @@ void nsCSSFrameConstructor::SetUpDocElementContainingBlock(
|
||||
nsPageContentFrame [fixed-cb]
|
||||
nsCanvasFrame [abs-cb]
|
||||
root element frame (nsBlockFrame, SVGOuterSVGFrame,
|
||||
nsTableWrapperFrame, nsPlaceholderFrame)
|
||||
nsTableWrapperFrame, nsPlaceholderFrame,
|
||||
nsFlexContainerFrame,
|
||||
nsGridContainerFrame)
|
||||
|
||||
Print-preview presentation, non-XUL
|
||||
|
||||
@ -2712,7 +2715,9 @@ void nsCSSFrameConstructor::SetUpDocElementContainingBlock(
|
||||
nsCanvasFrame [abs-cb]
|
||||
root element frame (nsBlockFrame, SVGOuterSVGFrame,
|
||||
nsTableWrapperFrame,
|
||||
nsPlaceholderFrame)
|
||||
nsPlaceholderFrame,
|
||||
nsFlexContainerFrame,
|
||||
nsGridContainerFrame)
|
||||
|
||||
Print/print preview of XUL is not supported.
|
||||
[fixed-cb]: the default containing block for fixed-pos content
|
||||
|
@ -578,8 +578,15 @@ static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) {
|
||||
IntrinsicDirty::FrameAncestorsAndDescendants);
|
||||
}
|
||||
|
||||
bool nsIFrame::IsPrimaryFrameOfRootOrBodyElement() const {
|
||||
bool nsIFrame::ShouldPropagateRepaintsToRoot() const {
|
||||
if (!IsPrimaryFrame()) {
|
||||
// special case for table frames because style images are associated to the
|
||||
// table frame, but the table wrapper frame is the primary frame
|
||||
if (IsTableFrame()) {
|
||||
MOZ_ASSERT(GetParent() && GetParent()->IsTableWrapperFrame());
|
||||
return GetParent()->ShouldPropagateRepaintsToRoot();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
nsIContent* content = GetContent();
|
||||
|
@ -2468,7 +2468,7 @@ class nsIFrame : public nsQueryFrame {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPrimaryFrameOfRootOrBodyElement() const;
|
||||
bool ShouldPropagateRepaintsToRoot() const;
|
||||
|
||||
/**
|
||||
* @return true if this frame is used as a fieldset's rendered legend.
|
||||
|
@ -527,7 +527,7 @@ static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest,
|
||||
return aFrame->InvalidateFrame();
|
||||
}
|
||||
|
||||
if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
|
||||
if (aFrame->ShouldPropagateRepaintsToRoot()) {
|
||||
if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) {
|
||||
// Try to invalidate the canvas too, in the probable case the background
|
||||
// was propagated to it.
|
||||
|
Loading…
Reference in New Issue
Block a user