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