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 "Clients.h"
8
9 #include "ClientDOMUtil.h"
10 #include "mozilla/dom/ClientIPCTypes.h"
11 #include "mozilla/dom/ClientManager.h"
12 #include "mozilla/dom/ClientsBinding.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/ServiceWorkerDescriptor.h"
15 #include "mozilla/dom/ServiceWorkerManager.h"
16 #include "mozilla/dom/WorkerPrivate.h"
17 #include "mozilla/ipc/BackgroundUtils.h"
18 #include "mozilla/SchedulerGroup.h"
19 #include "mozilla/StorageAccess.h"
20 #include "nsIGlobalObject.h"
21 #include "nsString.h"
22
23 namespace mozilla {
24 namespace dom {
25
26 using mozilla::ipc::CSPInfo;
27 using mozilla::ipc::PrincipalInfo;
28
29 NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients);
30 NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients);
31 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal);
32
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)
34 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
35 NS_INTERFACE_MAP_ENTRY(nsISupports)
36 NS_INTERFACE_MAP_END
37
38 Clients::Clients(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
39 MOZ_DIAGNOSTIC_ASSERT(mGlobal);
40 }
41
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)42 JSObject* Clients::WrapObject(JSContext* aCx,
43 JS::Handle<JSObject*> aGivenProto) {
44 return Clients_Binding::Wrap(aCx, this, aGivenProto);
45 }
46
GetParentObject() const47 nsIGlobalObject* Clients::GetParentObject() const { return mGlobal; }
48
Get(const nsAString & aClientID,ErrorResult & aRv)49 already_AddRefed<Promise> Clients::Get(const nsAString& aClientID,
50 ErrorResult& aRv) {
51 MOZ_ASSERT(!NS_IsMainThread());
52 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
53 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
54 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
55 workerPrivate->AssertIsOnWorkerThread();
56
57 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
58 if (aRv.Failed()) {
59 return outerPromise.forget();
60 }
61
62 nsID id;
63 // nsID::Parse accepts both "{...}" and "...", but we only emit the latter, so
64 // forbid strings that start with "{" to avoid inconsistency and bugs like
65 // bug 1446225.
66 if (aClientID.IsEmpty() || aClientID.CharAt(0) == '{' ||
67 !id.Parse(NS_ConvertUTF16toUTF8(aClientID).get())) {
68 // Invalid ID means we will definitely not find a match, so just
69 // resolve with undefined indicating "not found".
70 outerPromise->MaybeResolveWithUndefined();
71 return outerPromise.forget();
72 }
73
74 const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
75 nsCOMPtr<nsISerialEventTarget> target =
76 mGlobal->EventTargetFor(TaskCategory::Other);
77
78 RefPtr<ClientOpPromise> innerPromise = ClientManager::GetInfoAndState(
79 ClientGetInfoAndStateArgs(id, principalInfo), target);
80
81 nsCString scope = workerPrivate->ServiceWorkerScope();
82 auto holder =
83 MakeRefPtr<DOMMozPromiseRequestHolder<ClientOpPromise>>(mGlobal);
84
85 innerPromise
86 ->Then(
87 target, __func__,
88 [outerPromise, holder, scope](const ClientOpResult& aResult) {
89 holder->Complete();
90 NS_ENSURE_TRUE_VOID(holder->GetParentObject());
91 RefPtr<Client> client = new Client(
92 holder->GetParentObject(), aResult.get_ClientInfoAndState());
93 if (client->GetStorageAccess() == StorageAccess::eAllow) {
94 outerPromise->MaybeResolve(std::move(client));
95 return;
96 }
97 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
98 "Clients::Get() storage denied", [scope] {
99 ServiceWorkerManager::LocalizeAndReportToAllClients(
100 scope, "ServiceWorkerGetClientStorageError",
101 nsTArray<nsString>());
102 });
103 SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
104 outerPromise->MaybeResolveWithUndefined();
105 },
106 [outerPromise, holder](const CopyableErrorResult& aResult) {
107 holder->Complete();
108 outerPromise->MaybeResolveWithUndefined();
109 })
110 ->Track(*holder);
111
112 return outerPromise.forget();
113 }
114
115 namespace {
116
117 class MatchAllComparator final {
118 public:
LessThan(Client * aLeft,Client * aRight) const119 bool LessThan(Client* aLeft, Client* aRight) const {
120 TimeStamp leftFocusTime = aLeft->LastFocusTime();
121 TimeStamp rightFocusTime = aRight->LastFocusTime();
122 // If the focus times are the same, then default to creation order.
123 // MatchAll should return oldest Clients first.
124 if (leftFocusTime == rightFocusTime) {
125 return aLeft->CreationTime() < aRight->CreationTime();
126 }
127
128 // Otherwise compare focus times. We reverse the logic here so
129 // that the most recently focused window is first in the list.
130 if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) {
131 return true;
132 }
133 if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) {
134 return false;
135 }
136 return leftFocusTime > rightFocusTime;
137 }
138
Equals(Client * aLeft,Client * aRight) const139 bool Equals(Client* aLeft, Client* aRight) const {
140 return aLeft->LastFocusTime() == aRight->LastFocusTime() &&
141 aLeft->CreationTime() == aRight->CreationTime();
142 }
143 };
144
145 } // anonymous namespace
146
MatchAll(const ClientQueryOptions & aOptions,ErrorResult & aRv)147 already_AddRefed<Promise> Clients::MatchAll(const ClientQueryOptions& aOptions,
148 ErrorResult& aRv) {
149 MOZ_ASSERT(!NS_IsMainThread());
150 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
151 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
152 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
153 workerPrivate->AssertIsOnWorkerThread();
154
155 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
156 if (aRv.Failed()) {
157 return outerPromise.forget();
158 }
159
160 nsCOMPtr<nsIGlobalObject> global = mGlobal;
161 nsCString scope = workerPrivate->ServiceWorkerScope();
162
163 ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(),
164 aOptions.mType, aOptions.mIncludeUncontrolled);
165 StartClientManagerOp(
166 &ClientManager::MatchAll, args, mGlobal,
167 [outerPromise, global, scope](const ClientOpResult& aResult) {
168 nsTArray<RefPtr<Client>> clientList;
169 bool storageDenied = false;
170 for (const ClientInfoAndState& value :
171 aResult.get_ClientList().values()) {
172 RefPtr<Client> client = new Client(global, value);
173 if (client->GetStorageAccess() != StorageAccess::eAllow) {
174 storageDenied = true;
175 continue;
176 }
177 clientList.AppendElement(std::move(client));
178 }
179 if (storageDenied) {
180 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
181 "Clients::MatchAll() storage denied", [scope] {
182 ServiceWorkerManager::LocalizeAndReportToAllClients(
183 scope, "ServiceWorkerGetClientStorageError",
184 nsTArray<nsString>());
185 });
186 SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
187 }
188 clientList.Sort(MatchAllComparator());
189 outerPromise->MaybeResolve(clientList);
190 },
191 [outerPromise](const CopyableErrorResult& aResult) {
192 // MaybeReject needs a non-const-ref result, so make a copy.
193 outerPromise->MaybeReject(CopyableErrorResult(aResult));
194 });
195
196 return outerPromise.forget();
197 }
198
OpenWindow(const nsAString & aURL,ErrorResult & aRv)199 already_AddRefed<Promise> Clients::OpenWindow(const nsAString& aURL,
200 ErrorResult& aRv) {
201 MOZ_ASSERT(!NS_IsMainThread());
202 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
203 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
204 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
205 workerPrivate->AssertIsOnWorkerThread();
206
207 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
208 if (aRv.Failed()) {
209 return outerPromise.forget();
210 }
211
212 if (aURL.EqualsLiteral("about:blank")) {
213 CopyableErrorResult rv;
214 rv.ThrowTypeError(
215 "Passing \"about:blank\" to Clients.openWindow is not allowed");
216 outerPromise->MaybeReject(std::move(rv));
217 return outerPromise.forget();
218 }
219
220 if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
221 outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
222 return outerPromise.forget();
223 }
224
225 const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
226 const CSPInfo& cspInfo = workerPrivate->GetCSPInfo();
227 nsCString baseURL = workerPrivate->GetLocationInfo().mHref;
228
229 ClientOpenWindowArgs args(principalInfo, Some(cspInfo),
230 NS_ConvertUTF16toUTF8(aURL), baseURL);
231
232 nsCOMPtr<nsIGlobalObject> global = mGlobal;
233
234 StartClientManagerOp(
235 &ClientManager::OpenWindow, args, mGlobal,
236 [outerPromise, global](const ClientOpResult& aResult) {
237 if (aResult.type() != ClientOpResult::TClientInfoAndState) {
238 outerPromise->MaybeResolve(JS::NullHandleValue);
239 return;
240 }
241 RefPtr<Client> client =
242 new Client(global, aResult.get_ClientInfoAndState());
243 outerPromise->MaybeResolve(client);
244 },
245 [outerPromise](const CopyableErrorResult& aResult) {
246 // MaybeReject needs a non-const-ref result, so make a copy.
247 outerPromise->MaybeReject(CopyableErrorResult(aResult));
248 });
249
250 return outerPromise.forget();
251 }
252
Claim(ErrorResult & aRv)253 already_AddRefed<Promise> Clients::Claim(ErrorResult& aRv) {
254 MOZ_ASSERT(!NS_IsMainThread());
255 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
256 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
257 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
258 workerPrivate->AssertIsOnWorkerThread();
259
260 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
261 if (aRv.Failed()) {
262 return outerPromise.forget();
263 }
264
265 const ServiceWorkerDescriptor& serviceWorker =
266 workerPrivate->GetServiceWorkerDescriptor();
267
268 if (serviceWorker.State() != ServiceWorkerState::Activating &&
269 serviceWorker.State() != ServiceWorkerState::Activated) {
270 aRv.ThrowInvalidStateError("Service worker is not active");
271 return outerPromise.forget();
272 }
273
274 StartClientManagerOp(
275 &ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()), mGlobal,
276 [outerPromise](const ClientOpResult& aResult) {
277 outerPromise->MaybeResolveWithUndefined();
278 },
279 [outerPromise](const CopyableErrorResult& aResult) {
280 // MaybeReject needs a non-const-ref result, so make a copy.
281 outerPromise->MaybeReject(CopyableErrorResult(aResult));
282 });
283
284 return outerPromise.forget();
285 }
286
287 } // namespace dom
288 } // namespace mozilla
289