tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CrossProcessPaint.cpp (18251B)


      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/. */
      6 
      7 #include "CrossProcessPaint.h"
      8 
      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/gfx/RecordedEvent.h"
     21 #include "mozilla/Logging.h"
     22 #include "mozilla/PresShell.h"
     23 
     24 #include "gfxPlatform.h"
     25 
     26 #include "nsContentUtils.h"
     27 #include "nsIDocShell.h"
     28 #include "nsPresContext.h"
     29 
     30 static mozilla::LazyLogModule gCrossProcessPaintLog("CrossProcessPaint");
     31 static mozilla::LazyLogModule gPaintFragmentLog("PaintFragment");
     32 
     33 #define CPP_LOG(msg, ...) \
     34  MOZ_LOG(gCrossProcessPaintLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
     35 #define PF_LOG(msg, ...) \
     36  MOZ_LOG(gPaintFragmentLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
     37 
     38 namespace mozilla {
     39 namespace gfx {
     40 
     41 using namespace mozilla::ipc;
     42 
     43 /// The minimum scale we allow tabs to be rasterized at.
     44 static const float kMinPaintScale = 0.05f;
     45 
     46 /* static */
     47 PaintFragment PaintFragment::Record(dom::BrowsingContext* aBc,
     48                                    const Maybe<IntRect>& aRect, float aScale,
     49                                    nscolor aBackgroundColor,
     50                                    CrossProcessPaintFlags aFlags) {
     51  nsIDocShell* ds = aBc->GetDocShell();
     52  if (!ds) {
     53    PF_LOG("Couldn't find docshell.\n");
     54    return PaintFragment{};
     55  }
     56 
     57  RefPtr<nsPresContext> presContext = ds->GetPresContext();
     58  if (!presContext) {
     59    PF_LOG("Couldn't find PresContext.\n");
     60    return PaintFragment{};
     61  }
     62 
     63  CSSIntRect rect;
     64  if (!aRect) {
     65    nsCOMPtr<nsIWidget> widget =
     66        nsContentUtils::WidgetForDocument(presContext->Document());
     67 
     68    // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
     69    LayoutDeviceIntRect boundsDevice = widget->GetBounds();
     70    boundsDevice.MoveTo(0, 0);
     71    nsRect boundsAu = LayoutDevicePixel::ToAppUnits(
     72        boundsDevice, presContext->AppUnitsPerDevPixel());
     73    rect = gfx::RoundedOut(CSSPixel::FromAppUnits(boundsAu));
     74  } else {
     75    rect = CSSIntRect::FromUnknownRect(*aRect);
     76  }
     77 
     78  if (rect.IsEmpty()) {
     79    // TODO: Should we return an empty surface here?
     80    PF_LOG("Empty rect to paint.\n");
     81    return PaintFragment{};
     82  }
     83 
     84  // FIXME: Shouldn't the surface size be in device rather than CSS pixels?
     85  CSSIntSize surfaceSize = rect.Size();
     86  surfaceSize.width *= aScale;
     87  surfaceSize.height *= aScale;
     88 
     89  CPP_LOG(
     90      "Recording "
     91      "[browsingContext=%p, "
     92      "rect=(%d, %d) x (%d, %d), "
     93      "scale=%f, "
     94      "color=(%u, %u, %u, %u)]\n",
     95      aBc, rect.x, rect.y, rect.width, rect.height, aScale,
     96      NS_GET_R(aBackgroundColor), NS_GET_G(aBackgroundColor),
     97      NS_GET_B(aBackgroundColor), NS_GET_A(aBackgroundColor));
     98 
     99  // Check for invalid sizes
    100  if (surfaceSize.width <= 0 || surfaceSize.height <= 0 ||
    101      !Factory::CheckSurfaceSize(surfaceSize.ToUnknownSize())) {
    102    PF_LOG("Invalid surface size of (%d x %d).\n", surfaceSize.width,
    103           surfaceSize.height);
    104    return PaintFragment{};
    105  }
    106 
    107  // Flush any pending notifications
    108  nsContentUtils::FlushLayoutForTree(ds->GetWindow());
    109 
    110  // Initialize the recorder
    111  SurfaceFormat format = SurfaceFormat::B8G8R8A8;
    112  RefPtr<DrawTarget> referenceDt = Factory::CreateDrawTarget(
    113      gfxPlatform::GetPlatform()->GetSoftwareBackend(), IntSize(1, 1), format);
    114 
    115  // TODO: This may OOM crash if the content is complex enough
    116  RefPtr<DrawEventRecorderMemory> recorder =
    117      MakeAndAddRef<DrawEventRecorderMemory>(nullptr);
    118  RefPtr<DrawTarget> dt = Factory::CreateRecordingDrawTarget(
    119      recorder, referenceDt,
    120      IntRect(IntPoint(0, 0), surfaceSize.ToUnknownSize()));
    121  if (!dt || !dt->IsValid()) {
    122    PF_LOG("Failed to create drawTarget.\n");
    123    return PaintFragment{};
    124  }
    125 
    126  RenderDocumentFlags renderDocFlags = RenderDocumentFlags::None;
    127  if (!(aFlags & CrossProcessPaintFlags::DrawView)) {
    128    renderDocFlags |= RenderDocumentFlags::IgnoreViewportScrolling |
    129                      RenderDocumentFlags::DocumentRelative;
    130    if (aFlags & CrossProcessPaintFlags::ResetScrollPosition) {
    131      renderDocFlags |= RenderDocumentFlags::ResetViewportScrolling;
    132    }
    133  }
    134  if (aFlags & CrossProcessPaintFlags::UseHighQualityScaling) {
    135    renderDocFlags |= RenderDocumentFlags::UseHighQualityScaling;
    136  }
    137 
    138  // Perform the actual rendering
    139  {
    140    nsRect r = CSSPixel::ToAppUnits(rect);
    141 
    142    // This matches what nsDeviceContext::CreateRenderingContext does.
    143    if (presContext->IsPrintingOrPrintPreview()) {
    144      dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
    145    }
    146 
    147    gfxContext thebes(dt);
    148    thebes.SetMatrix(Matrix::Scaling(aScale, aScale));
    149    thebes.SetCrossProcessPaintScale(aScale);
    150    RefPtr<PresShell> presShell = presContext->PresShell();
    151    (void)presShell->RenderDocument(r, renderDocFlags, aBackgroundColor,
    152                                    &thebes);
    153  }
    154 
    155  if (!recorder->mOutputStream.mValid) {
    156    recorder->DetachResources();
    157    return PaintFragment{};
    158  }
    159 
    160  ByteBuf recording = ByteBuf((uint8_t*)recorder->mOutputStream.mData,
    161                              recorder->mOutputStream.mLength,
    162                              recorder->mOutputStream.mCapacity);
    163  recorder->mOutputStream.mData = nullptr;
    164  recorder->mOutputStream.mLength = 0;
    165  recorder->mOutputStream.mCapacity = 0;
    166 
    167  PaintFragment fragment{
    168      surfaceSize.ToUnknownSize(),
    169      std::move(recording),
    170      std::move(recorder->TakeDependentSurfaces()),
    171  };
    172 
    173  recorder->DetachResources();
    174  return fragment;
    175 }
    176 
    177 bool PaintFragment::IsEmpty() const {
    178  return !mRecording.mData || mRecording.mLen == 0 || mSize == IntSize(0, 0);
    179 }
    180 
    181 PaintFragment::PaintFragment(IntSize aSize, ByteBuf&& aRecording,
    182                             nsTHashSet<uint64_t>&& aDependencies)
    183    : mSize(aSize),
    184      mRecording(std::move(aRecording)),
    185      mDependencies(std::move(aDependencies)) {}
    186 
    187 static dom::TabId GetTabId(dom::WindowGlobalParent* aWGP) {
    188  // There is no unique TabId for a given WindowGlobalParent, as multiple
    189  // WindowGlobalParents share the same PBrowser actor. However, we only
    190  // ever queue one paint per PBrowser by just using the current
    191  // WindowGlobalParent for a PBrowser. So we can interchange TabId and
    192  // WindowGlobalParent when dealing with resolving surfaces.
    193  RefPtr<dom::BrowserParent> browserParent = aWGP->GetBrowserParent();
    194  return browserParent ? browserParent->GetTabId() : dom::TabId(0);
    195 }
    196 
    197 /* static */
    198 bool CrossProcessPaint::Start(dom::WindowGlobalParent* aRoot,
    199                              const dom::DOMRect* aRect, float aScale,
    200                              nscolor aBackgroundColor,
    201                              CrossProcessPaintFlags aFlags,
    202                              dom::Promise* aPromise) {
    203  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
    204  aScale = std::max(aScale, kMinPaintScale);
    205 
    206  CPP_LOG(
    207      "Starting paint. "
    208      "[wgp=%p, "
    209      "scale=%f, "
    210      "color=(%u, %u, %u, %u)]\n",
    211      aRoot, aScale, NS_GET_R(aBackgroundColor), NS_GET_G(aBackgroundColor),
    212      NS_GET_B(aBackgroundColor), NS_GET_A(aBackgroundColor));
    213 
    214  Maybe<IntRect> rect;
    215  if (aRect) {
    216    rect =
    217        Some(IntRect::RoundOut((float)aRect->X(), (float)aRect->Y(),
    218                               (float)aRect->Width(), (float)aRect->Height()));
    219  }
    220 
    221  if (rect && rect->IsEmpty()) {
    222    return false;
    223  }
    224 
    225  dom::TabId rootId = GetTabId(aRoot);
    226 
    227  RefPtr<CrossProcessPaint> resolver =
    228      new CrossProcessPaint(aScale, rootId, aFlags);
    229  RefPtr<CrossProcessPaint::ResolvePromise> promise;
    230  if (aRoot->IsInProcess()) {
    231    RefPtr<dom::WindowGlobalChild> childActor = aRoot->GetChildActor();
    232    if (!childActor) {
    233      return false;
    234    }
    235 
    236    // `BrowsingContext()` cannot be nullptr.
    237    RefPtr<dom::BrowsingContext> bc = childActor->BrowsingContext();
    238 
    239    promise = resolver->Init();
    240    resolver->mPendingFragments += 1;
    241    resolver->ReceiveFragment(
    242        aRoot,
    243        PaintFragment::Record(bc, rect, aScale, aBackgroundColor, aFlags));
    244  } else {
    245    promise = resolver->Init();
    246    resolver->QueuePaint(aRoot, rect, aBackgroundColor, aFlags);
    247  }
    248 
    249  promise->Then(
    250      GetMainThreadSerialEventTarget(), __func__,
    251      [promise = RefPtr{aPromise}, rootId](ResolvedFragmentMap&& aFragments) {
    252        RefPtr<RecordedDependentSurface> root = aFragments.Get(rootId);
    253        CPP_LOG("Resolved all fragments.\n");
    254 
    255        // Create the destination draw target
    256        RefPtr<DrawTarget> drawTarget =
    257            gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
    258                root->mSize, SurfaceFormat::B8G8R8A8);
    259        if (!drawTarget || !drawTarget->IsValid()) {
    260          CPP_LOG("Couldn't create (%d x %d) surface for fragment %" PRIu64
    261                  ".\n",
    262                  root->mSize.width, root->mSize.height, (uint64_t)rootId);
    263          promise->MaybeReject(NS_ERROR_FAILURE);
    264          return;
    265        }
    266 
    267        // Translate the recording using our child tabs
    268        {
    269          InlineTranslator translator(drawTarget, nullptr);
    270          translator.SetDependentSurfaces(&aFragments);
    271          if (!translator.TranslateRecording((char*)root->mRecording.mData,
    272                                             root->mRecording.mLen)) {
    273            CPP_LOG("Couldn't translate recording for fragment %" PRIu64 ".\n",
    274                    (uint64_t)rootId);
    275            promise->MaybeReject(NS_ERROR_FAILURE);
    276            return;
    277          }
    278        }
    279 
    280        RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
    281        if (!snapshot) {
    282          promise->MaybeReject(NS_ERROR_FAILURE);
    283          return;
    284        }
    285 
    286        ErrorResult rv;
    287        RefPtr<dom::ImageBitmap> bitmap =
    288            dom::ImageBitmap::CreateFromSourceSurface(
    289                promise->GetParentObject(), snapshot, rv);
    290 
    291        if (!rv.Failed()) {
    292          CPP_LOG("Success, fulfilling promise.\n");
    293          promise->MaybeResolve(bitmap);
    294        } else {
    295          CPP_LOG("Couldn't create ImageBitmap for SourceSurface.\n");
    296          promise->MaybeReject(std::move(rv));
    297        }
    298      },
    299      [promise = RefPtr{aPromise}](const nsresult& aRv) {
    300        promise->MaybeReject(aRv);
    301      });
    302 
    303  return true;
    304 }
    305 
    306 /* static */
    307 RefPtr<CrossProcessPaint::ResolvePromise> CrossProcessPaint::Start(
    308    nsTHashSet<uint64_t>&& aDependencies) {
    309  MOZ_ASSERT(!aDependencies.IsEmpty());
    310  RefPtr<CrossProcessPaint> resolver =
    311      new CrossProcessPaint(1.0, dom::TabId(0), CrossProcessPaintFlags::None);
    312 
    313  RefPtr<CrossProcessPaint::ResolvePromise> promise = resolver->Init();
    314 
    315  PaintFragment rootFragment;
    316  rootFragment.mDependencies = std::move(aDependencies);
    317 
    318  resolver->QueueDependencies(rootFragment.mDependencies);
    319  resolver->mReceivedFragments.InsertOrUpdate(dom::TabId(0),
    320                                              std::move(rootFragment));
    321 
    322  resolver->MaybeResolve();
    323 
    324  return promise;
    325 }
    326 
    327 CrossProcessPaint::CrossProcessPaint(float aScale, dom::TabId aRoot,
    328                                     CrossProcessPaintFlags aFlags)
    329    : mRoot{aRoot}, mScale{aScale}, mPendingFragments{0}, mFlags{aFlags} {}
    330 
    331 CrossProcessPaint::~CrossProcessPaint() { Clear(NS_ERROR_ABORT); }
    332 
    333 void CrossProcessPaint::ReceiveFragment(dom::WindowGlobalParent* aWGP,
    334                                        PaintFragment&& aFragment) {
    335  if (IsCleared()) {
    336    CPP_LOG("Ignoring fragment from %p.\n", aWGP);
    337    return;
    338  }
    339 
    340  dom::TabId surfaceId = GetTabId(aWGP);
    341 
    342  MOZ_ASSERT(mPendingFragments > 0);
    343  MOZ_ASSERT(!mReceivedFragments.Contains(surfaceId));
    344 
    345  // Double check our invariants to protect against a compromised content
    346  // process
    347  if (mPendingFragments == 0 || mReceivedFragments.Contains(surfaceId) ||
    348      aFragment.IsEmpty()) {
    349    CPP_LOG("Dropping invalid fragment from %p.\n", aWGP);
    350    LostFragment(aWGP);
    351    return;
    352  }
    353 
    354  CPP_LOG("Receiving fragment from %p(%" PRIu64 ").\n", aWGP,
    355          (uint64_t)surfaceId);
    356 
    357  // Queue paints for child tabs
    358  QueueDependencies(aFragment.mDependencies);
    359 
    360  mReceivedFragments.InsertOrUpdate(surfaceId, std::move(aFragment));
    361  mPendingFragments -= 1;
    362 
    363  // Resolve this paint if we have received all pending fragments
    364  MaybeResolve();
    365 }
    366 
    367 void CrossProcessPaint::LostFragment(dom::WindowGlobalParent* aWGP) {
    368  if (IsCleared()) {
    369    CPP_LOG("Ignoring lost fragment from %p.\n", aWGP);
    370    return;
    371  }
    372 
    373  Clear(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
    374 }
    375 
    376 void CrossProcessPaint::QueueDependencies(
    377    const nsTHashSet<uint64_t>& aDependencies) {
    378  dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton();
    379  if (!cpm) {
    380    CPP_LOG(
    381        "Skipping QueueDependencies with no"
    382        " current ContentProcessManager.\n");
    383    return;
    384  }
    385  for (const auto& key : aDependencies) {
    386    auto dependency = dom::TabId(key);
    387 
    388    // Get the current BrowserParent of the remote browser that was marked
    389    // as a dependency
    390    dom::ContentParentId cpId = cpm->GetTabProcessId(dependency);
    391    RefPtr<dom::BrowserParent> browser =
    392        cpm->GetBrowserParentByProcessAndTabId(cpId, dependency);
    393    if (!browser) {
    394      CPP_LOG("Skipping dependency %" PRIu64
    395              " with no current BrowserParent.\n",
    396              (uint64_t)dependency);
    397      continue;
    398    }
    399 
    400    // Note that if the remote document is currently being cloned, it's possible
    401    // that the BrowserParent isn't the one for the cloned document, but the
    402    // BrowsingContext should be persisted/consistent.
    403    QueuePaint(browser->GetBrowsingContext());
    404  }
    405 }
    406 
    407 void CrossProcessPaint::QueuePaint(dom::WindowGlobalParent* aWGP,
    408                                   const Maybe<IntRect>& aRect,
    409                                   nscolor aBackgroundColor,
    410                                   CrossProcessPaintFlags aFlags) {
    411  MOZ_ASSERT(!mReceivedFragments.Contains(GetTabId(aWGP)));
    412 
    413  CPP_LOG("Queueing paint for WindowGlobalParent(%p).\n", aWGP);
    414 
    415  aWGP->DrawSnapshotInternal(this, aRect, mScale, aBackgroundColor,
    416                             (uint32_t)aFlags);
    417  mPendingFragments += 1;
    418 }
    419 
    420 void CrossProcessPaint::QueuePaint(dom::CanonicalBrowsingContext* aBc) {
    421  RefPtr<GenericNonExclusivePromise> clonePromise = aBc->GetClonePromise();
    422 
    423  if (!clonePromise) {
    424    RefPtr<dom::WindowGlobalParent> wgp = aBc->GetCurrentWindowGlobal();
    425    if (!wgp) {
    426      CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n", aBc);
    427      return;
    428    }
    429 
    430    // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
    431    QueuePaint(wgp, Nothing(), NS_RGBA(0, 0, 0, 0), GetFlagsForDependencies());
    432    return;
    433  }
    434 
    435  CPP_LOG("Queueing paint for BrowsingContext(%p).\n", aBc);
    436  // In the case it's still in the process of cloning the remote document, we
    437  // should defer the snapshot request after the cloning has been finished.
    438  mPendingFragments += 1;
    439  clonePromise->Then(
    440      GetMainThreadSerialEventTarget(), __func__,
    441      [self = RefPtr{this}, bc = RefPtr{aBc}]() {
    442        RefPtr<dom::WindowGlobalParent> wgp = bc->GetCurrentWindowGlobal();
    443        if (!wgp) {
    444          CPP_LOG("Skipping BrowsingContext(%p) with no current WGP.\n",
    445                  bc.get());
    446          return;
    447        }
    448        MOZ_ASSERT(!self->mReceivedFragments.Contains(GetTabId(wgp)));
    449 
    450        // TODO: Apply some sort of clipping to visible bounds here (Bug
    451        // 1562720)
    452        wgp->DrawSnapshotInternal(self, Nothing(), self->mScale,
    453                                  NS_RGBA(0, 0, 0, 0),
    454                                  (uint32_t)self->GetFlagsForDependencies());
    455      },
    456      [self = RefPtr{this}]() {
    457        CPP_LOG(
    458            "Abort painting for BrowsingContext(%p) because cloning remote "
    459            "document failed.\n",
    460            self.get());
    461        self->Clear(NS_ERROR_FAILURE);
    462      });
    463 }
    464 
    465 void CrossProcessPaint::Clear(nsresult aStatus) {
    466  mPendingFragments = 0;
    467  mReceivedFragments.Clear();
    468  mPromise.RejectIfExists(aStatus, __func__);
    469 }
    470 
    471 bool CrossProcessPaint::IsCleared() const { return mPromise.IsEmpty(); }
    472 
    473 void CrossProcessPaint::MaybeResolve() {
    474  // Don't do anything if we aren't ready, experienced an error, or already
    475  // resolved this paint
    476  if (IsCleared() || mPendingFragments > 0) {
    477    CPP_LOG("Not ready to resolve yet, have %u fragments left.\n",
    478            mPendingFragments);
    479    return;
    480  }
    481 
    482  CPP_LOG("Starting to resolve fragments.\n");
    483 
    484  // Resolve the paint fragments from the bottom up
    485  ResolvedFragmentMap resolved;
    486  {
    487    nsresult rv = ResolveInternal(mRoot, &resolved);
    488    if (NS_FAILED(rv)) {
    489      CPP_LOG("Couldn't resolve.\n");
    490      Clear(rv);
    491      return;
    492    }
    493  }
    494 
    495  CPP_LOG("Resolved all fragments.\n");
    496 
    497  mPromise.ResolveIfExists(std::move(resolved), __func__);
    498  Clear(NS_OK);
    499 }
    500 
    501 nsresult CrossProcessPaint::ResolveInternal(dom::TabId aTabId,
    502                                            ResolvedFragmentMap* aResolved) {
    503  // We should not have resolved this paint already
    504  MOZ_ASSERT(!aResolved->GetWeak(aTabId));
    505 
    506  CPP_LOG("Resolving fragment %" PRIu64 ".\n", (uint64_t)aTabId);
    507 
    508  Maybe<PaintFragment> fragment = mReceivedFragments.Extract(aTabId);
    509  if (!fragment) {
    510    return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
    511  }
    512 
    513  // Rasterize all the dependencies first so that we can resolve this fragment
    514  for (const auto& key : fragment->mDependencies) {
    515    auto dependency = dom::TabId(key);
    516 
    517    nsresult rv = ResolveInternal(dependency, aResolved);
    518    if (NS_FAILED(rv)) {
    519      return rv;
    520    }
    521  }
    522 
    523  RefPtr<RecordedDependentSurface> surface = new RecordedDependentSurface{
    524      fragment->mSize, std::move(fragment->mRecording)};
    525  aResolved->InsertOrUpdate(aTabId, std::move(surface));
    526  return NS_OK;
    527 }
    528 
    529 }  // namespace gfx
    530 }  // namespace mozilla