1 // Copyright 2010 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "pdf/paint_manager.h"
14 #include "base/auto_reset.h"
15 #include "base/check.h"
16 #include "base/functional/bind.h"
17 #include "base/functional/callback.h"
18 #include "base/location.h"
19 #include "base/notreached.h"
20 #include "base/task/sequenced_task_runner.h"
21 #include "base/task/single_thread_task_runner.h"
22 #include "pdf/paint_ready_rect.h"
23 #include "third_party/skia/include/core/SkCanvas.h"
24 #include "third_party/skia/include/core/SkImage.h"
25 #include "third_party/skia/include/core/SkRect.h"
26 #include "third_party/skia/include/core/SkRefCnt.h"
27 #include "third_party/skia/include/core/SkSamplingOptions.h"
28 #include "third_party/skia/include/core/SkSurface.h"
29 #include "ui/gfx/blit.h"
30 #include "ui/gfx/geometry/point.h"
31 #include "ui/gfx/geometry/rect.h"
32 #include "ui/gfx/geometry/size.h"
33 #include "ui/gfx/geometry/skia_conversions.h"
34 #include "ui/gfx/geometry/vector2d.h"
35 #include "ui/gfx/geometry/vector2d_f.h"
37 namespace chrome_pdf {
39 PaintManager::PaintManager(Client* client) : client_(client) {
43 PaintManager::~PaintManager() = default;
46 gfx::Size PaintManager::GetNewContextSize(const gfx::Size& current_context_size,
47 const gfx::Size& plugin_size) {
48 // The amount of additional space in pixels to allocate to the right/bottom of
50 constexpr int kBufferSize = 50;
52 // Default to returning the same size.
53 gfx::Size result = current_context_size;
55 // The minimum size of the plugin before resizing the context to ensure we
56 // aren't wasting too much memory. We deduct twice the kBufferSize from the
57 // current context size which gives a threshhold that is kBufferSize below
58 // the plugin size when the context size was last computed.
60 std::max(current_context_size.width() - 2 * kBufferSize, 0),
61 std::max(current_context_size.height() - 2 * kBufferSize, 0));
63 // If the plugin size is bigger than the current context size, we need to
64 // resize the context. If the plugin size is smaller than the current
65 // context size by a given threshhold then resize the context so that we
66 // aren't wasting too much memory.
67 if (plugin_size.width() > current_context_size.width() ||
68 plugin_size.height() > current_context_size.height() ||
69 plugin_size.width() < min_size.width() ||
70 plugin_size.height() < min_size.height()) {
71 // Create a larger context than needed so that if we only resize by a
72 // small margin, we don't need a new context.
73 result = gfx::Size(plugin_size.width() + kBufferSize,
74 plugin_size.height() + kBufferSize);
80 void PaintManager::SetSize(const gfx::Size& new_size, float device_scale) {
81 if (GetEffectiveSize() == new_size &&
82 GetEffectiveDeviceScale() == device_scale) {
86 has_pending_resize_ = true;
87 pending_size_ = new_size;
88 pending_device_scale_ = device_scale;
90 view_size_changed_waiting_for_paint_ = true;
95 void PaintManager::SetTransform(float scale,
96 const gfx::Point& origin,
97 const gfx::Vector2d& translate,
98 bool schedule_flush) {
105 // translate_with_origin = origin - scale * origin - translate
106 gfx::Vector2dF translate_with_origin = origin.OffsetFromOrigin();
107 translate_with_origin.Scale(1.0f - scale);
108 translate_with_origin.Subtract(translate);
110 // TODO(crbug.com/1263614): Should update be deferred until `Flush()`?
111 client_->UpdateLayerTransform(scale, translate_with_origin);
117 if (flush_pending_) {
118 flush_requested_ = true;
124 void PaintManager::ClearTransform() {
125 SetTransform(1.f, gfx::Point(), gfx::Vector2d(), false);
128 void PaintManager::Invalidate() {
129 if (!surface_ && !has_pending_resize_)
132 EnsureCallbackPending();
133 aggregator_.InvalidateRect(gfx::Rect(GetEffectiveSize()));
136 void PaintManager::InvalidateRect(const gfx::Rect& rect) {
139 if (!surface_ && !has_pending_resize_)
142 // Clip the rect to the device area.
143 gfx::Rect clipped_rect =
144 gfx::IntersectRects(rect, gfx::Rect(GetEffectiveSize()));
145 if (clipped_rect.IsEmpty())
146 return; // Nothing to do.
148 EnsureCallbackPending();
149 aggregator_.InvalidateRect(clipped_rect);
152 void PaintManager::ScrollRect(const gfx::Rect& clip_rect,
153 const gfx::Vector2d& amount) {
156 if (!surface_ && !has_pending_resize_)
159 EnsureCallbackPending();
161 aggregator_.ScrollRect(clip_rect, amount);
164 gfx::Size PaintManager::GetEffectiveSize() const {
165 return has_pending_resize_ ? pending_size_ : plugin_size_;
168 float PaintManager::GetEffectiveDeviceScale() const {
169 return has_pending_resize_ ? pending_device_scale_ : device_scale_;
172 void PaintManager::EnsureCallbackPending() {
173 // The best way for us to do the next update is to get a notification that
174 // a previous one has completed. So if we're already waiting for one, we
175 // don't have to do anything differently now.
179 // If no flush is pending, we need to do a manual call to get back to the
180 // main thread. We may have one already pending, or we may need to schedule.
181 if (manual_callback_pending_)
184 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
185 FROM_HERE, base::BindOnce(&PaintManager::OnManualCallbackComplete,
186 weak_factory_.GetWeakPtr()));
187 manual_callback_pending_ = true;
190 void PaintManager::DoPaint() {
191 base::AutoReset<bool> auto_reset_in_paint(&in_paint_, true);
193 std::vector<PaintReadyRect> ready_rects;
194 std::vector<gfx::Rect> pending_rects;
196 DCHECK(aggregator_.HasPendingUpdate());
198 // Apply any pending resize. Setting the graphics to this class must happen
199 // before asking the plugin to paint in case it requests invalides or resizes.
200 // However, the bind must not happen until afterward since we don't want to
201 // have an unpainted device bound. The needs_binding flag tells us whether to
204 // Note that `has_pending_resize_` will always be set on the first DoPaint().
205 DCHECK(surface_ || has_pending_resize_);
206 if (has_pending_resize_) {
207 plugin_size_ = pending_size_;
208 // Only create a new graphics context if the current context isn't big
209 // enough or if it is far too big. This avoids creating a new context if
210 // we only resize by a small amount.
211 gfx::Size old_size = surface_
212 ? gfx::Size(surface_->width(), surface_->height())
214 gfx::Size new_size = GetNewContextSize(old_size, pending_size_);
215 if (old_size != new_size || !surface_) {
216 surface_ = SkSurfaces::Raster(
217 SkImageInfo::MakeN32Premul(new_size.width(), new_size.height()));
220 // TODO(crbug.com/1317832): Can we guarantee repainting some other way?
221 client_->InvalidatePluginContainer();
223 device_scale_ = 1.0f;
225 // Since we're binding a new one, all of the callbacks have been canceled.
226 manual_callback_pending_ = false;
227 flush_pending_ = false;
228 weak_factory_.InvalidateWeakPtrs();
231 if (pending_device_scale_ != device_scale_)
232 client_->UpdateScale(1.0f / pending_device_scale_);
233 device_scale_ = pending_device_scale_;
235 // This must be cleared before calling into the plugin since it may do
236 // additional invalidation or sizing operations.
237 has_pending_resize_ = false;
238 pending_size_ = gfx::Size();
241 PaintAggregator::PaintUpdate update = aggregator_.GetPendingUpdate();
242 client_->OnPaint(update.paint_rects, ready_rects, pending_rects);
244 if (ready_rects.empty() && pending_rects.empty())
245 return; // Nothing was painted, don't schedule a flush.
247 std::vector<PaintReadyRect> ready_now;
248 if (pending_rects.empty()) {
249 aggregator_.SetIntermediateResults(ready_rects, pending_rects);
250 ready_now = aggregator_.GetReadyRects();
251 aggregator_.ClearPendingUpdate();
253 // First, apply any scroll amount less than the surface's size.
254 if (update.has_scroll &&
255 std::abs(update.scroll_delta.x()) < surface_->width() &&
256 std::abs(update.scroll_delta.y()) < surface_->height()) {
257 // TODO(crbug.com/1263614): Use `SkSurface::notifyContentWillChange()`.
258 gfx::ScrollCanvas(surface_->getCanvas(), update.scroll_rect,
259 update.scroll_delta);
262 view_size_changed_waiting_for_paint_ = false;
264 std::vector<PaintReadyRect> ready_later;
265 for (const auto& ready_rect : ready_rects) {
266 // Don't flush any part (i.e. scrollbars) if we're resizing the browser,
267 // as that'll lead to flashes. Until we flush, the browser will use the
268 // previous image, but if we flush, it'll revert to using the blank image.
269 // We make an exception for the first paint since we want to show the
270 // default background color instead of the pepper default of black.
271 if (ready_rect.flush_now() &&
272 (!view_size_changed_waiting_for_paint_ || first_paint_)) {
273 ready_now.push_back(ready_rect);
275 ready_later.push_back(ready_rect);
278 // Take the rectangles, except the ones that need to be flushed right away,
279 // and save them so that everything is flushed at once.
280 aggregator_.SetIntermediateResults(ready_later, pending_rects);
282 if (ready_now.empty()) {
283 EnsureCallbackPending();
288 for (const auto& ready_rect : ready_now) {
289 SkRect skia_rect = gfx::RectToSkRect(ready_rect.rect());
290 surface_->getCanvas()->drawImageRect(
291 &ready_rect.image(), skia_rect, skia_rect, SkSamplingOptions(), nullptr,
292 SkCanvas::kStrict_SrcRectConstraint);
297 first_paint_ = false;
300 void PaintManager::Flush() {
301 flush_requested_ = false;
303 sk_sp<SkImage> snapshot = surface_->makeImageSnapshot();
304 surface_->getCanvas()->drawImage(snapshot.get(), /*x=*/0, /*y=*/0,
305 SkSamplingOptions(), /*paint=*/nullptr);
306 client_->UpdateSnapshot(std::move(snapshot));
308 // TODO(crbug.com/1403311): Complete flush synchronously.
309 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
310 FROM_HERE, base::BindOnce(&PaintManager::OnFlushComplete,
311 weak_factory_.GetWeakPtr()));
312 flush_pending_ = true;
315 void PaintManager::OnFlushComplete() {
316 DCHECK(flush_pending_);
317 flush_pending_ = false;
319 // If more paints were enqueued while we were waiting for the flush to
320 // complete, execute them now.
321 if (aggregator_.HasPendingUpdate())
324 // If there was another flush request while flushing we flush again.
325 if (flush_requested_) {
330 void PaintManager::OnManualCallbackComplete() {
331 DCHECK(manual_callback_pending_);
332 manual_callback_pending_ = false;
334 // Just because we have a manual callback doesn't mean there are actually any
335 // invalid regions. Even though we only schedule this callback when something
336 // is pending, a Flush callback could have come in before this callback was
337 // executed and that could have cleared the queue.
338 if (aggregator_.HasPendingUpdate())
342 } // namespace chrome_pdf