[Paint Preview] Blink Implementation of Paint Preview
This CL implements the required code to capture paint previews in Blink.
The majority of the code is shared with printing; however, new flags are
introduced to handle the differences between printing and painting of
previews. This CL *does not* introduce support for OOP iframe paint
previews and scrolling them (to be handled in a separate CL).
Desired Behaviors;
- Paint Previews should look exactly like the current webpage and should
not reformat in the way printing does. This includes ignoring print
display tags and the simplifications used in painting.
- Paint Previews should re-layout to be the full contents of the page
not just the viewport. This is achieved by printing to a canvas the
size of the document (single page).
- Paint Previews should print to a single Skia Picture (per frame)
rather than a Metafile Skia Document.
Design doc: go/fdt-design
Part of landing:
https://chromium-review.googlesource.com/c/chromium/src/+/1786583
Bug: 1001109
Change-Id: If0e947befea8ecc78b2cbee950e8c1cbd8e1281e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1844057
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: Philip Rogers <pdr@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713822}
diff --git a/cc/paint/paint_canvas.h b/cc/paint/paint_canvas.h
index 32918efd..31a736d 100644
--- a/cc/paint/paint_canvas.h
+++ b/cc/paint/paint_canvas.h
@@ -18,6 +18,10 @@
class MetafileSkia;
} // namespace printing
+namespace paint_preview {
+class PaintPreviewTracker;
+} // namespace paint_preview
+
namespace cc {
class SkottieWrapper;
class PaintFlags;
@@ -191,12 +195,19 @@
void SetPrintingMetafile(printing::MetafileSkia* metafile) {
metafile_ = metafile;
}
+ paint_preview::PaintPreviewTracker* GetPaintPreviewTracker() const {
+ return tracker_;
+ }
+ void SetPaintPreviewTracker(paint_preview::PaintPreviewTracker* tracker) {
+ tracker_ = tracker;
+ }
// Subclasses can override to handle custom data.
virtual void recordCustomData(uint32_t id) {}
private:
printing::MetafileSkia* metafile_ = nullptr;
+ paint_preview::PaintPreviewTracker* tracker_ = nullptr;
};
class CC_PAINT_EXPORT PaintCanvasAutoRestore {
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 7e247c6..800f4a7c 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -568,6 +568,7 @@
"//testing/gmock",
"//testing/gtest",
"//ui/base",
+ "//ui/native_theme:native_theme",
"//url",
]
diff --git a/components/paint_preview/renderer/DEPS b/components/paint_preview/renderer/DEPS
index 53de874..497159a7 100644
--- a/components/paint_preview/renderer/DEPS
+++ b/components/paint_preview/renderer/DEPS
@@ -3,4 +3,5 @@
"+content/public/renderer",
"+content/public/test",
"+third_party/blink/public",
+ "+ui/native_theme/native_theme_features.h",
]
diff --git a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
index c62e7aa..327d1389 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
@@ -7,11 +7,17 @@
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/threading/thread_restrictions.h"
+#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/public/test/render_view_test.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/skia/include/core/SkPicture.h"
+#include "ui/native_theme/native_theme_features.h"
namespace paint_preview {
@@ -38,6 +44,12 @@
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ // TODO(crbug/1022398): This is required to bypass a seemingly unrelated
+ // DCHECK for |use_overlay_scrollbars_| in NativeThemeAura on ChromeOS when
+ // painting scrollbars when first calling LoadHTML().
+ feature_list_.InitAndDisableFeature(features::kOverlayScrollbar);
+
RenderViewTest::SetUp();
}
@@ -49,14 +61,155 @@
private:
base::ScopedTempDir temp_dir_;
+ base::test::ScopedFeatureList feature_list_;
};
-TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrame) {
+TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndClipping) {
+ LoadHTML(
+ "<body>"
+ " <div style='width: 600px; height: 80vh; "
+ " background-color: #ff0000'> </div>"
+ " <a style='display:inline-block' href='http://www.google.com'>Foo</a>"
+ " <div style='width: 100px; height: 600px; "
+ " background-color: #000000'> </div>"
+ " <div style='overflow: hidden; width: 100px; height: 100px;"
+ " background: orange;'>"
+ " <div style='width: 500px; height: 500px;"
+ " background: yellow;'></div>"
+ " </div>"
+ "</body>");
+ base::FilePath skp_path = MakeTestFilePath("test.skp");
+
+ mojom::PaintPreviewCaptureParamsPtr params =
+ mojom::PaintPreviewCaptureParams::New();
+ auto token = base::UnguessableToken::Create();
+ params->guid = token;
+ params->clip_rect = gfx::Rect();
+ params->is_main_frame = true;
+ base::File skp_file(skp_path,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ params->file = std::move(skp_file);
+
+ auto out_response = mojom::PaintPreviewCaptureResponse::New();
+ content::RenderFrame* frame = GetFrame();
+ int routing_id = frame->GetRoutingID();
+ PaintPreviewRecorderImpl paint_preview_recorder(frame);
+ paint_preview_recorder.CapturePaintPreview(
+ std::move(params),
+ base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
+ base::Unretained(&out_response)));
+
+ // Here id() is just the routing ID.
+ EXPECT_EQ(static_cast<int>(out_response->id), routing_id);
+ EXPECT_EQ(out_response->content_id_proxy_id_map.size(), 0U);
+
+ EXPECT_EQ(out_response->links.size(), 1U);
+ EXPECT_EQ(out_response->links[0]->url, GURL("http://www.google.com/"));
+ // Relaxed checks on dimensions and no checks on positions. This is not
+ // intended to test the rendering behavior of the page only that a link
+ // was captured and has a bounding box.
+ EXPECT_GT(out_response->links[0]->rect.width(), 0);
+ EXPECT_GT(out_response->links[0]->rect.height(), 0);
+
+ sk_sp<SkPicture> pic;
+ {
+ base::ScopedAllowBlockingForTesting scope;
+ FileRStream rstream(base::File(
+ skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
+ pic = SkPicture::MakeFromStream(&rstream, nullptr);
+ }
+ // The min page height is the sum of the three top level divs of 800. The min
+ // width is that of the widest div at 600.
+ EXPECT_GE(pic->cullRect().height(), 800);
+ EXPECT_GE(pic->cullRect().width(), 600);
+ SkBitmap bitmap;
+ ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
+ pic->cullRect().height()));
+ SkCanvas canvas(bitmap);
+ canvas.drawPicture(pic);
+ // This should be inside the top right corner of the first top level div.
+ // Success means there was no horizontal clipping as this region is red,
+ // matching the div.
+ EXPECT_EQ(bitmap.getColor(600, 50), 0xFFFF0000U);
+ // This should be inside the bottom of the second top level div. Success means
+ // there was no vertical clipping as this region is black matching the div. If
+ // the yellow div within the orange div overflowed then this would be yellow
+ // and fail.
+ EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 150), 0xFF000000U);
+ // This should be for the white background in the bottom right. This checks
+ // that the background is not clipped.
+ EXPECT_EQ(bitmap.getColor(pic->cullRect().width() - 50,
+ pic->cullRect().height() - 50),
+ 0xFFFFFFFFU);
+}
+
+TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureFragment) {
+ // Use position absolute position to check that the captured link dimensions
+ // match what is specified.
LoadHTML(
"<body style='min-height:1000px;'>"
- " <div style='width: 100px; height: 100px; "
- " background-color: #000000'> </div>"
- " <p><a href='https://www.foo.com'>Foo</a></p>"
+ " <a style='position: absolute; left: -15px; top: 0px; width: 20px; "
+ " height: 30px;' href='#fragment'>Foo</a>"
+ " <h1 id='fragment'>I'm a fragment</h1>"
+ "</body>");
+ base::FilePath skp_path = MakeTestFilePath("test.skp");
+
+ mojom::PaintPreviewCaptureParamsPtr params =
+ mojom::PaintPreviewCaptureParams::New();
+ auto token = base::UnguessableToken::Create();
+ params->guid = token;
+ params->clip_rect = gfx::Rect();
+ params->is_main_frame = true;
+ base::File skp_file(skp_path,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ params->file = std::move(skp_file);
+
+ auto out_response = mojom::PaintPreviewCaptureResponse::New();
+ content::RenderFrame* frame = GetFrame();
+ int routing_id = frame->GetRoutingID();
+ PaintPreviewRecorderImpl paint_preview_recorder(frame);
+ paint_preview_recorder.CapturePaintPreview(
+ std::move(params),
+ base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
+ &out_response));
+ // Here id() is just the routing ID.
+ EXPECT_EQ(static_cast<int>(out_response->id), routing_id);
+ EXPECT_EQ(out_response->content_id_proxy_id_map.size(), 0U);
+
+ EXPECT_EQ(out_response->links.size(), 1U);
+ EXPECT_EQ(out_response->links[0]->url, GURL("fragment"));
+ EXPECT_EQ(out_response->links[0]->rect.x(), -15);
+ EXPECT_EQ(out_response->links[0]->rect.y(), 0);
+ EXPECT_EQ(out_response->links[0]->rect.width(), 20);
+ EXPECT_EQ(out_response->links[0]->rect.height(), 30);
+}
+
+TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidFile) {
+ LoadHTML("<body></body>");
+
+ mojom::PaintPreviewCaptureParamsPtr params =
+ mojom::PaintPreviewCaptureParams::New();
+ auto token = base::UnguessableToken::Create();
+ params->guid = token;
+ params->clip_rect = gfx::Rect();
+ params->is_main_frame = true;
+ base::File skp_file; // Invalid file.
+ params->file = std::move(skp_file);
+
+ content::RenderFrame* frame = GetFrame();
+ PaintPreviewRecorderImpl paint_preview_recorder(frame);
+ paint_preview_recorder.CapturePaintPreview(
+ std::move(params),
+ base::BindOnce(&OnCaptureFinished,
+ mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
+}
+
+TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndLocalFrame) {
+ LoadHTML(
+ "<body style='min-height:1000px;'>"
+ " <iframe style='width: 500px, height: 500px'"
+ " srcdoc=\"<div style='width: 100px; height: 100px;"
+ " background-color: #000000'> </div>\"></iframe>"
"</body>");
base::FilePath skp_path = MakeTestFilePath("test.skp");
@@ -81,29 +234,40 @@
// Here id() is just the routing ID.
EXPECT_EQ(static_cast<int>(out_response->id), routing_id);
EXPECT_EQ(out_response->content_id_proxy_id_map.size(), 0U);
-
- // NOTE: should be non-zero once the Blink implementation is hooked up.
- EXPECT_EQ(out_response->links.size(), 0U);
}
-TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidFile) {
- LoadHTML("<body></body>");
+TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureLocalFrame) {
+ LoadHTML(
+ "<body style='min-height:1000px;'>"
+ " <iframe style='width: 500px, height: 500px'"
+ " srcdoc=\"<div style='width: 100px; height: 100px;"
+ " background-color: #000000'> </div>\"></iframe>"
+ "</body>");
+ base::FilePath skp_path = MakeTestFilePath("test.skp");
mojom::PaintPreviewCaptureParamsPtr params =
mojom::PaintPreviewCaptureParams::New();
auto token = base::UnguessableToken::Create();
params->guid = token;
params->clip_rect = gfx::Rect();
- params->is_main_frame = true;
- base::File skp_file; // Invalid file.
+ params->is_main_frame = false;
+ base::File skp_file(skp_path,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
params->file = std::move(skp_file);
- content::RenderFrame* frame = GetFrame();
- PaintPreviewRecorderImpl paint_preview_recorder(frame);
+ auto out_response = mojom::PaintPreviewCaptureResponse::New();
+ auto* child_frame = content::RenderFrame::FromWebFrame(
+ GetFrame()->GetWebFrame()->FirstChild()->ToWebLocalFrame());
+ ASSERT_TRUE(child_frame);
+ int routing_id = child_frame->GetRoutingID();
+ PaintPreviewRecorderImpl paint_preview_recorder(child_frame);
paint_preview_recorder.CapturePaintPreview(
std::move(params),
- base::BindOnce(&OnCaptureFinished,
- mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
+ base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
+ base::Unretained(&out_response)));
+ // Here id() is just the routing ID.
+ EXPECT_EQ(static_cast<int>(out_response->id), routing_id);
+ EXPECT_EQ(out_response->content_id_proxy_id_map.size(), 0U);
}
} // namespace paint_preview
diff --git a/components/paint_preview/renderer/paint_preview_recorder_impl.cc b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
index 3b236b3..d807c8ec 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_impl.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
@@ -101,15 +101,18 @@
}
cc::PaintRecorder recorder;
- recorder.beginRecording(bounds.width(), bounds.height());
PaintPreviewTracker tracker(params->guid, routing_id_, is_main_frame_);
- // TODO(crbug/1008885): Create a method on |canvas| to inject |tracker_| to
- // propagate to graphics contexts and inner canvases
- // TODO(crbug/1001109): Create a method on |frame| to execute the capture
- // within Blink.
+ cc::PaintCanvas* canvas =
+ recorder.beginRecording(bounds.width(), bounds.height());
+ canvas->SetPaintPreviewTracker(&tracker);
+ bool success = frame->CapturePaintPreview(bounds, canvas);
// Restore to before out-of-lifecycle paint phase.
frame->DispatchAfterPrintEvent();
+ if (!success) {
+ *status = mojom::PaintPreviewStatus::kCaptureFailed;
+ return;
+ }
// TODO(crbug/1011896): Determine if making this async would be beneficial.
*status = FinishRecording(recorder.finishRecordingAsPicture(), bounds,
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index ebf2ca9..bac8761 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -701,6 +701,12 @@
virtual bool GetPrintPresetOptionsForPlugin(const WebNode&,
WebPrintPresetOptions*) = 0;
+ // Paint Preview ------------------------------------------------------------
+
+ // Captures a full frame paint preview of the WebFrame including subframes.
+ virtual bool CapturePaintPreview(const WebRect& bounds,
+ cc::PaintCanvas* canvas) = 0;
+
// Focus --------------------------------------------------------------
// Advance the focus of the WebView to next text input element from current
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 99aa1671..ba7f272 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -1047,6 +1047,7 @@
http_refresh_scheduler_(MakeGarbageCollected<HttpRefreshScheduler>(this)),
well_formed_(false),
printing_(kNotPrinting),
+ is_painting_preview_(false),
compatibility_mode_(kNoQuirksMode),
compatibility_mode_locked_(false),
last_focus_type_(kWebFocusTypeNone),
@@ -3555,6 +3556,10 @@
}
}
+void Document::SetIsPaintingPreview(bool is_painting_preview) {
+ is_painting_preview_ = is_painting_preview;
+}
+
// https://html.spec.whatwg.org/C/dynamic-markup-insertion.html#document-open-steps
void Document::open(Document* entered_document,
ExceptionState& exception_state) {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 8261c70a..26b5c756 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -774,6 +774,12 @@
}
void SetPrinting(PrintingState);
+ bool IsPaintingPreview() const { return is_painting_preview_; }
+ bool IsCapturingLayout() const {
+ return printing_ == kPrinting || is_painting_preview_;
+ }
+ void SetIsPaintingPreview(bool);
+
enum CompatibilityMode { kQuirksMode, kLimitedQuirksMode, kNoQuirksMode };
void SetCompatibilityMode(CompatibilityMode);
@@ -1840,6 +1846,7 @@
Member<CSSStyleSheet> elem_sheet_;
PrintingState printing_;
+ bool is_painting_preview_;
CompatibilityMode compatibility_mode_;
// This is cheaper than making setCompatibilityMode virtual.
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 24ea8a0..3640a0c 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1287,6 +1287,18 @@
frame_view->ScheduleAnimation();
}
+bool LocalFrame::ClipsContent() const {
+ // A paint preview shouldn't clip to the viewport if it is the main frame or a
+ // root remote frame.
+ if (GetDocument()->IsPaintingPreview() && IsLocalRoot())
+ return false;
+
+ if (IsMainFrame())
+ return GetSettings()->GetMainFrameClipsContent();
+ // By default clip to viewport.
+ return true;
+}
+
void LocalFrame::SetViewportIntersectionFromParent(
const ViewportIntersectionState& intersection_state) {
// We only schedule an update if the viewport intersection or occlusion state
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index a5e3023a..75cb56b5 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -411,6 +411,9 @@
void WasHidden();
void WasShown();
+ // Whether the frame clips its content to the frame's size.
+ bool ClipsContent() const;
+
// For a navigation initiated from this LocalFrame with user gesture, record
// the UseCounter AdClickNavigation if this frame is an adframe.
//
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index a716d44..d7005d1 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2342,7 +2342,7 @@
DCHECK_EQ(target_state, DocumentLifecycle::kPaintClean);
RunPaintLifecyclePhase();
DCHECK(ShouldThrottleRendering() ||
- (frame_->GetDocument()->Printing() &&
+ (frame_->GetDocument()->IsCapturingLayout() &&
!RuntimeEnabledFeatures::PrintBrowserEnabled()) ||
Lifecycle().GetState() == DocumentLifecycle::kPaintClean);
}
@@ -2495,16 +2495,16 @@
void LocalFrameView::RunPaintLifecyclePhase() {
TRACE_EVENT0("blink,benchmark", "LocalFrameView::RunPaintLifecyclePhase");
- // While printing a document, the paint walk is done by the printing component
- // into a special canvas. There is no point doing a normal paint step (or
- // animations update) when in this mode.
+ // While printing or capturing a paint preview of a document, the paint walk
+ // is done into a special canvas. There is no point doing a normal paint step
+ // (or animations update) when in this mode.
//
// RuntimeEnabledFeatures::PrintBrowserEnabled is a mode which runs the
// browser normally, but renders every page as if it were being printed.
// See crbug.com/667547
- bool print_mode_enabled = frame_->GetDocument()->Printing() &&
- !RuntimeEnabledFeatures::PrintBrowserEnabled();
- if (!print_mode_enabled)
+ bool is_capturing_layout = frame_->GetDocument()->IsCapturingLayout() &&
+ !RuntimeEnabledFeatures::PrintBrowserEnabled();
+ if (!is_capturing_layout)
PaintTree();
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
@@ -2513,7 +2513,7 @@
}
}
- if (!print_mode_enabled) {
+ if (!is_capturing_layout) {
bool needed_update = !paint_artifact_compositor_ ||
paint_artifact_compositor_->NeedsUpdate();
PushPaintArtifactToCompositor();
@@ -3541,9 +3541,11 @@
// with CompositeAfterPaint.
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
- // Paint the whole rect if "MainFrameClipsContent" is false, meaning that
- // WebPreferences::record_whole_document is true.
- if (!frame_->GetSettings()->GetMainFrameClipsContent())
+ // Paint the whole rect if ClipsContent is false, meaning that the whole
+ // document should be recorded. This occurs if:
+ // - A paint preview is being captured.
+ // - WebPreferences::record_whole_document is true.
+ if (!frame_->ClipsContent())
return;
// By default we consider the bounds of the FrameView to be what is considered
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 4c685ca..cd078c7 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -514,6 +514,60 @@
WebPrintParams print_params_;
};
+class PaintPreviewContext : public PrintContext {
+ public:
+ PaintPreviewContext(LocalFrame* frame) : PrintContext(frame, false) {}
+ ~PaintPreviewContext() override = default;
+
+ bool Capture(cc::PaintCanvas* canvas, FloatSize size) {
+ // This code is based on ChromePrintContext::SpoolSinglePage()/SpoolPage().
+ // It differs in that it:
+ // 1. Uses a different set of flags for painting and the graphics context.
+ // 2. Paints a single page of |size| rather than a specific page in a
+ // reformatted document.
+ // 3. Does no scaling.
+ if (!GetFrame()->GetDocument() ||
+ !GetFrame()->GetDocument()->GetLayoutView())
+ return false;
+ GetFrame()->View()->UpdateLifecyclePhasesForPrinting();
+ if (!GetFrame()->GetDocument() ||
+ !GetFrame()->GetDocument()->GetLayoutView())
+ return false;
+ FloatRect bounds(0, 0, size.Width(), size.Height());
+ PaintRecordBuilder builder(nullptr, nullptr, nullptr,
+ canvas->GetPaintPreviewTracker());
+ builder.Context().SetIsPaintingPreview(true);
+
+ LocalFrameView* frame_view = GetFrame()->View();
+ DCHECK(frame_view);
+ PropertyTreeState property_tree_state =
+ frame_view->GetLayoutView()->FirstFragment().LocalBorderBoxProperties();
+
+ // This calls BeginRecording on |builder| with dimensions specified by the
+ // CullRect.
+ frame_view->PaintContentsOutsideOfLifecycle(
+ builder.Context(),
+ kGlobalPaintNormalPhase | kGlobalPaintFlattenCompositingLayers |
+ kGlobalPaintAddUrlMetadata,
+ CullRect(RoundedIntRect(bounds)));
+ {
+ // Add anchors.
+ ScopedPaintChunkProperties scoped_paint_chunk_properties(
+ builder.Context().GetPaintController(), property_tree_state, builder,
+ DisplayItem::kPrintedContentDestinationLocations);
+ DrawingRecorder line_boundary_recorder(
+ builder.Context(), builder,
+ DisplayItem::kPrintedContentDestinationLocations);
+ OutputLinkedDestinations(builder.Context(), RoundedIntRect(bounds));
+ }
+ canvas->drawPicture(builder.EndRecording(property_tree_state));
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PaintPreviewContext);
+};
+
static WebDocumentLoader* DocumentLoaderForDocLoader(DocumentLoader* loader) {
return loader ? WebDocumentLoaderImpl::FromDocumentLoader(loader) : nullptr;
}
@@ -1548,6 +1602,21 @@
return plugin_container->GetPrintPresetOptionsFromDocument(preset_options);
}
+bool WebLocalFrameImpl::CapturePaintPreview(const WebRect& bounds,
+ cc::PaintCanvas* canvas) {
+ FloatSize float_bounds(bounds.width, bounds.height);
+ GetFrame()->GetDocument()->SetIsPaintingPreview(true);
+ ResourceCacheValidationSuppressor validation_suppressor(
+ GetFrame()->GetDocument()->Fetcher());
+ GetFrame()->View()->ForceLayoutForPagination(float_bounds, float_bounds, 1);
+ PaintPreviewContext* paint_preview_context =
+ MakeGarbageCollected<PaintPreviewContext>(GetFrame());
+ bool success = paint_preview_context->Capture(canvas, float_bounds);
+ GetFrame()->GetDocument()->SetIsPaintingPreview(false);
+ GetFrame()->EndPrinting();
+ return success;
+}
+
bool WebLocalFrameImpl::HasCustomPageSizeStyle(int page_index) {
return GetFrame()->GetDocument()->StyleForPage(page_index)->PageSizeType() !=
EPageSizeType::kAuto;
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index ef0067c..e862362 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -297,6 +297,8 @@
void DispatchAfterPrintEvent() override;
bool GetPrintPresetOptionsForPlugin(const WebNode&,
WebPrintPresetOptions*) override;
+ bool CapturePaintPreview(const WebRect& bounds,
+ cc::PaintCanvas* canvas) override;
void AdvanceFocusInForm(WebFocusType) override;
bool ShouldSuppressKeyboardForFocusedElement() override;
WebPerformance Performance() const override;
diff --git a/third_party/blink/renderer/core/layout/layout_view.cc b/third_party/blink/renderer/core/layout/layout_view.cc
index 82387ff4..9a16efd 100644
--- a/third_party/blink/renderer/core/layout/layout_view.cc
+++ b/third_party/blink/renderer/core/layout/layout_view.cc
@@ -647,8 +647,9 @@
RETURN_SCROLLBAR_MODE(ScrollbarMode::kAlwaysOff);
}
- if (document.Printing()) {
- // When printing, frame-level scrollbars are never displayed.
+ if (document.IsCapturingLayout()) {
+ // When capturing layout (e.g. printing), frame-level scrollbars are never
+ // displayed.
// TODO(szager): Figure out the right behavior when printing an overflowing
// iframe. https://bugs.chromium.org/p/chromium/issues/detail?id=777528
RETURN_SCROLLBAR_MODE(ScrollbarMode::kAlwaysOff);
diff --git a/third_party/blink/renderer/core/paint/frame_painter.cc b/third_party/blink/renderer/core/paint/frame_painter.cc
index f8ce906d..3ca2029 100644
--- a/third_party/blink/renderer/core/paint/frame_painter.cc
+++ b/third_party/blink/renderer/core/paint/frame_painter.cc
@@ -76,7 +76,7 @@
PaintLayerFlags root_layer_paint_flags = 0;
// This will prevent clipping the root PaintLayer to its visible content
// rect when root layer scrolling is enabled.
- if (document->Printing())
+ if (document->IsCapturingLayout())
root_layer_paint_flags = kPaintLayerPaintingOverflowContents;
PaintLayer* root_layer = layout_view->Layer();
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
index 16c67f1..9c3a8b9 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
@@ -112,8 +112,8 @@
const GraphicsContext& context,
const PaintLayerPaintingInfo& painting_info,
PaintLayerFlags paint_flags) {
- // Caching is not needed during printing.
- if (context.Printing())
+ // Caching is not needed during printing or painting previews.
+ if (context.Printing() || context.IsPaintingPreview())
return false;
if (context.GetPaintController().IsSkippingCache())
@@ -198,8 +198,7 @@
// of the main frame.
if (layer.GetLayoutObject().IsLayoutView()) {
const auto* frame = layer.GetLayoutObject().GetFrame();
- if (frame && frame->IsMainFrame() &&
- !frame->GetSettings()->GetMainFrameClipsContent())
+ if (frame && frame->IsMainFrame() && !frame->ClipsContent())
return true;
}
return false;
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 3e59e20..cc6a0ea 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1541,8 +1541,7 @@
static bool CanOmitOverflowClip(const LayoutObject& object) {
DCHECK(NeedsOverflowClip(object));
- if (object.IsLayoutView() && object.GetFrame()->IsMainFrame() &&
- !object.GetFrame()->GetSettings()->GetMainFrameClipsContent()) {
+ if (object.IsLayoutView() && !object.GetFrame()->ClipsContent()) {
return true;
}
diff --git a/third_party/blink/renderer/core/paint/view_painter.cc b/third_party/blink/renderer/core/paint/view_painter.cc
index 5d6bed5f..b643cc2 100644
--- a/third_party/blink/renderer/core/paint/view_painter.cc
+++ b/third_party/blink/renderer/core/paint/view_painter.cc
@@ -48,9 +48,12 @@
// The background rect always includes at least the visible content size.
PhysicalRect background_rect(layout_view_.BackgroundRect());
- // When printing, paint the entire unclipped scrolling content area.
- if (paint_info.IsPrinting())
+ // When printing or painting a preview, paint the entire unclipped scrolling
+ // content area.
+ if (paint_info.IsPrinting() ||
+ !layout_view_.GetFrameView()->GetFrame().ClipsContent()) {
background_rect.Unite(layout_view_.DocumentRect());
+ }
const DisplayItemClient* background_client = &layout_view_;
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index f2d047a..22074d10 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -306,6 +306,8 @@
canvas_ = paint_recorder_.beginRecording(bounds);
if (metafile_)
canvas_->SetPrintingMetafile(metafile_);
+ if (tracker_)
+ canvas_->SetPaintPreviewTracker(tracker_);
}
namespace {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h
index 42631cf..ca07051 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h
@@ -19,10 +19,6 @@
class PaintCanvas;
}
-namespace paint_preview {
-class PaintPreviewTracker;
-}
-
namespace blink {
class GraphicsContext;
class PaintController;