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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/dom/DocGroup.h"
8 
9 #include "mozilla/AbstractThread.h"
10 #include "mozilla/PerformanceUtils.h"
11 #include "mozilla/SchedulerGroup.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "mozilla/Telemetry.h"
14 #include "mozilla/ThrottledEventQueue.h"
15 #include "mozilla/dom/BrowsingContext.h"
16 #include "mozilla/dom/CustomElementRegistry.h"
17 #include "mozilla/dom/DOMTypes.h"
18 #include "mozilla/dom/JSExecutionManager.h"
19 #include "mozilla/dom/WindowContext.h"
20 #include "nsDOMMutationObserver.h"
21 #include "nsIDirectTaskDispatcher.h"
22 #include "nsIXULRuntime.h"
23 #include "nsProxyRelease.h"
24 #include "nsThread.h"
25 #if defined(XP_WIN)
26 #  include <processthreadsapi.h>  // for GetCurrentProcessId()
27 #else
28 #  include <unistd.h>  // for getpid()
29 #endif                 // defined(XP_WIN)
30 
31 namespace {
32 
33 #define NS_LABELLINGEVENTTARGET_IID                  \
34   {                                                  \
35     0x6087fa50, 0xe387, 0x45c8, {                    \
36       0xab, 0x72, 0xd2, 0x1f, 0x69, 0xee, 0xd3, 0x15 \
37     }                                                \
38   }
39 
40 // LabellingEventTarget labels all dispatches with the DocGroup that
41 // created it.
42 class LabellingEventTarget final : public nsISerialEventTarget,
43                                    public nsIDirectTaskDispatcher {
44  public:
45   NS_DECLARE_STATIC_IID_ACCESSOR(NS_LABELLINGEVENTTARGET_IID)
46 
LabellingEventTarget(mozilla::PerformanceCounter * aPerformanceCounter)47   explicit LabellingEventTarget(
48       mozilla::PerformanceCounter* aPerformanceCounter)
49       : mPerformanceCounter(aPerformanceCounter),
50         mMainThread(
51             static_cast<nsThread*>(mozilla::GetMainThreadSerialEventTarget())) {
52   }
53 
54   NS_DECL_THREADSAFE_ISUPPORTS
55   NS_DECL_NSIEVENTTARGET_FULL
56   NS_DECL_NSIDIRECTTASKDISPATCHER
57 
58  private:
59   ~LabellingEventTarget() = default;
60   const RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
61   const RefPtr<nsThread> mMainThread;
62 };
63 
64 NS_DEFINE_STATIC_IID_ACCESSOR(LabellingEventTarget, NS_LABELLINGEVENTTARGET_IID)
65 
66 }  // namespace
67 
68 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aRunnable,uint32_t aFlags)69 LabellingEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
70                                          uint32_t aFlags) {
71   return Dispatch(do_AddRef(aRunnable), aFlags);
72 }
73 
74 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aRunnable,uint32_t aFlags)75 LabellingEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
76                                uint32_t aFlags) {
77   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
78     return NS_ERROR_UNEXPECTED;
79   }
80 
81   return mozilla::SchedulerGroup::LabeledDispatch(
82       mozilla::TaskCategory::Other, std::move(aRunnable), mPerformanceCounter);
83 }
84 
85 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable>,uint32_t)86 LabellingEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
87   return NS_ERROR_NOT_IMPLEMENTED;
88 }
89 
90 NS_IMETHODIMP
IsOnCurrentThread(bool * aIsOnCurrentThread)91 LabellingEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
92   *aIsOnCurrentThread = NS_IsMainThread();
93   return NS_OK;
94 }
95 
NS_IMETHODIMP_(bool)96 NS_IMETHODIMP_(bool)
97 LabellingEventTarget::IsOnCurrentThreadInfallible() {
98   return NS_IsMainThread();
99 }
100 
101 //-----------------------------------------------------------------------------
102 // nsIDirectTaskDispatcher
103 //-----------------------------------------------------------------------------
104 // We are always running on the main thread, forward to the nsThread's
105 // MainThread
106 NS_IMETHODIMP
DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent)107 LabellingEventTarget::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
108   return mMainThread->DispatchDirectTask(std::move(aEvent));
109 }
110 
DrainDirectTasks()111 NS_IMETHODIMP LabellingEventTarget::DrainDirectTasks() {
112   return mMainThread->DrainDirectTasks();
113 }
114 
HaveDirectTasks(bool * aValue)115 NS_IMETHODIMP LabellingEventTarget::HaveDirectTasks(bool* aValue) {
116   return mMainThread->HaveDirectTasks(aValue);
117 }
118 
119 NS_IMPL_ISUPPORTS(LabellingEventTarget, nsIEventTarget, nsISerialEventTarget,
120                   nsIDirectTaskDispatcher)
121 
122 namespace mozilla::dom {
123 
124 AutoTArray<RefPtr<DocGroup>, 2>* DocGroup::sPendingDocGroups = nullptr;
125 
126 NS_IMPL_CYCLE_COLLECTION_CLASS(DocGroup)
127 
128 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DocGroup)
129   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalSlotList)
130   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup)
131 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
132 
133 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DocGroup)
134   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalSlotList)
135   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup)
136 
137   // If we still have any documents in this array, they were just unlinked, so
138   // clear out our weak pointers to them.
139   tmp->mDocuments.Clear();
140 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
141 
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DocGroup,AddRef)142 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DocGroup, AddRef)
143 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DocGroup, Release)
144 
145 /* static */
146 already_AddRefed<DocGroup> DocGroup::Create(
147     BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey) {
148   RefPtr<DocGroup> docGroup = new DocGroup(aBrowsingContextGroup, aKey);
149   docGroup->mEventTarget =
150       new LabellingEventTarget(docGroup->GetPerformanceCounter());
151   return docGroup.forget();
152 }
153 
154 /* static */
GetKey(nsIPrincipal * aPrincipal,bool aCrossOriginIsolated,nsACString & aKey)155 nsresult DocGroup::GetKey(nsIPrincipal* aPrincipal, bool aCrossOriginIsolated,
156                           nsACString& aKey) {
157   // Use GetBaseDomain() to handle things like file URIs, IP address URIs,
158   // etc. correctly.
159   nsresult rv = aCrossOriginIsolated ? aPrincipal->GetOrigin(aKey)
160                                      : aPrincipal->GetSiteOrigin(aKey);
161   if (NS_FAILED(rv)) {
162     aKey.Truncate();
163   }
164 
165   return rv;
166 }
167 
SetExecutionManager(JSExecutionManager * aManager)168 void DocGroup::SetExecutionManager(JSExecutionManager* aManager) {
169   mExecutionManager = aManager;
170 }
171 
172 mozilla::dom::CustomElementReactionsStack*
CustomElementReactionsStack()173 DocGroup::CustomElementReactionsStack() {
174   MOZ_ASSERT(NS_IsMainThread());
175   if (!mReactionsStack) {
176     mReactionsStack = new mozilla::dom::CustomElementReactionsStack();
177   }
178 
179   return mReactionsStack;
180 }
181 
AddDocument(Document * aDocument)182 void DocGroup::AddDocument(Document* aDocument) {
183   MOZ_ASSERT(NS_IsMainThread());
184   MOZ_ASSERT(!mDocuments.Contains(aDocument));
185   MOZ_ASSERT(mBrowsingContextGroup);
186   MOZ_ASSERT_IF(
187       FissionAutostart() && !mDocuments.IsEmpty(),
188       mDocuments[0]->CrossOriginIsolated() == aDocument->CrossOriginIsolated());
189   mDocuments.AppendElement(aDocument);
190 }
191 
RemoveDocument(Document * aDocument)192 void DocGroup::RemoveDocument(Document* aDocument) {
193   MOZ_ASSERT(NS_IsMainThread());
194   MOZ_ASSERT(mDocuments.Contains(aDocument));
195   mDocuments.RemoveElement(aDocument);
196 
197   if (mDocuments.IsEmpty()) {
198     mBrowsingContextGroup = nullptr;
199   }
200 }
201 
DocGroup(BrowsingContextGroup * aBrowsingContextGroup,const nsACString & aKey)202 DocGroup::DocGroup(BrowsingContextGroup* aBrowsingContextGroup,
203                    const nsACString& aKey)
204     : mKey(aKey),
205       mBrowsingContextGroup(aBrowsingContextGroup),
206       mAgentClusterId(nsID::GenerateUUID()) {
207   // This method does not add itself to
208   // mBrowsingContextGroup->mDocGroups as the caller does it for us.
209   MOZ_ASSERT(NS_IsMainThread());
210   if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
211     mArena = new mozilla::dom::DOMArena();
212   }
213 
214   mPerformanceCounter = new mozilla::PerformanceCounter("DocGroup:"_ns + aKey);
215 }
216 
~DocGroup()217 DocGroup::~DocGroup() {
218   MOZ_RELEASE_ASSERT(NS_IsMainThread());
219   MOZ_RELEASE_ASSERT(mDocuments.IsEmpty());
220 
221   if (mIframePostMessageQueue) {
222     FlushIframePostMessageQueue();
223   }
224 }
225 
ReportPerformanceInfo()226 RefPtr<PerformanceInfoPromise> DocGroup::ReportPerformanceInfo() {
227   AssertIsOnMainThread();
228   MOZ_ASSERT(mPerformanceCounter);
229 #if defined(XP_WIN)
230   uint32_t pid = GetCurrentProcessId();
231 #else
232   uint32_t pid = getpid();
233 #endif
234   uint64_t windowID = 0;
235   uint16_t count = 0;
236   uint64_t duration = 0;
237   nsCString host;
238   bool isTopLevel = false;
239   RefPtr<BrowsingContext> top;
240   RefPtr<AbstractThread> mainThread =
241       AbstractMainThreadFor(TaskCategory::Performance);
242 
243   for (const auto& document : *this) {
244     if (host.IsEmpty()) {
245       nsCOMPtr<nsIURI> docURI = document->GetDocumentURI();
246       if (!docURI) {
247         continue;
248       }
249 
250       docURI->GetHost(host);
251       if (host.IsEmpty()) {
252         host = docURI->GetSpecOrDefault();
253       }
254     }
255 
256     BrowsingContext* context = document->GetBrowsingContext();
257     if (!context) {
258       continue;
259     }
260 
261     top = context->Top();
262 
263     if (!top || !top->GetCurrentWindowContext()) {
264       continue;
265     }
266 
267     isTopLevel = context->IsTop();
268     windowID = top->GetCurrentWindowContext()->OuterWindowId();
269     break;
270   };
271 
272   MOZ_ASSERT(!host.IsEmpty());
273   duration = mPerformanceCounter->GetExecutionDuration();
274   FallibleTArray<CategoryDispatch> items;
275 
276   // now that we have the host and window ids, let's look at the perf counters
277   for (uint32_t index = 0; index < (uint32_t)TaskCategory::Count; index++) {
278     TaskCategory category = static_cast<TaskCategory>(index);
279     count = mPerformanceCounter->GetDispatchCount(DispatchCategory(category));
280     CategoryDispatch item = CategoryDispatch(index, count);
281     if (!items.AppendElement(item, fallible)) {
282       NS_ERROR("Could not complete the operation");
283       break;
284     }
285   }
286 
287   if (!isTopLevel && top && top->IsInProcess()) {
288     return PerformanceInfoPromise::CreateAndResolve(
289         PerformanceInfo(host, pid, windowID, duration,
290                         mPerformanceCounter->GetID(), false, isTopLevel,
291                         PerformanceMemoryInfo(),  // Empty memory info
292                         items),
293         __func__);
294   }
295 
296   MOZ_ASSERT(mainThread);
297   RefPtr<DocGroup> self = this;
298   return (isTopLevel ? CollectMemoryInfo(top, mainThread)
299                      : CollectMemoryInfo(self, mainThread))
300       ->Then(
301           mainThread, __func__,
302           [self, host, pid, windowID, duration, isTopLevel,
303            items = std::move(items)](const PerformanceMemoryInfo& aMemoryInfo) {
304             PerformanceInfo info =
305                 PerformanceInfo(host, pid, windowID, duration,
306                                 self->mPerformanceCounter->GetID(), false,
307                                 isTopLevel, aMemoryInfo, items);
308 
309             return PerformanceInfoPromise::CreateAndResolve(std::move(info),
310                                                             __func__);
311           },
312           [self](const nsresult rv) {
313             return PerformanceInfoPromise::CreateAndReject(rv, __func__);
314           });
315 }
316 
Dispatch(TaskCategory aCategory,already_AddRefed<nsIRunnable> && aRunnable)317 nsresult DocGroup::Dispatch(TaskCategory aCategory,
318                             already_AddRefed<nsIRunnable>&& aRunnable) {
319   MOZ_RELEASE_ASSERT(NS_IsMainThread());
320 
321   if (mPerformanceCounter) {
322     mPerformanceCounter->IncrementDispatchCounter(DispatchCategory(aCategory));
323   }
324   return SchedulerGroup::LabeledDispatch(aCategory, std::move(aRunnable),
325                                          mPerformanceCounter);
326 }
327 
EventTargetFor(TaskCategory aCategory) const328 nsISerialEventTarget* DocGroup::EventTargetFor(TaskCategory aCategory) const {
329   MOZ_RELEASE_ASSERT(NS_IsMainThread());
330   MOZ_ASSERT(!mDocuments.IsEmpty());
331 
332   // Here we have the same event target for every TaskCategory. The
333   // reason for that is that currently TaskCategory isn't used, and
334   // it's unsure if it ever will be (See Bug 1624819).
335   return mEventTarget;
336 }
337 
AbstractMainThreadFor(TaskCategory aCategory)338 AbstractThread* DocGroup::AbstractMainThreadFor(TaskCategory aCategory) {
339   MOZ_RELEASE_ASSERT(NS_IsMainThread());
340   MOZ_ASSERT(!mDocuments.IsEmpty());
341 
342   // Here we have the same thread for every TaskCategory. The reason
343   // for that is that currently TaskCategory isn't used, and it's
344   // unsure if it ever will be (See Bug 1624819).
345   return AbstractThread::MainThread();
346 }
347 
SignalSlotChange(HTMLSlotElement & aSlot)348 void DocGroup::SignalSlotChange(HTMLSlotElement& aSlot) {
349   MOZ_ASSERT(!mSignalSlotList.Contains(&aSlot));
350   mSignalSlotList.AppendElement(&aSlot);
351 
352   if (!sPendingDocGroups) {
353     // Queue a mutation observer compound microtask.
354     nsDOMMutationObserver::QueueMutationObserverMicroTask();
355     sPendingDocGroups = new AutoTArray<RefPtr<DocGroup>, 2>;
356   }
357 
358   sPendingDocGroups->AppendElement(this);
359 }
360 
TryToLoadIframesInBackground()361 bool DocGroup::TryToLoadIframesInBackground() {
362   return !FissionAutostart() &&
363          StaticPrefs::dom_separate_event_queue_for_post_message_enabled() &&
364          StaticPrefs::dom_cross_origin_iframes_loaded_in_background();
365 }
366 
QueueIframePostMessages(already_AddRefed<nsIRunnable> && aRunnable,uint64_t aWindowId)367 nsresult DocGroup::QueueIframePostMessages(
368     already_AddRefed<nsIRunnable>&& aRunnable, uint64_t aWindowId) {
369   if (DocGroup::TryToLoadIframesInBackground()) {
370     if (!mIframePostMessageQueue) {
371       nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
372       mIframePostMessageQueue = ThrottledEventQueue::Create(
373           target, "Background Loading Iframe PostMessage Queue",
374           nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS);
375       nsresult rv = mIframePostMessageQueue->SetIsPaused(true);
376       MOZ_ALWAYS_SUCCEEDS(rv);
377     }
378 
379     // Ensure the queue is disabled. Unlike the postMessageEvent queue
380     // in BrowsingContextGroup, this postMessage queue should always
381     // be paused, because if we leave it open, the postMessage may get
382     // dispatched to an unloaded iframe
383     MOZ_ASSERT(mIframePostMessageQueue);
384     MOZ_ASSERT(mIframePostMessageQueue->IsPaused());
385 
386     mIframesUsedPostMessageQueue.Insert(aWindowId);
387 
388     mIframePostMessageQueue->Dispatch(std::move(aRunnable), NS_DISPATCH_NORMAL);
389     return NS_OK;
390   }
391   return NS_ERROR_FAILURE;
392 }
393 
TryFlushIframePostMessages(uint64_t aWindowId)394 void DocGroup::TryFlushIframePostMessages(uint64_t aWindowId) {
395   if (DocGroup::TryToLoadIframesInBackground()) {
396     mIframesUsedPostMessageQueue.Remove(aWindowId);
397     if (mIframePostMessageQueue && mIframesUsedPostMessageQueue.IsEmpty()) {
398       MOZ_ASSERT(mIframePostMessageQueue->IsPaused());
399       nsresult rv = mIframePostMessageQueue->SetIsPaused(true);
400       MOZ_ALWAYS_SUCCEEDS(rv);
401       FlushIframePostMessageQueue();
402     }
403   }
404 }
405 
FlushIframePostMessageQueue()406 void DocGroup::FlushIframePostMessageQueue() {
407   nsCOMPtr<nsIRunnable> event;
408   while ((event = mIframePostMessageQueue->GetEvent())) {
409     Dispatch(TaskCategory::Other, event.forget());
410   }
411 }
412 
MoveSignalSlotList()413 nsTArray<RefPtr<HTMLSlotElement>> DocGroup::MoveSignalSlotList() {
414   for (const RefPtr<HTMLSlotElement>& slot : mSignalSlotList) {
415     slot->RemovedFromSignalSlotList();
416   }
417   return std::move(mSignalSlotList);
418 }
419 
IsActive() const420 bool DocGroup::IsActive() const {
421   for (Document* doc : mDocuments) {
422     if (doc->IsCurrentActiveDocument()) {
423       return true;
424     }
425   }
426 
427   return false;
428 }
429 
430 }  // namespace mozilla::dom
431