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