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