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/ContentProcessManager.h"
10 #include "mozilla/dom/ImageBitmap.h"
11 #include "mozilla/dom/BrowserParent.h"
12 #include "mozilla/dom/PWindowGlobalParent.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/WindowGlobalParent.h"
15 #include "mozilla/dom/WindowGlobalChild.h"
16 #include "mozilla/dom/WindowGlobalActorsBinding.h"
17 #include "mozilla/gfx/DrawEventRecorder.h"
18 #include "mozilla/gfx/InlineTranslator.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/PresShell.h"
21
22 #include "gfxPlatform.h"
23
24 #include "nsContentUtils.h"
25 #include "nsGlobalWindowInner.h"
26 #include "nsIDocShell.h"
27 #include "nsPresContext.h"
28
29 static mozilla::LazyLogModule gCrossProcessPaintLog("CrossProcessPaint");
30 static mozilla::LazyLogModule gPaintFragmentLog("PaintFragment");
31
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__))
36
37 namespace mozilla {
38 namespace gfx {
39
40 using namespace mozilla::ipc;
41
42 /// The minimum scale we allow tabs to be rasterized at.
43 static const float kMinPaintScale = 0.05f;
44
45 /* static */
Record(dom::BrowsingContext * aBc,const Maybe<IntRect> & aRect,float aScale,nscolor aBackgroundColor,CrossProcessPaintFlags aFlags)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{};
54 }
55
56 RefPtr<nsPresContext> presContext = ds->GetPresContext();
57 if (!presContext) {
58 PF_LOG("Couldn't find PresContext.\n");
59 return PaintFragment{};
60 }
61
62 CSSIntRect rect;
63 if (!aRect) {
64 nsCOMPtr<nsIWidget> widget =
65 nsContentUtils::WidgetForDocument(presContext->Document());
66
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);
75 }
76
77 if (rect.IsEmpty()) {
78 // TODO: Should we return an empty surface here?
79 PF_LOG("Empty rect to paint.\n");
80 return PaintFragment{};
81 }
82
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;
87
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));
97
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{};
104 }
105
106 // Flush any pending notifications
107 nsContentUtils::FlushLayoutForTree(ds->GetWindow());
108
109 // Initialize the recorder
110 SurfaceFormat format = SurfaceFormat::B8G8R8A8;
111 RefPtr<DrawTarget> referenceDt = Factory::CreateDrawTarget(
112 gfxPlatform::GetPlatform()->GetSoftwareBackend(), IntSize(1, 1), format);
113
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
121 RenderDocumentFlags renderDocFlags = RenderDocumentFlags::None;
122 if (!(aFlags & CrossProcessPaintFlags::DrawView)) {
123 renderDocFlags = (RenderDocumentFlags::IgnoreViewportScrolling |
124 RenderDocumentFlags::ResetViewportScrolling |
125 RenderDocumentFlags::DocumentRelative);
126 }
127
128 // Perform the actual rendering
129 {
130 nsRect r = CSSPixel::ToAppUnits(rect);
131
132 RefPtr<gfxContext> thebes = gfxContext::CreateOrNull(dt);
133 thebes->SetMatrix(Matrix::Scaling(aScale, aScale));
134 RefPtr<PresShell> presShell = presContext->PresShell();
135 Unused << presShell->RenderDocument(r, renderDocFlags, aBackgroundColor,
136 thebes);
137 }
138
139 if (!recorder->mOutputStream.mValid) {
140 return PaintFragment{};
141 }
142
143 ByteBuf recording = ByteBuf((uint8_t*)recorder->mOutputStream.mData,
144 recorder->mOutputStream.mLength,
145 recorder->mOutputStream.mCapacity);
146 recorder->mOutputStream.mData = nullptr;
147 recorder->mOutputStream.mLength = 0;
148 recorder->mOutputStream.mCapacity = 0;
149
150 return PaintFragment{
151 surfaceSize.ToUnknownSize(),
152 std::move(recording),
153 std::move(recorder->TakeDependentSurfaces()),
154 };
155 }
156
IsEmpty() const157 bool PaintFragment::IsEmpty() const {
158 return !mRecording.mData || mRecording.mLen == 0 || mSize == IntSize(0, 0);
159 }
160
PaintFragment(IntSize aSize,ByteBuf && aRecording,nsTHashSet<uint64_t> && aDependencies)161 PaintFragment::PaintFragment(IntSize aSize, ByteBuf&& aRecording,
162 nsTHashSet<uint64_t>&& aDependencies)
163 : mSize(aSize),
164 mRecording(std::move(aRecording)),
165 mDependencies(std::move(aDependencies)) {}
166
GetTabId(dom::WindowGlobalParent * aWGP)167 static dom::TabId GetTabId(dom::WindowGlobalParent* aWGP) {
168 // There is no unique TabId for a given WindowGlobalParent, as multiple
169 // WindowGlobalParents share the same PBrowser actor. However, we only
170 // ever queue one paint per PBrowser by just using the current
171 // WindowGlobalParent for a PBrowser. So we can interchange TabId and
172 // WindowGlobalParent when dealing with resolving surfaces.
173 RefPtr<dom::BrowserParent> browserParent = aWGP->GetBrowserParent();
174 return browserParent ? browserParent->GetTabId() : dom::TabId(0);
175 }
176
177 /* static */
Start(dom::WindowGlobalParent * aRoot,const dom::DOMRect * aRect,float aScale,nscolor aBackgroundColor,CrossProcessPaintFlags aFlags,dom::Promise * aPromise)178 bool CrossProcessPaint::Start(dom::WindowGlobalParent* aRoot,
179 const dom::DOMRect* aRect, float aScale,
180 nscolor aBackgroundColor,
181 CrossProcessPaintFlags aFlags,
182 dom::Promise* aPromise) {
183 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
184 aScale = std::max(aScale, kMinPaintScale);
185
186 CPP_LOG(
187 "Starting paint. "
188 "[wgp=%p, "
189 "scale=%f, "
190 "color=(%u, %u, %u, %u)]\n",
191 aRoot, aScale, NS_GET_R(aBackgroundColor), NS_GET_G(aBackgroundColor),
192 NS_GET_B(aBackgroundColor), NS_GET_A(aBackgroundColor));
193
194 Maybe<IntRect> rect;
195 if (aRect) {
196 rect =
197 Some(IntRect::RoundOut((float)aRect->X(), (float)aRect->Y(),
198 (float)aRect->Width(), (float)aRect->Height()));
199 }
200
201 if (rect && rect->IsEmpty()) {
202 return false;
203 }
204
205 dom::TabId rootId = GetTabId(aRoot);
206
207 RefPtr<CrossProcessPaint> resolver = new CrossProcessPaint(aScale, rootId);
208 RefPtr<CrossProcessPaint::ResolvePromise> promise;
209
210 if (aRoot->IsInProcess()) {
211 RefPtr<dom::WindowGlobalChild> childActor = aRoot->GetChildActor();
212 if (!childActor) {
213 return false;
214 }
215
216 // `BrowsingContext()` cannot be nullptr.
217 RefPtr<dom::BrowsingContext> bc = childActor->BrowsingContext();
218
219 promise = resolver->Init();
220 resolver->mPendingFragments += 1;
221 resolver->ReceiveFragment(
222 aRoot,
223 PaintFragment::Record(bc, rect, aScale, aBackgroundColor, aFlags));
224 } else {
225 promise = resolver->Init();
226 resolver->QueuePaint(aRoot, rect, aBackgroundColor, aFlags);
227 }
228
229 promise->Then(
230 GetCurrentSerialEventTarget(), __func__,
231 [promise = RefPtr{aPromise}, rootId](ResolvedFragmentMap&& aFragments) {
232 RefPtr<RecordedDependentSurface> root = aFragments.Get(rootId);
233 CPP_LOG("Resolved all fragments.\n");
234
235 // Create the destination draw target
236 RefPtr<DrawTarget> drawTarget =
237 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
238 root->mSize, SurfaceFormat::B8G8R8A8);
239 if (!drawTarget || !drawTarget->IsValid()) {
240 CPP_LOG("Couldn't create (%d x %d) surface for fragment %" PRIu64
241 ".\n",
242 root->mSize.width, root->mSize.height, (uint64_t)rootId);
243 promise->MaybeReject(NS_ERROR_FAILURE);
244 return;
245 }
246
247 // Translate the recording using our child tabs
248 {
249 InlineTranslator translator(drawTarget, nullptr);
250 translator.SetDependentSurfaces(&aFragments);
251 if (!translator.TranslateRecording((char*)root->mRecording.mData,
252 root->mRecording.mLen)) {
253 CPP_LOG("Couldn't translate recording for fragment %" PRIu64 ".\n",
254 (uint64_t)rootId);
255 promise->MaybeReject(NS_ERROR_FAILURE);
256 return;
257 }
258 }
259
260 RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
261 if (!snapshot) {
262 promise->MaybeReject(NS_ERROR_FAILURE);
263 return;
264 }
265
266 ErrorResult rv;
267 RefPtr<dom::ImageBitmap> bitmap =
268 dom::ImageBitmap::CreateFromSourceSurface(
269 promise->GetParentObject(), snapshot, rv);
270
271 if (!rv.Failed()) {
272 CPP_LOG("Success, fulfilling promise.\n");
273 promise->MaybeResolve(bitmap);
274 } else {
275 CPP_LOG("Couldn't create ImageBitmap for SourceSurface.\n");
276 promise->MaybeReject(std::move(rv));
277 }
278 },
279 [promise = RefPtr{aPromise}](const nsresult& aRv) {
280 promise->MaybeReject(aRv);
281 });
282
283 return true;
284 }
285
286 /* static */
Start(nsTHashSet<uint64_t> && aDependencies)287 RefPtr<CrossProcessPaint::ResolvePromise> CrossProcessPaint::Start(
288 nsTHashSet<uint64_t>&& aDependencies) {
289 MOZ_ASSERT(!aDependencies.IsEmpty());
290 RefPtr<CrossProcessPaint> resolver =
291 new CrossProcessPaint(1.0, dom::TabId(0));
292
293 RefPtr<CrossProcessPaint::ResolvePromise> promise = resolver->Init();
294
295 PaintFragment rootFragment;
296 rootFragment.mDependencies = std::move(aDependencies);
297
298 resolver->QueueDependencies(rootFragment.mDependencies);
299 resolver->mReceivedFragments.InsertOrUpdate(dom::TabId(0),
300 std::move(rootFragment));
301
302 resolver->MaybeResolve();
303
304 return promise;
305 }
306
CrossProcessPaint(float aScale,dom::TabId aRoot)307 CrossProcessPaint::CrossProcessPaint(float aScale, dom::TabId aRoot)
308 : mRoot{aRoot}, mScale{aScale}, mPendingFragments{0} {}
309
310 CrossProcessPaint::~CrossProcessPaint() = default;
311
ReceiveFragment(dom::WindowGlobalParent * aWGP,PaintFragment && aFragment)312 void CrossProcessPaint::ReceiveFragment(dom::WindowGlobalParent* aWGP,
313 PaintFragment&& aFragment) {
314 if (IsCleared()) {
315 CPP_LOG("Ignoring fragment from %p.\n", aWGP);
316 return;
317 }
318
319 dom::TabId surfaceId = GetTabId(aWGP);
320
321 MOZ_ASSERT(mPendingFragments > 0);
322 MOZ_ASSERT(!mReceivedFragments.Contains(surfaceId));
323
324 // Double check our invariants to protect against a compromised content
325 // process
326 if (mPendingFragments == 0 || mReceivedFragments.Contains(surfaceId) ||
327 aFragment.IsEmpty()) {
328 CPP_LOG("Dropping invalid fragment from %p.\n", aWGP);
329 LostFragment(aWGP);
330 return;
331 }
332
333 CPP_LOG("Receiving fragment from %p(%" PRIu64 ").\n", aWGP,
334 (uint64_t)surfaceId);
335
336 // Queue paints for child tabs
337 QueueDependencies(aFragment.mDependencies);
338
339 mReceivedFragments.InsertOrUpdate(surfaceId, std::move(aFragment));
340 mPendingFragments -= 1;
341
342 // Resolve this paint if we have received all pending fragments
343 MaybeResolve();
344 }
345
LostFragment(dom::WindowGlobalParent * aWGP)346 void CrossProcessPaint::LostFragment(dom::WindowGlobalParent* aWGP) {
347 if (IsCleared()) {
348 CPP_LOG("Ignoring lost fragment from %p.\n", aWGP);
349 return;
350 }
351
352 Clear(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
353 }
354
QueueDependencies(const nsTHashSet<uint64_t> & aDependencies)355 void CrossProcessPaint::QueueDependencies(
356 const nsTHashSet<uint64_t>& aDependencies) {
357 for (const auto& key : aDependencies) {
358 auto dependency = dom::TabId(key);
359
360 // Get the current WindowGlobalParent of the remote browser that was marked
361 // as a dependency
362 dom::ContentProcessManager* cpm =
363 dom::ContentProcessManager::GetSingleton();
364 dom::ContentParentId cpId = cpm->GetTabProcessId(dependency);
365 RefPtr<dom::BrowserParent> browser =
366 cpm->GetBrowserParentByProcessAndTabId(cpId, dependency);
367 if (!browser) {
368 CPP_LOG("Skipping dependency %" PRIu64
369 " with no current BrowserParent.\n",
370 (uint64_t)dependency);
371 continue;
372 }
373 RefPtr<dom::WindowGlobalParent> wgp =
374 browser->GetBrowsingContext()->GetCurrentWindowGlobal();
375
376 if (!wgp) {
377 CPP_LOG("Skipping dependency %" PRIu64 " with no current WGP.\n",
378 (uint64_t)dependency);
379 continue;
380 }
381
382 // TODO: Apply some sort of clipping to visible bounds here (Bug 1562720)
383 QueuePaint(wgp, Nothing());
384 }
385 }
386
QueuePaint(dom::WindowGlobalParent * aWGP,const Maybe<IntRect> & aRect,nscolor aBackgroundColor,CrossProcessPaintFlags aFlags)387 void CrossProcessPaint::QueuePaint(dom::WindowGlobalParent* aWGP,
388 const Maybe<IntRect>& aRect,
389 nscolor aBackgroundColor,
390 CrossProcessPaintFlags aFlags) {
391 MOZ_ASSERT(!mReceivedFragments.Contains(GetTabId(aWGP)));
392
393 CPP_LOG("Queueing paint for %p.\n", aWGP);
394
395 aWGP->DrawSnapshotInternal(this, aRect, mScale, aBackgroundColor,
396 (uint32_t)aFlags);
397 mPendingFragments += 1;
398 }
399
Clear(nsresult aStatus)400 void CrossProcessPaint::Clear(nsresult aStatus) {
401 mPendingFragments = 0;
402 mReceivedFragments.Clear();
403 mPromise.RejectIfExists(aStatus, __func__);
404 }
405
IsCleared() const406 bool CrossProcessPaint::IsCleared() const { return mPromise.IsEmpty(); }
407
MaybeResolve()408 void CrossProcessPaint::MaybeResolve() {
409 // Don't do anything if we aren't ready, experienced an error, or already
410 // resolved this paint
411 if (IsCleared() || mPendingFragments > 0) {
412 CPP_LOG("Not ready to resolve yet, have %u fragments left.\n",
413 mPendingFragments);
414 return;
415 }
416
417 CPP_LOG("Starting to resolve fragments.\n");
418
419 // Resolve the paint fragments from the bottom up
420 ResolvedFragmentMap resolved;
421 {
422 nsresult rv = ResolveInternal(mRoot, &resolved);
423 if (NS_FAILED(rv)) {
424 CPP_LOG("Couldn't resolve.\n");
425 Clear(rv);
426 return;
427 }
428 }
429
430 CPP_LOG("Resolved all fragments.\n");
431
432 mPromise.ResolveIfExists(std::move(resolved), __func__);
433 Clear(NS_OK);
434 }
435
ResolveInternal(dom::TabId aTabId,ResolvedFragmentMap * aResolved)436 nsresult CrossProcessPaint::ResolveInternal(dom::TabId aTabId,
437 ResolvedFragmentMap* aResolved) {
438 // We should not have resolved this paint already
439 MOZ_ASSERT(!aResolved->GetWeak(aTabId));
440
441 CPP_LOG("Resolving fragment %" PRIu64 ".\n", (uint64_t)aTabId);
442
443 Maybe<PaintFragment> fragment = mReceivedFragments.Extract(aTabId);
444 if (!fragment) {
445 return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
446 }
447
448 // Rasterize all the dependencies first so that we can resolve this fragment
449 for (const auto& key : fragment->mDependencies) {
450 auto dependency = dom::TabId(key);
451
452 nsresult rv = ResolveInternal(dependency, aResolved);
453 if (NS_FAILED(rv)) {
454 return rv;
455 }
456 }
457
458 RefPtr<RecordedDependentSurface> surface = new RecordedDependentSurface{
459 fragment->mSize, std::move(fragment->mRecording)};
460 aResolved->InsertOrUpdate(aTabId, std::move(surface));
461 return NS_OK;
462 }
463
464 } // namespace gfx
465 } // namespace mozilla
466