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/. */
10 #include "ClientDOMUtil.h"
11 #include "mozilla/dom/ClientIPCTypes.h"
12 #include "mozilla/dom/ClientManager.h"
13 #include "mozilla/dom/ClientsBinding.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/ServiceWorkerDescriptor.h"
16 #include "mozilla/dom/ServiceWorkerManager.h"
17 #include "mozilla/dom/WorkerScope.h"
18 #include "mozilla/ipc/BackgroundUtils.h"
19 #include "mozilla/SchedulerGroup.h"
20 #include "mozilla/StaticPrefs_privacy.h"
21 #include "mozilla/StorageAccess.h"
22 #include "nsIGlobalObject.h"
24 #include "nsReadableUtils.h"
26 namespace mozilla::dom
{
28 using mozilla::ipc::CSPInfo
;
29 using mozilla::ipc::PrincipalInfo
;
31 NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients
);
32 NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients
);
33 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients
, mGlobal
);
35 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients
)
36 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
37 NS_INTERFACE_MAP_ENTRY(nsISupports
)
40 Clients::Clients(nsIGlobalObject
* aGlobal
) : mGlobal(aGlobal
) {
41 MOZ_DIAGNOSTIC_ASSERT(mGlobal
);
44 JSObject
* Clients::WrapObject(JSContext
* aCx
,
45 JS::Handle
<JSObject
*> aGivenProto
) {
46 return Clients_Binding::Wrap(aCx
, this, aGivenProto
);
49 nsIGlobalObject
* Clients::GetParentObject() const { return mGlobal
; }
51 already_AddRefed
<Promise
> Clients::Get(const nsAString
& aClientID
,
53 MOZ_ASSERT(!NS_IsMainThread());
54 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
55 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
);
56 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
->IsServiceWorker());
57 workerPrivate
->AssertIsOnWorkerThread();
59 RefPtr
<Promise
> outerPromise
= Promise::Create(mGlobal
, aRv
);
61 return outerPromise
.forget();
65 // nsID::Parse accepts both "{...}" and "...", but we only emit the latter, so
66 // forbid strings that start with "{" to avoid inconsistency and bugs like
68 if (aClientID
.IsEmpty() || aClientID
.CharAt(0) == '{' ||
69 !id
.Parse(NS_ConvertUTF16toUTF8(aClientID
).get())) {
70 // Invalid ID means we will definitely not find a match, so just
71 // resolve with undefined indicating "not found".
72 outerPromise
->MaybeResolveWithUndefined();
73 return outerPromise
.forget();
76 const PrincipalInfo
& principalInfo
= workerPrivate
->GetPrincipalInfo();
77 nsCOMPtr
<nsISerialEventTarget
> target
= mGlobal
->SerialEventTarget();
78 RefPtr
<ClientOpPromise
> innerPromise
= ClientManager::GetInfoAndState(
79 ClientGetInfoAndStateArgs(id
, principalInfo
), target
);
81 nsCString scope
= workerPrivate
->ServiceWorkerScope();
83 MakeRefPtr
<DOMMozPromiseRequestHolder
<ClientOpPromise
>>(mGlobal
);
88 [outerPromise
, holder
, scope
](const ClientOpResult
& aResult
) {
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 (StaticPrefs::privacy_partition_serviceWorkers() &&
95 ShouldPartitionStorage(client
->GetStorageAccess()))) {
96 outerPromise
->MaybeResolve(std::move(client
));
99 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
100 "Clients::Get() storage denied", [scope
] {
101 ServiceWorkerManager::LocalizeAndReportToAllClients(
102 scope
, "ServiceWorkerGetClientStorageError",
103 nsTArray
<nsString
>());
105 SchedulerGroup::Dispatch(r
.forget());
106 outerPromise
->MaybeResolveWithUndefined();
108 [outerPromise
, holder
](const CopyableErrorResult
& aResult
) {
110 outerPromise
->MaybeResolveWithUndefined();
114 return outerPromise
.forget();
119 class MatchAllComparator final
{
121 bool LessThan(Client
* aLeft
, Client
* aRight
) const {
122 TimeStamp leftFocusTime
= aLeft
->LastFocusTime();
123 TimeStamp rightFocusTime
= aRight
->LastFocusTime();
124 // If the focus times are the same, then default to creation order.
125 // MatchAll should return oldest Clients first.
126 if (leftFocusTime
== rightFocusTime
) {
127 return aLeft
->CreationTime() < aRight
->CreationTime();
130 // Otherwise compare focus times. We reverse the logic here so
131 // that the most recently focused window is first in the list.
132 if (!leftFocusTime
.IsNull() && rightFocusTime
.IsNull()) {
135 if (leftFocusTime
.IsNull() && !rightFocusTime
.IsNull()) {
138 return leftFocusTime
> rightFocusTime
;
141 bool Equals(Client
* aLeft
, Client
* aRight
) const {
142 return aLeft
->LastFocusTime() == aRight
->LastFocusTime() &&
143 aLeft
->CreationTime() == aRight
->CreationTime();
147 } // anonymous namespace
149 already_AddRefed
<Promise
> Clients::MatchAll(const ClientQueryOptions
& aOptions
,
151 MOZ_ASSERT(!NS_IsMainThread());
152 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
153 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
);
154 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
->IsServiceWorker());
155 workerPrivate
->AssertIsOnWorkerThread();
157 RefPtr
<Promise
> outerPromise
= Promise::Create(mGlobal
, aRv
);
159 return outerPromise
.forget();
162 nsCOMPtr
<nsIGlobalObject
> global
= mGlobal
;
163 nsCString scope
= workerPrivate
->ServiceWorkerScope();
165 ClientMatchAllArgs
args(workerPrivate
->GetServiceWorkerDescriptor().ToIPC(),
166 aOptions
.mType
, aOptions
.mIncludeUncontrolled
);
167 StartClientManagerOp(
168 &ClientManager::MatchAll
, args
, mGlobal
,
169 [outerPromise
, global
, scope
](const ClientOpResult
& aResult
) {
170 nsTArray
<RefPtr
<Client
>> clientList
;
171 bool storageDenied
= false;
172 for (const ClientInfoAndState
& value
:
173 aResult
.get_ClientList().values()) {
174 RefPtr
<Client
> client
= new Client(global
, value
);
175 if (client
->GetStorageAccess() != StorageAccess::eAllow
&&
176 (!StaticPrefs::privacy_partition_serviceWorkers() ||
177 !ShouldPartitionStorage(client
->GetStorageAccess()))) {
178 storageDenied
= true;
181 clientList
.AppendElement(std::move(client
));
184 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
185 "Clients::MatchAll() storage denied", [scope
] {
186 ServiceWorkerManager::LocalizeAndReportToAllClients(
187 scope
, "ServiceWorkerGetClientStorageError",
188 nsTArray
<nsString
>());
190 SchedulerGroup::Dispatch(r
.forget());
192 clientList
.Sort(MatchAllComparator());
193 outerPromise
->MaybeResolve(clientList
);
195 [outerPromise
](const CopyableErrorResult
& aResult
) {
196 // MaybeReject needs a non-const-ref result, so make a copy.
197 outerPromise
->MaybeReject(CopyableErrorResult(aResult
));
200 return outerPromise
.forget();
203 already_AddRefed
<Promise
> Clients::OpenWindow(const nsAString
& aURL
,
205 MOZ_ASSERT(!NS_IsMainThread());
206 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
207 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
);
208 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
->IsServiceWorker());
209 workerPrivate
->AssertIsOnWorkerThread();
211 RefPtr
<Promise
> outerPromise
= Promise::Create(mGlobal
, aRv
);
213 return outerPromise
.forget();
216 if (aURL
.EqualsLiteral(u
"about:blank") ||
217 StringBeginsWith(aURL
, u
"about:blank?"_ns
) ||
218 StringBeginsWith(aURL
, u
"about:blank#"_ns
)) {
219 CopyableErrorResult rv
;
221 "Passing \"about:blank\" to Clients.openWindow is not allowed");
222 outerPromise
->MaybeReject(std::move(rv
));
223 return outerPromise
.forget();
226 if (!workerPrivate
->GlobalScope()->WindowInteractionAllowed()) {
227 outerPromise
->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
228 return outerPromise
.forget();
231 const PrincipalInfo
& principalInfo
= workerPrivate
->GetPrincipalInfo();
232 const CSPInfo
& cspInfo
= workerPrivate
->GetCSPInfo();
233 nsCString baseURL
= workerPrivate
->GetLocationInfo().mHref
;
235 ClientOpenWindowArgs
args(principalInfo
, Some(cspInfo
),
236 NS_ConvertUTF16toUTF8(aURL
), baseURL
);
238 nsCOMPtr
<nsIGlobalObject
> global
= mGlobal
;
240 StartClientManagerOp(
241 &ClientManager::OpenWindow
, args
, mGlobal
,
242 [outerPromise
, global
](const ClientOpResult
& aResult
) {
243 if (aResult
.type() != ClientOpResult::TClientInfoAndState
) {
244 outerPromise
->MaybeResolve(JS::NullHandleValue
);
247 RefPtr
<Client
> client
=
248 new Client(global
, aResult
.get_ClientInfoAndState());
249 outerPromise
->MaybeResolve(client
);
251 [outerPromise
](const CopyableErrorResult
& aResult
) {
252 // MaybeReject needs a non-const-ref result, so make a copy.
253 outerPromise
->MaybeReject(CopyableErrorResult(aResult
));
256 return outerPromise
.forget();
259 already_AddRefed
<Promise
> Clients::Claim(ErrorResult
& aRv
) {
260 MOZ_ASSERT(!NS_IsMainThread());
261 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
262 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
);
263 MOZ_DIAGNOSTIC_ASSERT(workerPrivate
->IsServiceWorker());
264 workerPrivate
->AssertIsOnWorkerThread();
266 RefPtr
<Promise
> outerPromise
= Promise::Create(mGlobal
, aRv
);
268 return outerPromise
.forget();
271 const ServiceWorkerDescriptor
& serviceWorker
=
272 workerPrivate
->GetServiceWorkerDescriptor();
274 if (serviceWorker
.State() != ServiceWorkerState::Activating
&&
275 serviceWorker
.State() != ServiceWorkerState::Activated
) {
276 aRv
.ThrowInvalidStateError("Service worker is not active");
277 return outerPromise
.forget();
280 StartClientManagerOp(
281 &ClientManager::Claim
, ClientClaimArgs(serviceWorker
.ToIPC()), mGlobal
,
282 [outerPromise
](const ClientOpResult
& aResult
) {
283 outerPromise
->MaybeResolveWithUndefined();
285 [outerPromise
](const CopyableErrorResult
& aResult
) {
286 // MaybeReject needs a non-const-ref result, so make a copy.
287 outerPromise
->MaybeReject(CopyableErrorResult(aResult
));
290 return outerPromise
.forget();
293 } // namespace mozilla::dom