Bug 1822393 - Consume GeckoView directly in Android Components for CI builds. r=owlis...
[gecko.git] / gfx / ipc / CrossProcessPaint.cpp
blobe8a2f4474ef06477c517346139c4ddd2d63fd8e0
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__))
37 namespace mozilla {
38 namespace gfx {
40 using namespace mozilla::ipc;
42 /// The minimum scale we allow tabs to be rasterized at.
43 static const float kMinPaintScale = 0.05f;
45 /* static */
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();
51 if (!ds) {
52 PF_LOG("Couldn't find docshell.\n");
53 return PaintFragment{};
56 RefPtr<nsPresContext> presContext = ds->GetPresContext();
57 if (!presContext) {
58 PF_LOG("Couldn't find PresContext.\n");
59 return PaintFragment{};
62 CSSIntRect rect;
63 if (!aRect) {
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));
73 } else {
74 rect = CSSIntRect::FromUnknownRect(*aRect);
77 if (rect.IsEmpty()) {
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;
88 CPP_LOG(
89 "Recording "
90 "[browsingContext=%p, "
91 "rect=(%d, %d) x (%d, %d), "
92 "scale=%f, "
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,
102 surfaceSize.height);
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,
151 &thebes);
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();
173 return fragment;
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)
182 : mSize(aSize),
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);
196 /* static */
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);
205 CPP_LOG(
206 "Starting paint. "
207 "[wgp=%p, "
208 "scale=%f, "
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));
213 Maybe<IntRect> rect;
214 if (aRect) {
215 rect =
216 Some(IntRect::RoundOut((float)aRect->X(), (float)aRect->Y(),
217 (float)aRect->Width(), (float)aRect->Height()));
220 if (rect && rect->IsEmpty()) {
221 return false;
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();
231 if (!childActor) {
232 return false;
235 // `BrowsingContext()` cannot be nullptr.
236 RefPtr<dom::BrowsingContext> bc = childActor->BrowsingContext();
238 promise = resolver->Init();
239 resolver->mPendingFragments += 1;
240 resolver->ReceiveFragment(
241 aRoot,
242 PaintFragment::Record(bc, rect, aScale, aBackgroundColor, aFlags));
243 } else {
244 promise = resolver->Init();
245 resolver->QueuePaint(aRoot, rect, aBackgroundColor, aFlags);
248 promise->Then(
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
260 ".\n",
261 root->mSize.width, root->mSize.height, (uint64_t)rootId);
262 promise->MaybeReject(NS_ERROR_FAILURE);
263 return;
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",
273 (uint64_t)rootId);
274 promise->MaybeReject(NS_ERROR_FAILURE);
275 return;
279 RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
280 if (!snapshot) {
281 promise->MaybeReject(NS_ERROR_FAILURE);
282 return;
285 ErrorResult rv;
286 RefPtr<dom::ImageBitmap> bitmap =
287 dom::ImageBitmap::CreateFromSourceSurface(
288 promise->GetParentObject(), snapshot, rv);
290 if (!rv.Failed()) {
291 CPP_LOG("Success, fulfilling promise.\n");
292 promise->MaybeResolve(bitmap);
293 } else {
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);
302 return true;
305 /* static */
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();
323 return promise;
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) {
334 if (IsCleared()) {
335 CPP_LOG("Ignoring fragment from %p.\n", aWGP);
336 return;
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
345 // process
346 if (mPendingFragments == 0 || mReceivedFragments.Contains(surfaceId) ||
347 aFragment.IsEmpty()) {
348 CPP_LOG("Dropping invalid fragment from %p.\n", aWGP);
349 LostFragment(aWGP);
350 return;
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
363 MaybeResolve();
366 void CrossProcessPaint::LostFragment(dom::WindowGlobalParent* aWGP) {
367 if (IsCleared()) {
368 CPP_LOG("Ignoring lost fragment from %p.\n", aWGP);
369 return;
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();
378 if (!cpm) {
379 CPP_LOG(
380 "Skipping QueueDependencies with no"
381 " current ContentProcessManager.\n");
382 return;
384 for (const auto& key : aDependencies) {
385 auto dependency = dom::TabId(key);
387 // Get the current BrowserParent of the remote browser that was marked
388 // as a dependency
389 dom::ContentParentId cpId = cpm->GetTabProcessId(dependency);
390 RefPtr<dom::BrowserParent> browser =
391 cpm->GetBrowserParentByProcessAndTabId(cpId, dependency);
392 if (!browser) {
393 CPP_LOG("Skipping dependency %" PRIu64
394 " with no current BrowserParent.\n",
395 (uint64_t)dependency);
396 continue;
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,
415 (uint32_t)aFlags);
416 mPendingFragments += 1;
419 void CrossProcessPaint::QueuePaint(dom::CanonicalBrowsingContext* aBc) {
420 RefPtr<GenericNonExclusivePromise> clonePromise = aBc->GetClonePromise();
422 if (!clonePromise) {
423 RefPtr<dom::WindowGlobalParent> wgp = aBc->GetCurrentWindowGlobal();
424 if (!wgp) {
425 CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n", aBc);
426 return;
429 // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
430 QueuePaint(wgp, Nothing(), NS_RGBA(0, 0, 0, 0), GetFlagsForDependencies());
431 return;
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;
438 clonePromise->Then(
439 GetMainThreadSerialEventTarget(), __func__,
440 [self = RefPtr{this}, bc = RefPtr{aBc}]() {
441 RefPtr<dom::WindowGlobalParent> wgp = bc->GetCurrentWindowGlobal();
442 if (!wgp) {
443 CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n",
444 bc.get());
445 return;
447 MOZ_ASSERT(!self->mReceivedFragments.Contains(GetTabId(wgp)));
449 // TODO: Apply some sort of clipping to visible bounds here (Bug
450 // 1562720)
451 wgp->DrawSnapshotInternal(self, Nothing(), self->mScale,
452 NS_RGBA(0, 0, 0, 0),
453 (uint32_t)self->GetFlagsForDependencies());
455 [self = RefPtr{this}]() {
456 CPP_LOG(
457 "Abort painting for BrowsingContext(%p) because cloning remote "
458 "document failed.\n",
459 self.get());
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",
477 mPendingFragments);
478 return;
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);
487 if (NS_FAILED(rv)) {
488 CPP_LOG("Couldn't resolve.\n");
489 Clear(rv);
490 return;
494 CPP_LOG("Resolved all fragments.\n");
496 mPromise.ResolveIfExists(std::move(resolved), __func__);
497 Clear(NS_OK);
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);
508 if (!fragment) {
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);
517 if (NS_FAILED(rv)) {
518 return rv;
522 RefPtr<RecordedDependentSurface> surface = new RecordedDependentSurface{
523 fragment->mSize, std::move(fragment->mRecording)};
524 aResolved->InsertOrUpdate(aTabId, std::move(surface));
525 return NS_OK;
528 } // namespace gfx
529 } // namespace mozilla