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