Bug 1688813 - Draw position:fixed elements relative to the top-left of the document when using drawSnapshot. r=emilio,mstange

Differential Revision: https://phabricator.services.mozilla.com/D104935
This commit is contained in:
Matt Woodrow 2021-02-16 07:38:51 +00:00
parent 5d9d0b4383
commit cd814ab6a8
8 changed files with 104 additions and 0 deletions

View File

@ -177,6 +177,7 @@ support-files = captureStream_common.js
support-files = file_drawWindow_source.html file_drawWindow_common.js
[test_drawSnapshot.html]
support-files = file_drawWindow_source.html file_drawWindow_common.js
[test_drawSnapshot_fixed.html]
[test_imagebitmap.html]
tags = imagebitmap
[test_imagebitmap_close.html]

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for drawSnapshot</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
html {
height: 2000px;
overflow: hidden;
}
header {
position: fixed;
background: red;
display: block;
left: 0;
right: 0;
top: 0;
height: 200px;
}
</style>
<script type="application/javascript">
function make_canvas() {
var canvas = document.createElement("canvas");
canvas.setAttribute("height", 2000);
canvas.setAttribute("width", 500);
return canvas;
}
SimpleTest.waitForExplicitFinish();
window.addEventListener("load", runTests);
window.scrollTo(0, 1000);
async function runTests(event) {
var testCanvas = make_canvas();
var testCx = testCanvas.getContext("2d");
var testWrapCx = SpecialPowers.wrap(testCx);
// Take a snapshot of the page while scrolled down, so that the fixed header will
// be visually in the middle of the root scroll frame, but should still be at the
// top of the snapshot (since snapshots with a rect are taken relative to the document).
var rect = new window.DOMRect(0, 0, 500, 2000);
let image = await SpecialPowers.snapshotContext(window, rect, "rgb(255, 255, 255)");
testWrapCx.drawImage(image, 0, 0);
var refCanvas = make_canvas();
var refCx = refCanvas.getContext("2d");
// Construct a reference image with an equivalent red square at the top.
refCx.fillStyle = "white";
refCx.fillRect(0, 0, 500, 2000);
refCx.fillStyle = "red";
refCx.fillRect(0, 0, 500, 200);
assertSnapshots(testCanvas, refCanvas, true, null, "position:fixed element with scrolling", "reference");
SimpleTest.finish();
}
</script>
</head>
<body>
<header></header>
</body>
</html>

View File

@ -119,6 +119,7 @@ PaintFragment PaintFragment::Record(dom::BrowsingContext* aBc,
RenderDocumentFlags renderDocFlags = RenderDocumentFlags::None;
if (!(aFlags & CrossProcessPaintFlags::DrawView)) {
renderDocFlags = (RenderDocumentFlags::IgnoreViewportScrolling |
RenderDocumentFlags::ResetViewportScrolling |
RenderDocumentFlags::DocumentRelative);
}

View File

@ -4607,6 +4607,10 @@ nsresult PresShell::RenderDocument(const nsRect& aRect,
wouldFlushRetainedLayers = !IgnoringViewportScrolling();
mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
}
if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
wouldFlushRetainedLayers = true;
flags |= PaintFrameFlags::ResetViewportScrolling;
}
if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
}

View File

@ -779,6 +779,9 @@ class PresShell final : public nsStubDocumentObserver,
* might choose not to paint themes.
* set RenderDocumentFlags::IgnoreViewportScrolling to ignore clipping and
* scrollbar painting due to scrolling in the viewport
* set RenderDocumentFlags::ResetViewportScrolling to temporarily set the
* viewport scroll position to 0 so that position:fixed elements are drawn
* at their initial position.
* set RenderDocumentFlags::DrawCaret to draw the caret if one would be
* visible (by default the caret is never drawn)
* set RenderDocumentFlags::UseWidgetLayers to force rendering to go

View File

@ -160,6 +160,7 @@ enum class RenderDocumentFlags {
DocumentRelative = 1 << 5,
DrawWindowNotFlushing = 1 << 6,
UseHighQualityScaling = 1 << 7,
ResetViewportScrolling = 1 << 8,
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RenderDocumentFlags)

View File

@ -3224,12 +3224,34 @@ nsresult nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext,
visibleRegion = aDirtyRegion;
}
Maybe<nsPoint> originalScrollPosition;
auto maybeResetScrollPosition = MakeScopeExit([&]() {
if (originalScrollPosition && rootScrollFrame) {
nsIScrollableFrame* rootScrollableFrame =
presShell->GetRootScrollFrameAsScrollable();
MOZ_ASSERT(rootScrollableFrame->GetScrolledFrame()->GetPosition() ==
nsPoint());
rootScrollableFrame->GetScrolledFrame()->SetPosition(
*originalScrollPosition);
}
});
PresShell::AutoAssertNoFlush assertNoFlush(*presShell);
nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
bool ignoreViewportScrolling =
!aFrame->GetParent() && presShell->IgnoringViewportScrolling();
if (ignoreViewportScrolling && rootScrollFrame) {
nsIScrollableFrame* rootScrollableFrame =
presShell->GetRootScrollFrameAsScrollable();
if (aFlags & PaintFrameFlags::ResetViewportScrolling) {
// Temporarily scroll the root scroll frame to 0,0 so that position:fixed
// elements will appear fixed to the top-left of the document. We manually
// set the position of the scrolled frame instead of using ScrollTo, since
// the latter fires scroll listeners, which we don't want.
originalScrollPosition.emplace(
rootScrollableFrame->GetScrolledFrame()->GetPosition());
rootScrollableFrame->GetScrolledFrame()->SetPosition(nsPoint());
}
if (aFlags & PaintFrameFlags::DocumentRelative) {
// Make visibleRegion and aRenderingContext relative to the
// scrolled frame instead of the root frame.

View File

@ -1098,6 +1098,7 @@ class nsLayoutUtils {
Compressed = 0x200,
ForWebRender = 0x400,
UseHighQualityScaling = 0x800,
ResetViewportScrolling = 0x1000,
};
/**
@ -1126,6 +1127,9 @@ class nsLayoutUtils {
* as being relative to the document (normally it's relative to the CSS
* viewport) and the document is painted as if no scrolling has occured.
* Only considered if PresShell::IgnoringViewportScrolling is true.
* If ResetViewportScrolling is used, then the root scroll frame's scroll
* position is set to 0 during painting, so that position:fixed elements
* are drawn in their initial position.
* PAINT_TO_WINDOW sets painting to window to true on the display list
* builder even if we can't tell that we are painting to the window.
* If PAINT_EXISTING_TRANSACTION is set, then BeginTransaction() has already