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