1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CrossProcessPaint.h"
9 #include "mozilla/dom/CanonicalBrowsingContext.h"
10 #include "mozilla/dom/ContentProcessManager.h"
11 #include "mozilla/dom/ImageBitmap.h"
12 #include "mozilla/dom/BrowserParent.h"
13 #include "mozilla/dom/PWindowGlobalParent.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/WindowGlobalParent.h"
16 #include "mozilla/dom/WindowGlobalChild.h"
17 #include "mozilla/dom/WindowGlobalActorsBinding.h"
18 #include "mozilla/gfx/DrawEventRecorder.h"
19 #include "mozilla/gfx/InlineTranslator.h"
20 #include "mozilla/Logging.h"
21 #include "mozilla/PresShell.h"
23 #include "gfxPlatform.h"
25 #include "nsContentUtils.h"
26 #include "nsIDocShell.h"
27 #include "nsPresContext.h"
29 static mozilla::LazyLogModule
gCrossProcessPaintLog("CrossProcessPaint");
30 static mozilla::LazyLogModule
gPaintFragmentLog("PaintFragment");
32 #define CPP_LOG(msg, ...) \
33 MOZ_LOG(gCrossProcessPaintLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
34 #define PF_LOG(msg, ...) \
35 MOZ_LOG(gPaintFragmentLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
40 using namespace mozilla::ipc
;
42 /// The minimum scale we allow tabs to be rasterized at.
43 static const float kMinPaintScale
= 0.05f
;
46 PaintFragment
PaintFragment::Record(dom::BrowsingContext
* aBc
,
47 const Maybe
<IntRect
>& aRect
, float aScale
,
48 nscolor aBackgroundColor
,
49 CrossProcessPaintFlags aFlags
) {
50 nsIDocShell
* ds
= aBc
->GetDocShell();
52 PF_LOG("Couldn't find docshell.\n");
53 return PaintFragment
{};
56 RefPtr
<nsPresContext
> presContext
= ds
->GetPresContext();
58 PF_LOG("Couldn't find PresContext.\n");
59 return PaintFragment
{};
64 nsCOMPtr
<nsIWidget
> widget
=
65 nsContentUtils::WidgetForDocument(presContext
->Document());
67 // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
68 LayoutDeviceIntRect boundsDevice
= widget
->GetBounds();
69 boundsDevice
.MoveTo(0, 0);
70 nsRect boundsAu
= LayoutDevicePixel::ToAppUnits(
71 boundsDevice
, presContext
->AppUnitsPerDevPixel());
72 rect
= gfx::RoundedOut(CSSPixel::FromAppUnits(boundsAu
));
74 rect
= CSSIntRect::FromUnknownRect(*aRect
);
78 // TODO: Should we return an empty surface here?
79 PF_LOG("Empty rect to paint.\n");
80 return PaintFragment
{};
83 // FIXME: Shouldn't the surface size be in device rather than CSS pixels?
84 CSSIntSize surfaceSize
= rect
.Size();
85 surfaceSize
.width
*= aScale
;
86 surfaceSize
.height
*= aScale
;
90 "[browsingContext=%p, "
91 "rect=(%d, %d) x (%d, %d), "
93 "color=(%u, %u, %u, %u)]\n",
94 aBc
, rect
.x
, rect
.y
, rect
.width
, rect
.height
, aScale
,
95 NS_GET_R(aBackgroundColor
), NS_GET_G(aBackgroundColor
),
96 NS_GET_B(aBackgroundColor
), NS_GET_A(aBackgroundColor
));
98 // Check for invalid sizes
99 if (surfaceSize
.width
<= 0 || surfaceSize
.height
<= 0 ||
100 !Factory::CheckSurfaceSize(surfaceSize
.ToUnknownSize())) {
101 PF_LOG("Invalid surface size of (%d x %d).\n", surfaceSize
.width
,
103 return PaintFragment
{};
106 // Flush any pending notifications
107 nsContentUtils::FlushLayoutForTree(ds
->GetWindow());
109 // Initialize the recorder
110 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
111 RefPtr
<DrawTarget
> referenceDt
= Factory::CreateDrawTarget(
112 gfxPlatform::GetPlatform()->GetSoftwareBackend(), IntSize(1, 1), format
);
114 // TODO: This may OOM crash if the content is complex enough
115 RefPtr
<DrawEventRecorderMemory
> recorder
=
116 MakeAndAddRef
<DrawEventRecorderMemory
>(nullptr);
117 RefPtr
<DrawTarget
> dt
= Factory::CreateRecordingDrawTarget(
118 recorder
, referenceDt
,
119 IntRect(IntPoint(0, 0), surfaceSize
.ToUnknownSize()));
120 if (!dt
|| !dt
->IsValid()) {
121 PF_LOG("Failed to create drawTarget.\n");
122 return PaintFragment
{};
125 RenderDocumentFlags renderDocFlags
= RenderDocumentFlags::None
;
126 if (!(aFlags
& CrossProcessPaintFlags::DrawView
)) {
127 renderDocFlags
|= RenderDocumentFlags::IgnoreViewportScrolling
|
128 RenderDocumentFlags::DocumentRelative
;
129 if (aFlags
& CrossProcessPaintFlags::ResetScrollPosition
) {
130 renderDocFlags
|= RenderDocumentFlags::ResetViewportScrolling
;
133 if (aFlags
& CrossProcessPaintFlags::UseHighQualityScaling
) {
134 renderDocFlags
|= RenderDocumentFlags::UseHighQualityScaling
;
137 // Perform the actual rendering
139 nsRect r
= CSSPixel::ToAppUnits(rect
);
141 // This matches what nsDeviceContext::CreateRenderingContext does.
142 if (presContext
->IsPrintingOrPrintPreview()) {
143 dt
->AddUserData(&sDisablePixelSnapping
, (void*)0x1, nullptr);
146 gfxContext
thebes(dt
);
147 thebes
.SetMatrix(Matrix::Scaling(aScale
, aScale
));
148 thebes
.SetCrossProcessPaintScale(aScale
);
149 RefPtr
<PresShell
> presShell
= presContext
->PresShell();
150 Unused
<< presShell
->RenderDocument(r
, renderDocFlags
, aBackgroundColor
,
154 if (!recorder
->mOutputStream
.mValid
) {
155 recorder
->DetachResources();
156 return PaintFragment
{};
159 ByteBuf recording
= ByteBuf((uint8_t*)recorder
->mOutputStream
.mData
,
160 recorder
->mOutputStream
.mLength
,
161 recorder
->mOutputStream
.mCapacity
);
162 recorder
->mOutputStream
.mData
= nullptr;
163 recorder
->mOutputStream
.mLength
= 0;
164 recorder
->mOutputStream
.mCapacity
= 0;
166 PaintFragment fragment
{
167 surfaceSize
.ToUnknownSize(),
168 std::move(recording
),
169 std::move(recorder
->TakeDependentSurfaces()),
172 recorder
->DetachResources();
176 bool PaintFragment::IsEmpty() const {
177 return !mRecording
.mData
|| mRecording
.mLen
== 0 || mSize
== IntSize(0, 0);
180 PaintFragment::PaintFragment(IntSize aSize
, ByteBuf
&& aRecording
,
181 nsTHashSet
<uint64_t>&& aDependencies
)
183 mRecording(std::move(aRecording
)),
184 mDependencies(std::move(aDependencies
)) {}
186 static dom::TabId
GetTabId(dom::WindowGlobalParent
* aWGP
) {
187 // There is no unique TabId for a given WindowGlobalParent, as multiple
188 // WindowGlobalParents share the same PBrowser actor. However, we only
189 // ever queue one paint per PBrowser by just using the current
190 // WindowGlobalParent for a PBrowser. So we can interchange TabId and
191 // WindowGlobalParent when dealing with resolving surfaces.
192 RefPtr
<dom::BrowserParent
> browserParent
= aWGP
->GetBrowserParent();
193 return browserParent
? browserParent
->GetTabId() : dom::TabId(0);
197 bool CrossProcessPaint::Start(dom::WindowGlobalParent
* aRoot
,
198 const dom::DOMRect
* aRect
, float aScale
,
199 nscolor aBackgroundColor
,
200 CrossProcessPaintFlags aFlags
,
201 dom::Promise
* aPromise
) {
202 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
203 aScale
= std::max(aScale
, kMinPaintScale
);
209 "color=(%u, %u, %u, %u)]\n",
210 aRoot
, aScale
, NS_GET_R(aBackgroundColor
), NS_GET_G(aBackgroundColor
),
211 NS_GET_B(aBackgroundColor
), NS_GET_A(aBackgroundColor
));
216 Some(IntRect::RoundOut((float)aRect
->X(), (float)aRect
->Y(),
217 (float)aRect
->Width(), (float)aRect
->Height()));
220 if (rect
&& rect
->IsEmpty()) {
224 dom::TabId rootId
= GetTabId(aRoot
);
226 RefPtr
<CrossProcessPaint
> resolver
=
227 new CrossProcessPaint(aScale
, rootId
, aFlags
);
228 RefPtr
<CrossProcessPaint::ResolvePromise
> promise
;
229 if (aRoot
->IsInProcess()) {
230 RefPtr
<dom::WindowGlobalChild
> childActor
= aRoot
->GetChildActor();
235 // `BrowsingContext()` cannot be nullptr.
236 RefPtr
<dom::BrowsingContext
> bc
= childActor
->BrowsingContext();
238 promise
= resolver
->Init();
239 resolver
->mPendingFragments
+= 1;
240 resolver
->ReceiveFragment(
242 PaintFragment::Record(bc
, rect
, aScale
, aBackgroundColor
, aFlags
));
244 promise
= resolver
->Init();
245 resolver
->QueuePaint(aRoot
, rect
, aBackgroundColor
, aFlags
);
249 GetMainThreadSerialEventTarget(), __func__
,
250 [promise
= RefPtr
{aPromise
}, rootId
](ResolvedFragmentMap
&& aFragments
) {
251 RefPtr
<RecordedDependentSurface
> root
= aFragments
.Get(rootId
);
252 CPP_LOG("Resolved all fragments.\n");
254 // Create the destination draw target
255 RefPtr
<DrawTarget
> drawTarget
=
256 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
257 root
->mSize
, SurfaceFormat::B8G8R8A8
);
258 if (!drawTarget
|| !drawTarget
->IsValid()) {
259 CPP_LOG("Couldn't create (%d x %d) surface for fragment %" PRIu64
261 root
->mSize
.width
, root
->mSize
.height
, (uint64_t)rootId
);
262 promise
->MaybeReject(NS_ERROR_FAILURE
);
266 // Translate the recording using our child tabs
268 InlineTranslator
translator(drawTarget
, nullptr);
269 translator
.SetDependentSurfaces(&aFragments
);
270 if (!translator
.TranslateRecording((char*)root
->mRecording
.mData
,
271 root
->mRecording
.mLen
)) {
272 CPP_LOG("Couldn't translate recording for fragment %" PRIu64
".\n",
274 promise
->MaybeReject(NS_ERROR_FAILURE
);
279 RefPtr
<SourceSurface
> snapshot
= drawTarget
->Snapshot();
281 promise
->MaybeReject(NS_ERROR_FAILURE
);
286 RefPtr
<dom::ImageBitmap
> bitmap
=
287 dom::ImageBitmap::CreateFromSourceSurface(
288 promise
->GetParentObject(), snapshot
, rv
);
291 CPP_LOG("Success, fulfilling promise.\n");
292 promise
->MaybeResolve(bitmap
);
294 CPP_LOG("Couldn't create ImageBitmap for SourceSurface.\n");
295 promise
->MaybeReject(std::move(rv
));
298 [promise
= RefPtr
{aPromise
}](const nsresult
& aRv
) {
299 promise
->MaybeReject(aRv
);
306 RefPtr
<CrossProcessPaint::ResolvePromise
> CrossProcessPaint::Start(
307 nsTHashSet
<uint64_t>&& aDependencies
) {
308 MOZ_ASSERT(!aDependencies
.IsEmpty());
309 RefPtr
<CrossProcessPaint
> resolver
=
310 new CrossProcessPaint(1.0, dom::TabId(0), CrossProcessPaintFlags::None
);
312 RefPtr
<CrossProcessPaint::ResolvePromise
> promise
= resolver
->Init();
314 PaintFragment rootFragment
;
315 rootFragment
.mDependencies
= std::move(aDependencies
);
317 resolver
->QueueDependencies(rootFragment
.mDependencies
);
318 resolver
->mReceivedFragments
.InsertOrUpdate(dom::TabId(0),
319 std::move(rootFragment
));
321 resolver
->MaybeResolve();
326 CrossProcessPaint::CrossProcessPaint(float aScale
, dom::TabId aRoot
,
327 CrossProcessPaintFlags aFlags
)
328 : mRoot
{aRoot
}, mScale
{aScale
}, mPendingFragments
{0}, mFlags
{aFlags
} {}
330 CrossProcessPaint::~CrossProcessPaint() { Clear(NS_ERROR_ABORT
); }
332 void CrossProcessPaint::ReceiveFragment(dom::WindowGlobalParent
* aWGP
,
333 PaintFragment
&& aFragment
) {
335 CPP_LOG("Ignoring fragment from %p.\n", aWGP
);
339 dom::TabId surfaceId
= GetTabId(aWGP
);
341 MOZ_ASSERT(mPendingFragments
> 0);
342 MOZ_ASSERT(!mReceivedFragments
.Contains(surfaceId
));
344 // Double check our invariants to protect against a compromised content
346 if (mPendingFragments
== 0 || mReceivedFragments
.Contains(surfaceId
) ||
347 aFragment
.IsEmpty()) {
348 CPP_LOG("Dropping invalid fragment from %p.\n", aWGP
);
353 CPP_LOG("Receiving fragment from %p(%" PRIu64
").\n", aWGP
,
354 (uint64_t)surfaceId
);
356 // Queue paints for child tabs
357 QueueDependencies(aFragment
.mDependencies
);
359 mReceivedFragments
.InsertOrUpdate(surfaceId
, std::move(aFragment
));
360 mPendingFragments
-= 1;
362 // Resolve this paint if we have received all pending fragments
366 void CrossProcessPaint::LostFragment(dom::WindowGlobalParent
* aWGP
) {
368 CPP_LOG("Ignoring lost fragment from %p.\n", aWGP
);
372 Clear(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA
);
375 void CrossProcessPaint::QueueDependencies(
376 const nsTHashSet
<uint64_t>& aDependencies
) {
377 dom::ContentProcessManager
* cpm
= dom::ContentProcessManager::GetSingleton();
380 "Skipping QueueDependencies with no"
381 " current ContentProcessManager.\n");
384 for (const auto& key
: aDependencies
) {
385 auto dependency
= dom::TabId(key
);
387 // Get the current BrowserParent of the remote browser that was marked
389 dom::ContentParentId cpId
= cpm
->GetTabProcessId(dependency
);
390 RefPtr
<dom::BrowserParent
> browser
=
391 cpm
->GetBrowserParentByProcessAndTabId(cpId
, dependency
);
393 CPP_LOG("Skipping dependency %" PRIu64
394 " with no current BrowserParent.\n",
395 (uint64_t)dependency
);
399 // Note that if the remote document is currently being cloned, it's possible
400 // that the BrowserParent isn't the one for the cloned document, but the
401 // BrowsingContext should be persisted/consistent.
402 QueuePaint(browser
->GetBrowsingContext());
406 void CrossProcessPaint::QueuePaint(dom::WindowGlobalParent
* aWGP
,
407 const Maybe
<IntRect
>& aRect
,
408 nscolor aBackgroundColor
,
409 CrossProcessPaintFlags aFlags
) {
410 MOZ_ASSERT(!mReceivedFragments
.Contains(GetTabId(aWGP
)));
412 CPP_LOG("Queueing paint for WindowGlobalParent(%p).\n", aWGP
);
414 aWGP
->DrawSnapshotInternal(this, aRect
, mScale
, aBackgroundColor
,
416 mPendingFragments
+= 1;
419 void CrossProcessPaint::QueuePaint(dom::CanonicalBrowsingContext
* aBc
) {
420 RefPtr
<GenericNonExclusivePromise
> clonePromise
= aBc
->GetClonePromise();
423 RefPtr
<dom::WindowGlobalParent
> wgp
= aBc
->GetCurrentWindowGlobal();
425 CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n", aBc
);
429 // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
430 QueuePaint(wgp
, Nothing(), NS_RGBA(0, 0, 0, 0), GetFlagsForDependencies());
434 CPP_LOG("Queueing paint for BrowsingContext(%p).\n", aBc
);
435 // In the case it's still in the process of cloning the remote document, we
436 // should defer the snapshot request after the cloning has been finished.
437 mPendingFragments
+= 1;
439 GetMainThreadSerialEventTarget(), __func__
,
440 [self
= RefPtr
{this}, bc
= RefPtr
{aBc
}]() {
441 RefPtr
<dom::WindowGlobalParent
> wgp
= bc
->GetCurrentWindowGlobal();
443 CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n",
447 MOZ_ASSERT(!self
->mReceivedFragments
.Contains(GetTabId(wgp
)));
449 // TODO: Apply some sort of clipping to visible bounds here (Bug
451 wgp
->DrawSnapshotInternal(self
, Nothing(), self
->mScale
,
453 (uint32_t)self
->GetFlagsForDependencies());
455 [self
= RefPtr
{this}]() {
457 "Abort painting for BrowsingContext(%p) because cloning remote "
458 "document failed.\n",
460 self
->Clear(NS_ERROR_FAILURE
);
464 void CrossProcessPaint::Clear(nsresult aStatus
) {
465 mPendingFragments
= 0;
466 mReceivedFragments
.Clear();
467 mPromise
.RejectIfExists(aStatus
, __func__
);
470 bool CrossProcessPaint::IsCleared() const { return mPromise
.IsEmpty(); }
472 void CrossProcessPaint::MaybeResolve() {
473 // Don't do anything if we aren't ready, experienced an error, or already
474 // resolved this paint
475 if (IsCleared() || mPendingFragments
> 0) {
476 CPP_LOG("Not ready to resolve yet, have %u fragments left.\n",
481 CPP_LOG("Starting to resolve fragments.\n");
483 // Resolve the paint fragments from the bottom up
484 ResolvedFragmentMap resolved
;
486 nsresult rv
= ResolveInternal(mRoot
, &resolved
);
488 CPP_LOG("Couldn't resolve.\n");
494 CPP_LOG("Resolved all fragments.\n");
496 mPromise
.ResolveIfExists(std::move(resolved
), __func__
);
500 nsresult
CrossProcessPaint::ResolveInternal(dom::TabId aTabId
,
501 ResolvedFragmentMap
* aResolved
) {
502 // We should not have resolved this paint already
503 MOZ_ASSERT(!aResolved
->GetWeak(aTabId
));
505 CPP_LOG("Resolving fragment %" PRIu64
".\n", (uint64_t)aTabId
);
507 Maybe
<PaintFragment
> fragment
= mReceivedFragments
.Extract(aTabId
);
509 return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA
;
512 // Rasterize all the dependencies first so that we can resolve this fragment
513 for (const auto& key
: fragment
->mDependencies
) {
514 auto dependency
= dom::TabId(key
);
516 nsresult rv
= ResolveInternal(dependency
, aResolved
);
522 RefPtr
<RecordedDependentSurface
> surface
= new RecordedDependentSurface
{
523 fragment
->mSize
, std::move(fragment
->mRecording
)};
524 aResolved
->InsertOrUpdate(aTabId
, std::move(surface
));
529 } // namespace mozilla