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