diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/ipc/CrossProcessPaint.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/ipc/CrossProcessPaint.cpp')
-rw-r--r-- | gfx/ipc/CrossProcessPaint.cpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/gfx/ipc/CrossProcessPaint.cpp b/gfx/ipc/CrossProcessPaint.cpp new file mode 100644 index 0000000000..143cd882d4 --- /dev/null +++ b/gfx/ipc/CrossProcessPaint.cpp @@ -0,0 +1,456 @@ +/* -*- 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/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 "nsGlobalWindowInner.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{}; + } + + IntRect 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).ToUnknownRect()); + } else { + rect = *aRect; + } + + if (rect.IsEmpty()) { + // TODO: Should we return an empty surface here? + PF_LOG("Empty rect to paint.\n"); + return PaintFragment{}; + } + + IntSize 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)) { + 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)); + + RenderDocumentFlags renderDocFlags = RenderDocumentFlags::None; + if (!(aFlags & CrossProcessPaintFlags::DrawView)) { + renderDocFlags = (RenderDocumentFlags::IgnoreViewportScrolling | + RenderDocumentFlags::DocumentRelative); + } + + // Perform the actual rendering + { + nsRect r(nsPresContext::CSSPixelsToAppUnits(rect.x), + nsPresContext::CSSPixelsToAppUnits(rect.y), + nsPresContext::CSSPixelsToAppUnits(rect.width), + nsPresContext::CSSPixelsToAppUnits(rect.height)); + + RefPtr<gfxContext> thebes = gfxContext::CreateOrNull(dt); + thebes->SetMatrix(Matrix::Scaling(aScale, aScale)); + RefPtr<PresShell> presShell = presContext->PresShell(); + Unused << presShell->RenderDocument(r, renderDocFlags, aBackgroundColor, + thebes); + } + + if (!recorder->mOutputStream.mValid) { + 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; + + return PaintFragment{ + surfaceSize, + std::move(recording), + std::move(recorder->TakeDependentSurfaces()), + }; +} + +bool PaintFragment::IsEmpty() const { + return !mRecording.mData || mRecording.mLen == 0 || mSize == IntSize(0, 0); +} + +PaintFragment::PaintFragment(IntSize aSize, ByteBuf&& aRecording, + nsTHashtable<nsUint64HashKey>&& 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); + 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( + GetCurrentSerialEventTarget(), __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( + nsTHashtable<nsUint64HashKey>&& aDependencies) { + MOZ_ASSERT(!aDependencies.IsEmpty()); + RefPtr<CrossProcessPaint> resolver = + new CrossProcessPaint(1.0, dom::TabId(0)); + + RefPtr<CrossProcessPaint::ResolvePromise> promise = resolver->Init(); + + PaintFragment rootFragment; + rootFragment.mDependencies = std::move(aDependencies); + + resolver->QueueDependencies(rootFragment.mDependencies); + resolver->mReceivedFragments.Put(dom::TabId(0), std::move(rootFragment)); + + return promise; +} + +CrossProcessPaint::CrossProcessPaint(float aScale, dom::TabId aRoot) + : mRoot{aRoot}, mScale{aScale}, mPendingFragments{0} {} + +CrossProcessPaint::~CrossProcessPaint() = default; + +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.GetValue(surfaceId)); + + // Double check our invariants to protect against a compromised content + // process + if (mPendingFragments == 0 || mReceivedFragments.GetValue(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.Put(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 nsTHashtable<nsUint64HashKey>& aDependencies) { + for (auto iter = aDependencies.ConstIter(); !iter.Done(); iter.Next()) { + auto dependency = dom::TabId(iter.Get()->GetKey()); + + // Get the current WindowGlobalParent of the remote browser that was marked + // as a dependency + dom::ContentProcessManager* cpm = + dom::ContentProcessManager::GetSingleton(); + dom::ContentParentId cpId = cpm->GetTabProcessId(dependency); + RefPtr<dom::BrowserParent> browser = + cpm->GetBrowserParentByProcessAndTabId(cpId, dependency); + RefPtr<dom::WindowGlobalParent> wgp = + browser->GetBrowsingContext()->GetCurrentWindowGlobal(); + + if (!wgp) { + CPP_LOG("Skipping dependency %" PRIu64 "with no current WGP.\n", + (uint64_t)dependency); + continue; + } + + // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720) + QueuePaint(wgp, Nothing()); + } +} + +void CrossProcessPaint::QueuePaint(dom::WindowGlobalParent* aWGP, + const Maybe<IntRect>& aRect, + nscolor aBackgroundColor, + CrossProcessPaintFlags aFlags) { + MOZ_ASSERT(!mReceivedFragments.GetValue(GetTabId(aWGP))); + + CPP_LOG("Queueing paint for %p.\n", aWGP); + + aWGP->DrawSnapshotInternal(this, aRect, mScale, aBackgroundColor, + (uint32_t)aFlags); + mPendingFragments += 1; +} + +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.GetAndRemove(aTabId); + if (!fragment) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + // Rasterize all the dependencies first so that we can resolve this fragment + for (auto iter = fragment->mDependencies.Iter(); !iter.Done(); iter.Next()) { + auto dependency = dom::TabId(iter.Get()->GetKey()); + + nsresult rv = ResolveInternal(dependency, aResolved); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<RecordedDependentSurface> surface = new RecordedDependentSurface{ + fragment->mSize, std::move(fragment->mRecording)}; + aResolved->Put(aTabId, std::move(surface)); + return NS_OK; +} + +} // namespace gfx +} // namespace mozilla |