summaryrefslogtreecommitdiffstats
path: root/gfx/ipc/CrossProcessPaint.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/ipc/CrossProcessPaint.cpp
parentInitial commit. (diff)
downloadfirefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.tar.xz
firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/ipc/CrossProcessPaint.cpp')
-rw-r--r--gfx/ipc/CrossProcessPaint.cpp529
1 files changed, 529 insertions, 0 deletions
diff --git a/gfx/ipc/CrossProcessPaint.cpp b/gfx/ipc/CrossProcessPaint.cpp
new file mode 100644
index 0000000000..e8a2f4474e
--- /dev/null
+++ b/gfx/ipc/CrossProcessPaint.cpp
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CrossProcessPaint.h"
+
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/PWindowGlobalParent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/gfx/InlineTranslator.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+
+#include "gfxPlatform.h"
+
+#include "nsContentUtils.h"
+#include "nsIDocShell.h"
+#include "nsPresContext.h"
+
+static mozilla::LazyLogModule gCrossProcessPaintLog("CrossProcessPaint");
+static mozilla::LazyLogModule gPaintFragmentLog("PaintFragment");
+
+#define CPP_LOG(msg, ...) \
+ MOZ_LOG(gCrossProcessPaintLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+#define PF_LOG(msg, ...) \
+ MOZ_LOG(gPaintFragmentLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla::ipc;
+
+/// The minimum scale we allow tabs to be rasterized at.
+static const float kMinPaintScale = 0.05f;
+
+/* static */
+PaintFragment PaintFragment::Record(dom::BrowsingContext* aBc,
+ const Maybe<IntRect>& aRect, float aScale,
+ nscolor aBackgroundColor,
+ CrossProcessPaintFlags aFlags) {
+ nsIDocShell* ds = aBc->GetDocShell();
+ if (!ds) {
+ PF_LOG("Couldn't find docshell.\n");
+ return PaintFragment{};
+ }
+
+ RefPtr<nsPresContext> presContext = ds->GetPresContext();
+ if (!presContext) {
+ PF_LOG("Couldn't find PresContext.\n");
+ return PaintFragment{};
+ }
+
+ CSSIntRect rect;
+ if (!aRect) {
+ nsCOMPtr<nsIWidget> widget =
+ nsContentUtils::WidgetForDocument(presContext->Document());
+
+ // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
+ LayoutDeviceIntRect boundsDevice = widget->GetBounds();
+ boundsDevice.MoveTo(0, 0);
+ nsRect boundsAu = LayoutDevicePixel::ToAppUnits(
+ boundsDevice, presContext->AppUnitsPerDevPixel());
+ rect = gfx::RoundedOut(CSSPixel::FromAppUnits(boundsAu));
+ } else {
+ rect = CSSIntRect::FromUnknownRect(*aRect);
+ }
+
+ if (rect.IsEmpty()) {
+ // TODO: Should we return an empty surface here?
+ PF_LOG("Empty rect to paint.\n");
+ return PaintFragment{};
+ }
+
+ // FIXME: Shouldn't the surface size be in device rather than CSS pixels?
+ CSSIntSize surfaceSize = rect.Size();
+ surfaceSize.width *= aScale;
+ surfaceSize.height *= aScale;
+
+ CPP_LOG(
+ "Recording "
+ "[browsingContext=%p, "
+ "rect=(%d, %d) x (%d, %d), "
+ "scale=%f, "
+ "color=(%u, %u, %u, %u)]\n",
+ aBc, rect.x, rect.y, rect.width, rect.height, aScale,
+ NS_GET_R(aBackgroundColor), NS_GET_G(aBackgroundColor),
+ NS_GET_B(aBackgroundColor), NS_GET_A(aBackgroundColor));
+
+ // Check for invalid sizes
+ if (surfaceSize.width <= 0 || surfaceSize.height <= 0 ||
+ !Factory::CheckSurfaceSize(surfaceSize.ToUnknownSize())) {
+ PF_LOG("Invalid surface size of (%d x %d).\n", surfaceSize.width,
+ surfaceSize.height);
+ return PaintFragment{};
+ }
+
+ // Flush any pending notifications
+ nsContentUtils::FlushLayoutForTree(ds->GetWindow());
+
+ // Initialize the recorder
+ SurfaceFormat format = SurfaceFormat::B8G8R8A8;
+ RefPtr<DrawTarget> referenceDt = Factory::CreateDrawTarget(
+ gfxPlatform::GetPlatform()->GetSoftwareBackend(), IntSize(1, 1), format);
+
+ // TODO: This may OOM crash if the content is complex enough
+ RefPtr<DrawEventRecorderMemory> recorder =
+ MakeAndAddRef<DrawEventRecorderMemory>(nullptr);
+ RefPtr<DrawTarget> dt = Factory::CreateRecordingDrawTarget(
+ recorder, referenceDt,
+ IntRect(IntPoint(0, 0), surfaceSize.ToUnknownSize()));
+ if (!dt || !dt->IsValid()) {
+ PF_LOG("Failed to create drawTarget.\n");
+ return PaintFragment{};
+ }
+
+ RenderDocumentFlags renderDocFlags = RenderDocumentFlags::None;
+ if (!(aFlags & CrossProcessPaintFlags::DrawView)) {
+ renderDocFlags |= RenderDocumentFlags::IgnoreViewportScrolling |
+ RenderDocumentFlags::DocumentRelative;
+ if (aFlags & CrossProcessPaintFlags::ResetScrollPosition) {
+ renderDocFlags |= RenderDocumentFlags::ResetViewportScrolling;
+ }
+ }
+ if (aFlags & CrossProcessPaintFlags::UseHighQualityScaling) {
+ renderDocFlags |= RenderDocumentFlags::UseHighQualityScaling;
+ }
+
+ // Perform the actual rendering
+ {
+ nsRect r = CSSPixel::ToAppUnits(rect);
+
+ // This matches what nsDeviceContext::CreateRenderingContext does.
+ if (presContext->IsPrintingOrPrintPreview()) {
+ dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
+ }
+
+ gfxContext thebes(dt);
+ thebes.SetMatrix(Matrix::Scaling(aScale, aScale));
+ thebes.SetCrossProcessPaintScale(aScale);
+ RefPtr<PresShell> presShell = presContext->PresShell();
+ Unused << presShell->RenderDocument(r, renderDocFlags, aBackgroundColor,
+ &thebes);
+ }
+
+ if (!recorder->mOutputStream.mValid) {
+ recorder->DetachResources();
+ return PaintFragment{};
+ }
+
+ ByteBuf recording = ByteBuf((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength,
+ recorder->mOutputStream.mCapacity);
+ recorder->mOutputStream.mData = nullptr;
+ recorder->mOutputStream.mLength = 0;
+ recorder->mOutputStream.mCapacity = 0;
+
+ PaintFragment fragment{
+ surfaceSize.ToUnknownSize(),
+ std::move(recording),
+ std::move(recorder->TakeDependentSurfaces()),
+ };
+
+ recorder->DetachResources();
+ return fragment;
+}
+
+bool PaintFragment::IsEmpty() const {
+ return !mRecording.mData || mRecording.mLen == 0 || mSize == IntSize(0, 0);
+}
+
+PaintFragment::PaintFragment(IntSize aSize, ByteBuf&& aRecording,
+ nsTHashSet<uint64_t>&& aDependencies)
+ : mSize(aSize),
+ mRecording(std::move(aRecording)),
+ mDependencies(std::move(aDependencies)) {}
+
+static dom::TabId GetTabId(dom::WindowGlobalParent* aWGP) {
+ // There is no unique TabId for a given WindowGlobalParent, as multiple
+ // WindowGlobalParents share the same PBrowser actor. However, we only
+ // ever queue one paint per PBrowser by just using the current
+ // WindowGlobalParent for a PBrowser. So we can interchange TabId and
+ // WindowGlobalParent when dealing with resolving surfaces.
+ RefPtr<dom::BrowserParent> browserParent = aWGP->GetBrowserParent();
+ return browserParent ? browserParent->GetTabId() : dom::TabId(0);
+}
+
+/* static */
+bool CrossProcessPaint::Start(dom::WindowGlobalParent* aRoot,
+ const dom::DOMRect* aRect, float aScale,
+ nscolor aBackgroundColor,
+ CrossProcessPaintFlags aFlags,
+ dom::Promise* aPromise) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ aScale = std::max(aScale, kMinPaintScale);
+
+ CPP_LOG(
+ "Starting paint. "
+ "[wgp=%p, "
+ "scale=%f, "
+ "color=(%u, %u, %u, %u)]\n",
+ aRoot, aScale, NS_GET_R(aBackgroundColor), NS_GET_G(aBackgroundColor),
+ NS_GET_B(aBackgroundColor), NS_GET_A(aBackgroundColor));
+
+ Maybe<IntRect> rect;
+ if (aRect) {
+ rect =
+ Some(IntRect::RoundOut((float)aRect->X(), (float)aRect->Y(),
+ (float)aRect->Width(), (float)aRect->Height()));
+ }
+
+ if (rect && rect->IsEmpty()) {
+ return false;
+ }
+
+ dom::TabId rootId = GetTabId(aRoot);
+
+ RefPtr<CrossProcessPaint> resolver =
+ new CrossProcessPaint(aScale, rootId, aFlags);
+ RefPtr<CrossProcessPaint::ResolvePromise> promise;
+ if (aRoot->IsInProcess()) {
+ RefPtr<dom::WindowGlobalChild> childActor = aRoot->GetChildActor();
+ if (!childActor) {
+ return false;
+ }
+
+ // `BrowsingContext()` cannot be nullptr.
+ RefPtr<dom::BrowsingContext> bc = childActor->BrowsingContext();
+
+ promise = resolver->Init();
+ resolver->mPendingFragments += 1;
+ resolver->ReceiveFragment(
+ aRoot,
+ PaintFragment::Record(bc, rect, aScale, aBackgroundColor, aFlags));
+ } else {
+ promise = resolver->Init();
+ resolver->QueuePaint(aRoot, rect, aBackgroundColor, aFlags);
+ }
+
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise = RefPtr{aPromise}, rootId](ResolvedFragmentMap&& aFragments) {
+ RefPtr<RecordedDependentSurface> root = aFragments.Get(rootId);
+ CPP_LOG("Resolved all fragments.\n");
+
+ // Create the destination draw target
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ root->mSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget || !drawTarget->IsValid()) {
+ CPP_LOG("Couldn't create (%d x %d) surface for fragment %" PRIu64
+ ".\n",
+ root->mSize.width, root->mSize.height, (uint64_t)rootId);
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Translate the recording using our child tabs
+ {
+ InlineTranslator translator(drawTarget, nullptr);
+ translator.SetDependentSurfaces(&aFragments);
+ if (!translator.TranslateRecording((char*)root->mRecording.mData,
+ root->mRecording.mLen)) {
+ CPP_LOG("Couldn't translate recording for fragment %" PRIu64 ".\n",
+ (uint64_t)rootId);
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
+ if (!snapshot) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ ErrorResult rv;
+ RefPtr<dom::ImageBitmap> bitmap =
+ dom::ImageBitmap::CreateFromSourceSurface(
+ promise->GetParentObject(), snapshot, rv);
+
+ if (!rv.Failed()) {
+ CPP_LOG("Success, fulfilling promise.\n");
+ promise->MaybeResolve(bitmap);
+ } else {
+ CPP_LOG("Couldn't create ImageBitmap for SourceSurface.\n");
+ promise->MaybeReject(std::move(rv));
+ }
+ },
+ [promise = RefPtr{aPromise}](const nsresult& aRv) {
+ promise->MaybeReject(aRv);
+ });
+
+ return true;
+}
+
+/* static */
+RefPtr<CrossProcessPaint::ResolvePromise> CrossProcessPaint::Start(
+ nsTHashSet<uint64_t>&& aDependencies) {
+ MOZ_ASSERT(!aDependencies.IsEmpty());
+ RefPtr<CrossProcessPaint> resolver =
+ new CrossProcessPaint(1.0, dom::TabId(0), CrossProcessPaintFlags::None);
+
+ RefPtr<CrossProcessPaint::ResolvePromise> promise = resolver->Init();
+
+ PaintFragment rootFragment;
+ rootFragment.mDependencies = std::move(aDependencies);
+
+ resolver->QueueDependencies(rootFragment.mDependencies);
+ resolver->mReceivedFragments.InsertOrUpdate(dom::TabId(0),
+ std::move(rootFragment));
+
+ resolver->MaybeResolve();
+
+ return promise;
+}
+
+CrossProcessPaint::CrossProcessPaint(float aScale, dom::TabId aRoot,
+ CrossProcessPaintFlags aFlags)
+ : mRoot{aRoot}, mScale{aScale}, mPendingFragments{0}, mFlags{aFlags} {}
+
+CrossProcessPaint::~CrossProcessPaint() { Clear(NS_ERROR_ABORT); }
+
+void CrossProcessPaint::ReceiveFragment(dom::WindowGlobalParent* aWGP,
+ PaintFragment&& aFragment) {
+ if (IsCleared()) {
+ CPP_LOG("Ignoring fragment from %p.\n", aWGP);
+ return;
+ }
+
+ dom::TabId surfaceId = GetTabId(aWGP);
+
+ MOZ_ASSERT(mPendingFragments > 0);
+ MOZ_ASSERT(!mReceivedFragments.Contains(surfaceId));
+
+ // Double check our invariants to protect against a compromised content
+ // process
+ if (mPendingFragments == 0 || mReceivedFragments.Contains(surfaceId) ||
+ aFragment.IsEmpty()) {
+ CPP_LOG("Dropping invalid fragment from %p.\n", aWGP);
+ LostFragment(aWGP);
+ return;
+ }
+
+ CPP_LOG("Receiving fragment from %p(%" PRIu64 ").\n", aWGP,
+ (uint64_t)surfaceId);
+
+ // Queue paints for child tabs
+ QueueDependencies(aFragment.mDependencies);
+
+ mReceivedFragments.InsertOrUpdate(surfaceId, std::move(aFragment));
+ mPendingFragments -= 1;
+
+ // Resolve this paint if we have received all pending fragments
+ MaybeResolve();
+}
+
+void CrossProcessPaint::LostFragment(dom::WindowGlobalParent* aWGP) {
+ if (IsCleared()) {
+ CPP_LOG("Ignoring lost fragment from %p.\n", aWGP);
+ return;
+ }
+
+ Clear(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+}
+
+void CrossProcessPaint::QueueDependencies(
+ const nsTHashSet<uint64_t>& aDependencies) {
+ dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton();
+ if (!cpm) {
+ CPP_LOG(
+ "Skipping QueueDependencies with no"
+ " current ContentProcessManager.\n");
+ return;
+ }
+ for (const auto& key : aDependencies) {
+ auto dependency = dom::TabId(key);
+
+ // Get the current BrowserParent of the remote browser that was marked
+ // as a dependency
+ dom::ContentParentId cpId = cpm->GetTabProcessId(dependency);
+ RefPtr<dom::BrowserParent> browser =
+ cpm->GetBrowserParentByProcessAndTabId(cpId, dependency);
+ if (!browser) {
+ CPP_LOG("Skipping dependency %" PRIu64
+ " with no current BrowserParent.\n",
+ (uint64_t)dependency);
+ continue;
+ }
+
+ // Note that if the remote document is currently being cloned, it's possible
+ // that the BrowserParent isn't the one for the cloned document, but the
+ // BrowsingContext should be persisted/consistent.
+ QueuePaint(browser->GetBrowsingContext());
+ }
+}
+
+void CrossProcessPaint::QueuePaint(dom::WindowGlobalParent* aWGP,
+ const Maybe<IntRect>& aRect,
+ nscolor aBackgroundColor,
+ CrossProcessPaintFlags aFlags) {
+ MOZ_ASSERT(!mReceivedFragments.Contains(GetTabId(aWGP)));
+
+ CPP_LOG("Queueing paint for WindowGlobalParent(%p).\n", aWGP);
+
+ aWGP->DrawSnapshotInternal(this, aRect, mScale, aBackgroundColor,
+ (uint32_t)aFlags);
+ mPendingFragments += 1;
+}
+
+void CrossProcessPaint::QueuePaint(dom::CanonicalBrowsingContext* aBc) {
+ RefPtr<GenericNonExclusivePromise> clonePromise = aBc->GetClonePromise();
+
+ if (!clonePromise) {
+ RefPtr<dom::WindowGlobalParent> wgp = aBc->GetCurrentWindowGlobal();
+ if (!wgp) {
+ CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n", aBc);
+ return;
+ }
+
+ // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
+ QueuePaint(wgp, Nothing(), NS_RGBA(0, 0, 0, 0), GetFlagsForDependencies());
+ return;
+ }
+
+ CPP_LOG("Queueing paint for BrowsingContext(%p).\n", aBc);
+ // In the case it's still in the process of cloning the remote document, we
+ // should defer the snapshot request after the cloning has been finished.
+ mPendingFragments += 1;
+ clonePromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, bc = RefPtr{aBc}]() {
+ RefPtr<dom::WindowGlobalParent> wgp = bc->GetCurrentWindowGlobal();
+ if (!wgp) {
+ CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n",
+ bc.get());
+ return;
+ }
+ MOZ_ASSERT(!self->mReceivedFragments.Contains(GetTabId(wgp)));
+
+ // TODO: Apply some sort of clipping to visible bounds here (Bug
+ // 1562720)
+ wgp->DrawSnapshotInternal(self, Nothing(), self->mScale,
+ NS_RGBA(0, 0, 0, 0),
+ (uint32_t)self->GetFlagsForDependencies());
+ },
+ [self = RefPtr{this}]() {
+ CPP_LOG(
+ "Abort painting for BrowsingContext(%p) because cloning remote "
+ "document failed.\n",
+ self.get());
+ self->Clear(NS_ERROR_FAILURE);
+ });
+}
+
+void CrossProcessPaint::Clear(nsresult aStatus) {
+ mPendingFragments = 0;
+ mReceivedFragments.Clear();
+ mPromise.RejectIfExists(aStatus, __func__);
+}
+
+bool CrossProcessPaint::IsCleared() const { return mPromise.IsEmpty(); }
+
+void CrossProcessPaint::MaybeResolve() {
+ // Don't do anything if we aren't ready, experienced an error, or already
+ // resolved this paint
+ if (IsCleared() || mPendingFragments > 0) {
+ CPP_LOG("Not ready to resolve yet, have %u fragments left.\n",
+ mPendingFragments);
+ return;
+ }
+
+ CPP_LOG("Starting to resolve fragments.\n");
+
+ // Resolve the paint fragments from the bottom up
+ ResolvedFragmentMap resolved;
+ {
+ nsresult rv = ResolveInternal(mRoot, &resolved);
+ if (NS_FAILED(rv)) {
+ CPP_LOG("Couldn't resolve.\n");
+ Clear(rv);
+ return;
+ }
+ }
+
+ CPP_LOG("Resolved all fragments.\n");
+
+ mPromise.ResolveIfExists(std::move(resolved), __func__);
+ Clear(NS_OK);
+}
+
+nsresult CrossProcessPaint::ResolveInternal(dom::TabId aTabId,
+ ResolvedFragmentMap* aResolved) {
+ // We should not have resolved this paint already
+ MOZ_ASSERT(!aResolved->GetWeak(aTabId));
+
+ CPP_LOG("Resolving fragment %" PRIu64 ".\n", (uint64_t)aTabId);
+
+ Maybe<PaintFragment> fragment = mReceivedFragments.Extract(aTabId);
+ if (!fragment) {
+ return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+
+ // Rasterize all the dependencies first so that we can resolve this fragment
+ for (const auto& key : fragment->mDependencies) {
+ auto dependency = dom::TabId(key);
+
+ nsresult rv = ResolveInternal(dependency, aResolved);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<RecordedDependentSurface> surface = new RecordedDependentSurface{
+ fragment->mSize, std::move(fragment->mRecording)};
+ aResolved->InsertOrUpdate(aTabId, std::move(surface));
+ return NS_OK;
+}
+
+} // namespace gfx
+} // namespace mozilla