Clients.cpp (10703B)
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 "Client.h" 10 #include "ClientDOMUtil.h" 11 #include "mozilla/SchedulerGroup.h" 12 #include "mozilla/StaticPrefs_privacy.h" 13 #include "mozilla/dom/ClientIPCTypes.h" 14 #include "mozilla/dom/ClientManager.h" 15 #include "mozilla/dom/ClientsBinding.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/ServiceWorkerDescriptor.h" 18 #include "mozilla/dom/ServiceWorkerManager.h" 19 #include "mozilla/dom/ServiceWorkerUtils.h" 20 #include "mozilla/dom/WorkerScope.h" 21 #include "mozilla/ipc/BackgroundUtils.h" 22 #include "nsIGlobalObject.h" 23 #include "nsReadableUtils.h" 24 #include "nsString.h" 25 26 namespace mozilla::dom { 27 28 using mozilla::ipc::CSPInfo; 29 using mozilla::ipc::PrincipalInfo; 30 31 NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients); 32 NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients); 33 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal); 34 35 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients) 36 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 37 NS_INTERFACE_MAP_ENTRY(nsISupports) 38 NS_INTERFACE_MAP_END 39 40 Clients::Clients(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) { 41 MOZ_DIAGNOSTIC_ASSERT(mGlobal); 42 } 43 44 JSObject* Clients::WrapObject(JSContext* aCx, 45 JS::Handle<JSObject*> aGivenProto) { 46 return Clients_Binding::Wrap(aCx, this, aGivenProto); 47 } 48 49 nsIGlobalObject* Clients::GetParentObject() const { return mGlobal; } 50 51 already_AddRefed<Promise> Clients::Get(const nsAString& aClientID, 52 ErrorResult& aRv) { 53 MOZ_ASSERT(!NS_IsMainThread()); 54 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 55 MOZ_DIAGNOSTIC_ASSERT(workerPrivate); 56 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker()); 57 workerPrivate->AssertIsOnWorkerThread(); 58 59 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv); 60 if (aRv.Failed()) { 61 return outerPromise.forget(); 62 } 63 64 nsID id; 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 67 // bug 1446225. 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(); 74 } 75 76 const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo(); 77 nsCOMPtr<nsISerialEventTarget> target = mGlobal->SerialEventTarget(); 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 if (ServiceWorkersStorageAllowedForClient( 92 aResult.get_ClientInfoAndState())) { 93 RefPtr<Client> client = new Client( 94 holder->GetParentObject(), aResult.get_ClientInfoAndState()); 95 outerPromise->MaybeResolve(std::move(client)); 96 return; 97 } 98 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 99 "Clients::Get() storage denied", [scope] { 100 ServiceWorkerManager::LocalizeAndReportToAllClients( 101 scope, "ServiceWorkerGetClientStorageError", 102 nsTArray<nsString>()); 103 }); 104 SchedulerGroup::Dispatch(r.forget()); 105 outerPromise->MaybeResolveWithUndefined(); 106 }, 107 [outerPromise, holder](const CopyableErrorResult& aResult) { 108 holder->Complete(); 109 outerPromise->MaybeResolveWithUndefined(); 110 }) 111 ->Track(*holder); 112 113 return outerPromise.forget(); 114 } 115 116 namespace { 117 118 class MatchAllComparator final { 119 public: 120 bool LessThan(Client* aLeft, Client* aRight) const { 121 TimeStamp leftFocusTime = aLeft->LastFocusTime(); 122 TimeStamp rightFocusTime = aRight->LastFocusTime(); 123 // If the focus times are the same, then default to creation order. 124 // MatchAll should return oldest Clients first. 125 if (leftFocusTime == rightFocusTime) { 126 return aLeft->CreationTime() < aRight->CreationTime(); 127 } 128 129 // Otherwise compare focus times. We reverse the logic here so 130 // that the most recently focused window is first in the list. 131 if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) { 132 return true; 133 } 134 if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) { 135 return false; 136 } 137 return leftFocusTime > rightFocusTime; 138 } 139 140 bool Equals(Client* aLeft, Client* aRight) const { 141 return aLeft->LastFocusTime() == aRight->LastFocusTime() && 142 aLeft->CreationTime() == aRight->CreationTime(); 143 } 144 }; 145 146 } // anonymous namespace 147 148 already_AddRefed<Promise> Clients::MatchAll(const ClientQueryOptions& aOptions, 149 ErrorResult& aRv) { 150 MOZ_ASSERT(!NS_IsMainThread()); 151 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 152 MOZ_DIAGNOSTIC_ASSERT(workerPrivate); 153 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker()); 154 workerPrivate->AssertIsOnWorkerThread(); 155 156 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv); 157 if (aRv.Failed()) { 158 return outerPromise.forget(); 159 } 160 161 nsCOMPtr<nsIGlobalObject> global = mGlobal; 162 nsCString scope = workerPrivate->ServiceWorkerScope(); 163 164 ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(), 165 aOptions.mType, aOptions.mIncludeUncontrolled); 166 StartClientManagerOp( 167 &ClientManager::MatchAll, args, mGlobal, 168 [outerPromise, global, scope](const ClientOpResult& aResult) { 169 nsTArray<RefPtr<Client>> clientList; 170 bool storageDenied = false; 171 for (const ClientInfoAndState& value : 172 aResult.get_ClientList().values()) { 173 if (!ServiceWorkersStorageAllowedForClient(value)) { 174 storageDenied = true; 175 continue; 176 } 177 clientList.AppendElement(new Client(global, value)); 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(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 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(u"about:blank") || 213 StringBeginsWith(aURL, u"about:blank?"_ns) || 214 StringBeginsWith(aURL, u"about:blank#"_ns)) { 215 CopyableErrorResult rv; 216 rv.ThrowTypeError( 217 "Passing \"about:blank\" to Clients.openWindow is not allowed"); 218 outerPromise->MaybeReject(std::move(rv)); 219 return outerPromise.forget(); 220 } 221 222 if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) { 223 outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR); 224 return outerPromise.forget(); 225 } 226 227 const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo(); 228 const CSPInfo& cspInfo = workerPrivate->GetCSPInfo(); 229 nsCString baseURL = workerPrivate->GetLocationInfo().mHref; 230 231 ClientOpenWindowArgs args(principalInfo, Some(cspInfo), 232 NS_ConvertUTF16toUTF8(aURL), baseURL); 233 234 nsCOMPtr<nsIGlobalObject> global = mGlobal; 235 236 StartClientManagerOp( 237 &ClientManager::OpenWindow, args, mGlobal, 238 [outerPromise, global](const ClientOpResult& aResult) { 239 if (aResult.type() != ClientOpResult::TClientInfoAndState) { 240 outerPromise->MaybeResolve(JS::NullHandleValue); 241 return; 242 } 243 RefPtr<Client> client = 244 new Client(global, aResult.get_ClientInfoAndState()); 245 outerPromise->MaybeResolve(client); 246 }, 247 [outerPromise](const CopyableErrorResult& aResult) { 248 // MaybeReject needs a non-const-ref result, so make a copy. 249 outerPromise->MaybeReject(CopyableErrorResult(aResult)); 250 }); 251 252 return outerPromise.forget(); 253 } 254 255 already_AddRefed<Promise> Clients::Claim(ErrorResult& aRv) { 256 MOZ_ASSERT(!NS_IsMainThread()); 257 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 258 MOZ_DIAGNOSTIC_ASSERT(workerPrivate); 259 MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker()); 260 workerPrivate->AssertIsOnWorkerThread(); 261 262 RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv); 263 if (aRv.Failed()) { 264 return outerPromise.forget(); 265 } 266 267 const ServiceWorkerDescriptor& serviceWorker = 268 workerPrivate->GetServiceWorkerDescriptor(); 269 270 if (serviceWorker.State() != ServiceWorkerState::Activating && 271 serviceWorker.State() != ServiceWorkerState::Activated) { 272 aRv.ThrowInvalidStateError("Service worker is not active"); 273 return outerPromise.forget(); 274 } 275 276 StartClientManagerOp( 277 &ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()), mGlobal, 278 [outerPromise](const ClientOpResult& aResult) { 279 outerPromise->MaybeResolveWithUndefined(); 280 }, 281 [outerPromise](const CopyableErrorResult& aResult) { 282 // MaybeReject needs a non-const-ref result, so make a copy. 283 outerPromise->MaybeReject(CopyableErrorResult(aResult)); 284 }); 285 286 return outerPromise.forget(); 287 } 288 289 } // namespace mozilla::dom