/src/mozilla-central/dom/base/TabGroup.cpp
Line | Count | Source (jump to first uncovered line) |
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 "mozilla/dom/TabGroup.h" |
8 | | |
9 | | #include "mozilla/dom/ContentChild.h" |
10 | | #include "mozilla/dom/TabChild.h" |
11 | | #include "mozilla/dom/DocGroup.h" |
12 | | #include "mozilla/dom/TimeoutManager.h" |
13 | | #include "mozilla/AbstractThread.h" |
14 | | #include "mozilla/ClearOnShutdown.h" |
15 | | #include "mozilla/StaticPtr.h" |
16 | | #include "mozilla/Telemetry.h" |
17 | | #include "mozilla/ThrottledEventQueue.h" |
18 | | #include "nsIDocShell.h" |
19 | | #include "nsIEffectiveTLDService.h" |
20 | | #include "nsIURI.h" |
21 | | |
22 | | namespace mozilla { |
23 | | namespace dom { |
24 | | |
25 | | static StaticRefPtr<TabGroup> sChromeTabGroup; |
26 | | |
27 | | LinkedList<TabGroup>* TabGroup::sTabGroups = nullptr; |
28 | | |
29 | | TabGroup::TabGroup(bool aIsChrome) |
30 | | : mLastWindowLeft(false) |
31 | | , mThrottledQueuesInitialized(false) |
32 | | , mNumOfIndexedDBTransactions(0) |
33 | | , mNumOfIndexedDBDatabases(0) |
34 | | , mIsChrome(aIsChrome) |
35 | | , mForegroundCount(0) |
36 | 0 | { |
37 | 0 | if (!sTabGroups) { |
38 | 0 | sTabGroups = new LinkedList<TabGroup>(); |
39 | 0 | } |
40 | 0 | sTabGroups->insertBack(this); |
41 | 0 |
|
42 | 0 | CreateEventTargets(/* aNeedValidation = */ !aIsChrome); |
43 | 0 |
|
44 | 0 | // Do not throttle runnables from chrome windows. In theory we should |
45 | 0 | // not have abuse issues from these windows and many browser chrome |
46 | 0 | // tests have races that fail if we do throttle chrome runnables. |
47 | 0 | if (aIsChrome) { |
48 | 0 | MOZ_ASSERT(!sChromeTabGroup); |
49 | 0 | return; |
50 | 0 | } |
51 | 0 |
|
52 | 0 | // This constructor can be called from the IPC I/O thread. In that case, we |
53 | 0 | // won't actually use the TabGroup on the main thread until GetFromWindowActor |
54 | 0 | // is called, so we initialize the throttled queues there. |
55 | 0 | if (NS_IsMainThread()) { |
56 | 0 | EnsureThrottledEventQueues(); |
57 | 0 | } |
58 | 0 | } |
59 | | |
60 | | TabGroup::~TabGroup() |
61 | 0 | { |
62 | 0 | MOZ_ASSERT(mDocGroups.IsEmpty()); |
63 | 0 | MOZ_ASSERT(mWindows.IsEmpty()); |
64 | 0 | MOZ_RELEASE_ASSERT(mLastWindowLeft || mIsChrome); |
65 | 0 |
|
66 | 0 | LinkedListElement<TabGroup>* listElement = |
67 | 0 | static_cast<LinkedListElement<TabGroup>*>(this); |
68 | 0 | listElement->remove(); |
69 | 0 |
|
70 | 0 | if (sTabGroups->isEmpty()) { |
71 | 0 | delete sTabGroups; |
72 | 0 | sTabGroups = nullptr; |
73 | 0 | } |
74 | 0 | } |
75 | | |
76 | | void |
77 | | TabGroup::EnsureThrottledEventQueues() |
78 | 0 | { |
79 | 0 | if (mThrottledQueuesInitialized) { |
80 | 0 | return; |
81 | 0 | } |
82 | 0 | |
83 | 0 | mThrottledQueuesInitialized = true; |
84 | 0 |
|
85 | 0 | for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { |
86 | 0 | TaskCategory category = static_cast<TaskCategory>(i); |
87 | 0 | if (category == TaskCategory::Worker || category == TaskCategory::Timer) { |
88 | 0 | nsCOMPtr<nsISerialEventTarget> target = ThrottledEventQueue::Create(mEventTargets[i]); |
89 | 0 | if (target) { |
90 | 0 | // This may return nullptr during xpcom shutdown. This is ok as we |
91 | 0 | // do not guarantee a ThrottledEventQueue will be present. |
92 | 0 | mEventTargets[i] = target; |
93 | 0 | } |
94 | 0 | } |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | /* static */ TabGroup* |
99 | | TabGroup::GetChromeTabGroup() |
100 | 0 | { |
101 | 0 | if (!sChromeTabGroup) { |
102 | 0 | sChromeTabGroup = new TabGroup(true /* chrome tab group */); |
103 | 0 | ClearOnShutdown(&sChromeTabGroup); |
104 | 0 | } |
105 | 0 | return sChromeTabGroup; |
106 | 0 | } |
107 | | |
108 | | /* static */ TabGroup* |
109 | | TabGroup::GetFromWindow(mozIDOMWindowProxy* aWindow) |
110 | 0 | { |
111 | 0 | if (TabChild* tabChild = TabChild::GetFrom(aWindow)) { |
112 | 0 | return tabChild->TabGroup(); |
113 | 0 | } |
114 | 0 | |
115 | 0 | return nullptr; |
116 | 0 | } |
117 | | |
118 | | /* static */ TabGroup* |
119 | | TabGroup::GetFromActor(TabChild* aTabChild) |
120 | 0 | { |
121 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
122 | 0 |
|
123 | 0 | // Middleman processes do not assign event targets to their tab children. |
124 | 0 | if (recordreplay::IsMiddleman()) { |
125 | 0 | return GetChromeTabGroup(); |
126 | 0 | } |
127 | 0 | |
128 | 0 | nsCOMPtr<nsIEventTarget> target = aTabChild->Manager()->GetEventTargetFor(aTabChild); |
129 | 0 | if (!target) { |
130 | 0 | return nullptr; |
131 | 0 | } |
132 | 0 | |
133 | 0 | // We have an event target. We assume the IPC code created it via |
134 | 0 | // TabGroup::CreateEventTarget. |
135 | 0 | RefPtr<SchedulerGroup> group = |
136 | 0 | SchedulerGroup::FromEventTarget(target); |
137 | 0 | MOZ_RELEASE_ASSERT(group); |
138 | 0 | auto tabGroup = group->AsTabGroup(); |
139 | 0 | MOZ_RELEASE_ASSERT(tabGroup); |
140 | 0 |
|
141 | 0 | // We delay creating the event targets until now since the TabGroup |
142 | 0 | // constructor ran off the main thread. |
143 | 0 | tabGroup->EnsureThrottledEventQueues(); |
144 | 0 |
|
145 | 0 | return tabGroup; |
146 | 0 | } |
147 | | |
148 | | already_AddRefed<DocGroup> |
149 | | TabGroup::GetDocGroup(const nsACString& aKey) |
150 | 0 | { |
151 | 0 | RefPtr<DocGroup> docGroup(mDocGroups.GetEntry(aKey)->mDocGroup); |
152 | 0 | return docGroup.forget(); |
153 | 0 | } |
154 | | |
155 | | already_AddRefed<DocGroup> |
156 | | TabGroup::AddDocument(const nsACString& aKey, nsIDocument* aDocument) |
157 | 0 | { |
158 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
159 | 0 | HashEntry* entry = mDocGroups.PutEntry(aKey); |
160 | 0 | RefPtr<DocGroup> docGroup; |
161 | 0 | if (entry->mDocGroup) { |
162 | 0 | docGroup = entry->mDocGroup; |
163 | 0 | } else { |
164 | 0 | docGroup = new DocGroup(this, aKey); |
165 | 0 | entry->mDocGroup = docGroup; |
166 | 0 | } |
167 | 0 |
|
168 | 0 | // Make sure that the hashtable was updated and now contains the correct value |
169 | 0 | MOZ_ASSERT(RefPtr<DocGroup>(GetDocGroup(aKey)) == docGroup); |
170 | 0 |
|
171 | 0 | docGroup->mDocuments.AppendElement(aDocument); |
172 | 0 |
|
173 | 0 | return docGroup.forget(); |
174 | 0 | } |
175 | | |
176 | | /* static */ already_AddRefed<TabGroup> |
177 | | TabGroup::Join(nsPIDOMWindowOuter* aWindow, TabGroup* aTabGroup) |
178 | 0 | { |
179 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
180 | 0 | RefPtr<TabGroup> tabGroup = aTabGroup; |
181 | 0 | if (!tabGroup) { |
182 | 0 | tabGroup = new TabGroup(); |
183 | 0 | } |
184 | 0 | MOZ_RELEASE_ASSERT(!tabGroup->mLastWindowLeft); |
185 | 0 | MOZ_ASSERT(!tabGroup->mWindows.Contains(aWindow)); |
186 | 0 | tabGroup->mWindows.AppendElement(aWindow); |
187 | 0 |
|
188 | 0 | if (!aWindow->IsBackground()) { |
189 | 0 | tabGroup->mForegroundCount++; |
190 | 0 | } |
191 | 0 |
|
192 | 0 | return tabGroup.forget(); |
193 | 0 | } |
194 | | |
195 | | void |
196 | | TabGroup::Leave(nsPIDOMWindowOuter* aWindow) |
197 | 0 | { |
198 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
199 | 0 | MOZ_ASSERT(mWindows.Contains(aWindow)); |
200 | 0 | mWindows.RemoveElement(aWindow); |
201 | 0 |
|
202 | 0 | if (!aWindow->IsBackground()) { |
203 | 0 | MOZ_DIAGNOSTIC_ASSERT(mForegroundCount > 0); |
204 | 0 | mForegroundCount--; |
205 | 0 | } |
206 | 0 |
|
207 | 0 | // The Chrome TabGroup doesn't have cyclical references through mEventTargets |
208 | 0 | // to itself, meaning that we don't have to worry about nulling mEventTargets |
209 | 0 | // out after the last window leaves. |
210 | 0 | if (!mIsChrome && mWindows.IsEmpty()) { |
211 | 0 | mLastWindowLeft = true; |
212 | 0 | Shutdown(false); |
213 | 0 | } |
214 | 0 | } |
215 | | |
216 | | nsresult |
217 | | TabGroup::FindItemWithName(const nsAString& aName, |
218 | | nsIDocShellTreeItem* aRequestor, |
219 | | nsIDocShellTreeItem* aOriginalRequestor, |
220 | | nsIDocShellTreeItem** aFoundItem) |
221 | 0 | { |
222 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
223 | 0 | NS_ENSURE_ARG_POINTER(aFoundItem); |
224 | 0 | *aFoundItem = nullptr; |
225 | 0 |
|
226 | 0 | MOZ_ASSERT(!aName.LowerCaseEqualsLiteral("_blank") && |
227 | 0 | !aName.LowerCaseEqualsLiteral("_top") && |
228 | 0 | !aName.LowerCaseEqualsLiteral("_parent") && |
229 | 0 | !aName.LowerCaseEqualsLiteral("_self")); |
230 | 0 |
|
231 | 0 | for (nsPIDOMWindowOuter* outerWindow : mWindows) { |
232 | 0 | // Ignore non-toplevel windows |
233 | 0 | if (outerWindow->GetScriptableParentOrNull()) { |
234 | 0 | continue; |
235 | 0 | } |
236 | 0 | |
237 | 0 | nsCOMPtr<nsIDocShellTreeItem> docshell = outerWindow->GetDocShell(); |
238 | 0 | if (!docshell) { |
239 | 0 | continue; |
240 | 0 | } |
241 | 0 | |
242 | 0 | nsCOMPtr<nsIDocShellTreeItem> root; |
243 | 0 | docshell->GetSameTypeRootTreeItem(getter_AddRefs(root)); |
244 | 0 | MOZ_RELEASE_ASSERT(docshell == root); |
245 | 0 | if (root && aRequestor != root) { |
246 | 0 | root->FindItemWithName(aName, aRequestor, aOriginalRequestor, |
247 | 0 | /* aSkipTabGroup = */ true, aFoundItem); |
248 | 0 | if (*aFoundItem) { |
249 | 0 | break; |
250 | 0 | } |
251 | 0 | } |
252 | 0 | } |
253 | 0 |
|
254 | 0 | return NS_OK; |
255 | 0 | } |
256 | | |
257 | | nsTArray<nsPIDOMWindowOuter*> |
258 | | TabGroup::GetTopLevelWindows() const |
259 | 0 | { |
260 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
261 | 0 | nsTArray<nsPIDOMWindowOuter*> array; |
262 | 0 |
|
263 | 0 | for (nsPIDOMWindowOuter* outerWindow : mWindows) { |
264 | 0 | if (outerWindow->GetDocShell() && |
265 | 0 | !outerWindow->GetScriptableParentOrNull()) { |
266 | 0 | array.AppendElement(outerWindow); |
267 | 0 | } |
268 | 0 | } |
269 | 0 |
|
270 | 0 | return array; |
271 | 0 | } |
272 | | |
273 | | TabGroup::HashEntry::HashEntry(const nsACString* aKey) |
274 | | : nsCStringHashKey(aKey), mDocGroup(nullptr) |
275 | 0 | {} |
276 | | |
277 | | nsISerialEventTarget* |
278 | | TabGroup::EventTargetFor(TaskCategory aCategory) const |
279 | 0 | { |
280 | 0 | if (aCategory == TaskCategory::Worker || aCategory == TaskCategory::Timer) { |
281 | 0 | MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || mIsChrome); |
282 | 0 | } |
283 | 0 | return SchedulerGroup::EventTargetFor(aCategory); |
284 | 0 | } |
285 | | |
286 | | AbstractThread* |
287 | | TabGroup::AbstractMainThreadForImpl(TaskCategory aCategory) |
288 | 0 | { |
289 | 0 | // The mEventTargets of the chrome TabGroup are all set to do_GetMainThread(). |
290 | 0 | // We could just return AbstractThread::MainThread() without a wrapper. |
291 | 0 | // Once we've disconnected everything, we still allow people to dispatch. |
292 | 0 | // We'll just go directly to the main thread. |
293 | 0 | if (this == sChromeTabGroup || NS_WARN_IF(mLastWindowLeft)) { |
294 | 0 | return AbstractThread::MainThread(); |
295 | 0 | } |
296 | 0 | |
297 | 0 | return SchedulerGroup::AbstractMainThreadForImpl(aCategory); |
298 | 0 | } |
299 | | |
300 | | void |
301 | | TabGroup::WindowChangedBackgroundStatus(bool aIsNowBackground) |
302 | 0 | { |
303 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
304 | 0 |
|
305 | 0 | if (aIsNowBackground) { |
306 | 0 | MOZ_DIAGNOSTIC_ASSERT(mForegroundCount > 0); |
307 | 0 | mForegroundCount -= 1; |
308 | 0 | } else { |
309 | 0 | mForegroundCount += 1; |
310 | 0 | } |
311 | 0 | } |
312 | | |
313 | | bool |
314 | | TabGroup::IsBackground() const |
315 | 0 | { |
316 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
317 | 0 |
|
318 | | #ifdef DEBUG |
319 | | uint32_t foregrounded = 0; |
320 | | for (auto& window : mWindows) { |
321 | | if (!window->IsBackground()) { |
322 | | foregrounded++; |
323 | | } |
324 | | } |
325 | | MOZ_ASSERT(foregrounded == mForegroundCount); |
326 | | #endif |
327 | |
|
328 | 0 | return mForegroundCount == 0; |
329 | 0 | } |
330 | | |
331 | | uint32_t |
332 | | TabGroup::Count(bool aActiveOnly) const |
333 | 0 | { |
334 | 0 | if (!aActiveOnly) { |
335 | 0 | return mDocGroups.Count(); |
336 | 0 | } |
337 | 0 | |
338 | 0 | uint32_t count = 0; |
339 | 0 | for (auto iter = mDocGroups.ConstIter(); !iter.Done(); iter.Next()) { |
340 | 0 | if (iter.Get()->mDocGroup->IsActive()) { |
341 | 0 | ++count; |
342 | 0 | } |
343 | 0 | } |
344 | 0 |
|
345 | 0 | return count; |
346 | 0 | } |
347 | | |
348 | | /*static*/ bool |
349 | | TabGroup::HasOnlyThrottableTabs() |
350 | 0 | { |
351 | 0 | if (!sTabGroups) { |
352 | 0 | return false; |
353 | 0 | } |
354 | 0 | |
355 | 0 | for (TabGroup* tabGroup = sTabGroups->getFirst(); tabGroup; |
356 | 0 | tabGroup = |
357 | 0 | static_cast<LinkedListElement<TabGroup>*>(tabGroup)->getNext()) { |
358 | 0 | for (auto iter = tabGroup->Iter(); !iter.Done(); iter.Next()) { |
359 | 0 | DocGroup* docGroup = iter.Get()->mDocGroup; |
360 | 0 | for (auto* documentInDocGroup : *docGroup) { |
361 | 0 | if (documentInDocGroup->IsCurrentActiveDocument()) { |
362 | 0 | nsPIDOMWindowInner* win = |
363 | 0 | documentInDocGroup->GetInnerWindow(); |
364 | 0 | if (win && win->IsCurrentInnerWindow()) { |
365 | 0 | nsPIDOMWindowOuter* outer = win->GetOuterWindow(); |
366 | 0 | if (outer) { |
367 | 0 | TimeoutManager& tm = win->TimeoutManager(); |
368 | 0 | if (!tm.BudgetThrottlingEnabled(outer->IsBackground())) { |
369 | 0 | return false; |
370 | 0 | } |
371 | 0 | } |
372 | 0 | } |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | 0 | } |
377 | 0 | return true; |
378 | 0 | } |
379 | | |
380 | | } // namespace dom |
381 | | } // namespace mozilla |