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