Bug 1293277 P4 Add Client and Clients DOM classes, but don't hook them into bindings yet. r=baku
authorBen Kelly <ben@wanderview.com>
Tue, 12 Dec 2017 15:44:47 -0500
changeset 396154 f7b0cd514d46f242222aa50e498b85f89b7e668a
parent 396153 6d09b4467c62da66ce870de1a90d310cc7abb858
child 396155 63938d2d5492022b7d3808d11a87faf83742ed6b
push id33081
push usercsabou@mozilla.com
push dateWed, 13 Dec 2017 10:14:59 +0000
treeherdermozilla-central@defccba824aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1293277
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1293277 P4 Add Client and Clients DOM classes, but don't hook them into bindings yet. r=baku
dom/clients/api/Client.cpp
dom/clients/api/Client.h
dom/clients/api/ClientDOMUtil.h
dom/clients/api/Clients.cpp
dom/clients/api/Clients.h
dom/clients/api/moz.build
dom/clients/moz.build
dom/indexedDB/ActorsParent.cpp
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/Client.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Client.h"
+
+#include "ClientDOMUtil.h"
+#include "mozilla/dom/ClientHandle.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::dom::workers::Closing;
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::WorkerHolderToken;
+using mozilla::dom::workers::WorkerPrivate;
+using mozilla::dom::ipc::StructuredCloneData;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::Client);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::Client);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::Client, mGlobal);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(mozilla::dom::Client)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void
+Client::EnsureHandle()
+{
+  NS_ASSERT_OWNINGTHREAD(mozilla::dom::Client);
+  if (!mHandle) {
+    mHandle = ClientManager::CreateHandle(ClientInfo(mData->info()),
+                                          mGlobal->EventTargetFor(TaskCategory::Other));
+  }
+}
+
+Client::Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData)
+  : mGlobal(aGlobal)
+  , mData(MakeUnique<ClientInfoAndState>(aData))
+{
+  MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+}
+
+TimeStamp
+Client::CreationTime() const
+{
+  return mData->info().creationTime();
+}
+
+TimeStamp
+Client::LastFocusTime() const
+{
+  if (mData->info().type() != ClientType::Window) {
+    return TimeStamp();
+  }
+  return mData->state().get_IPCClientWindowState().lastFocusTime();
+}
+
+nsContentUtils::StorageAccess
+Client::GetStorageAccess() const
+{
+  ClientState state(ClientState::FromIPC(mData->state()));
+  return state.GetStorageAccess();
+}
+
+JSObject*
+Client::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+#if 0
+  // TODO: Enable when bindings are updated to point to this class.
+  if (mData->info().type() == ClientType::Window) {
+    return WindowClientBinding::Wrap(aCx, this, aGivenProto);
+  }
+  return ClientBinding::Wrap(aCx, this, aGivenProto);
+#endif
+  return nullptr;
+}
+
+nsIGlobalObject*
+Client::GetParentObject() const
+{
+  return mGlobal;
+}
+
+void
+Client::GetUrl(nsAString& aUrlOut) const
+{
+  CopyUTF8toUTF16(mData->info().url(), aUrlOut);
+}
+
+void
+Client::GetId(nsAString& aIdOut) const
+{
+  char buf[NSID_LENGTH];
+  mData->info().id().ToProvidedString(buf);
+  NS_ConvertASCIItoUTF16 uuid(buf);
+
+  // Remove {} and the null terminator
+  aIdOut.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
+}
+
+ClientType
+Client::Type() const
+{
+  return mData->info().type();
+}
+
+FrameType
+Client::GetFrameType() const
+{
+  return mData->info().frameType();
+}
+
+void
+Client::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                    const Sequence<JSObject*>& aTransferable,
+                    ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+                                                          &transferable);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  StructuredCloneData data;
+  data.Write(aCx, aMessage, transferable, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  EnsureHandle();
+  mHandle->PostMessage(data, workerPrivate->GetServiceWorkerDescriptor());
+}
+
+VisibilityState
+Client::GetVisibilityState() const
+{
+  return mData->state().get_IPCClientWindowState().visibilityState();
+}
+
+bool
+Client::Focused() const
+{
+  return mData->state().get_IPCClientWindowState().focused();
+}
+
+already_AddRefed<Promise>
+Client::Focus(ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+    outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return outerPromise.forget();
+  }
+
+  // Hold the worker thread alive while we perform the async operation
+  // and also avoid invoking callbacks if the worker starts shutting
+  // down.
+  RefPtr<WorkerHolderToken> token =
+    WorkerHolderToken::Create(GetCurrentThreadWorkerPrivate(), Closing);
+
+  EnsureHandle();
+  RefPtr<ClientStatePromise> innerPromise = mHandle->Focus();
+  RefPtr<Client> self = this;
+
+  innerPromise->Then(mGlobal->EventTargetFor(TaskCategory::Other), __func__,
+    [self, token, outerPromise] (const ClientState& aResult) {
+      if (token->IsShuttingDown()) {
+        return;
+      }
+      RefPtr<Client> newClient =
+        new Client(self->mGlobal, ClientInfoAndState(self->mData->info(), aResult.ToIPC()));
+      outerPromise->MaybeResolve(newClient);
+    }, [self, token, outerPromise] (nsresult aResult) {
+      if (token->IsShuttingDown()) {
+        return;
+      }
+      outerPromise->MaybeReject(aResult);
+    });
+
+  return outerPromise.forget();
+}
+
+already_AddRefed<Promise>
+Client::Navigate(const nsAString& aURL, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  ClientNavigateArgs args(mData->info(), NS_ConvertUTF16toUTF8(aURL),
+                          workerPrivate->GetLocationInfo().mHref);
+  RefPtr<Client> self = this;
+
+  StartClientManagerOp(&ClientManager::Navigate, args,
+    mGlobal->EventTargetFor(TaskCategory::Other),
+    [self, outerPromise] (const ClientOpResult& aResult) {
+      if (aResult.type() != ClientOpResult::TClientInfoAndState) {
+        outerPromise->MaybeResolve(JS::NullHandleValue);
+        return;
+      }
+      RefPtr<Client> newClient =
+        new Client(self->mGlobal, aResult.get_ClientInfoAndState());
+      outerPromise->MaybeResolve(newClient);
+    }, [self, outerPromise] (nsresult aResult) {
+      // TODO: Improve this error in bug 1412856.  Ideally we should throw
+      //       the TypeError in the child process and pass it back to here.
+      outerPromise->MaybeReject(NS_ERROR_TYPE_ERR);
+    });
+
+  return outerPromise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/Client.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_Client_h
+#define _mozilla_dom_Client_h
+
+#include "mozilla/dom/ClientBinding.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class ClientHandle;
+class ClientInfoAndState;
+class Promise;
+
+template <typename t> class Sequence;
+
+class Client final : public nsISupports
+                   , public nsWrapperCache
+{
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  UniquePtr<ClientInfoAndState> mData;
+  RefPtr<ClientHandle> mHandle;
+
+  ~Client() = default;
+
+  void
+  EnsureHandle();
+
+public:
+  Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData);
+
+  TimeStamp
+  CreationTime() const;
+
+  TimeStamp
+  LastFocusTime() const;
+
+  nsContentUtils::StorageAccess
+  GetStorageAccess() const;
+
+  // nsWrapperCache interface methods
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // DOM bindings methods
+  nsIGlobalObject*
+  GetParentObject() const;
+
+  // Client Bindings
+  void
+  GetUrl(nsAString& aUrlOut) const;
+
+  void
+  GetId(nsAString& aIdOut) const;
+
+  ClientType
+  Type() const;
+
+  FrameType
+  GetFrameType() const;
+
+  // WindowClient bindings
+  VisibilityState
+  GetVisibilityState() const;
+
+  bool
+  Focused() const;
+
+  already_AddRefed<Promise>
+  Focus(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Navigate(const nsAString& aURL, ErrorResult& aRv);
+
+  void
+  PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+              const Sequence<JSObject*>& aTransferrable,
+              ErrorResult& aRv);
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(mozilla::dom::Client)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_Client_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/ClientDOMUtil.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_ClientDOMUtil_h
+#define _mozilla_dom_ClientDOMUtil_h
+
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/bindings/WorkerHolderToken.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+// Utility method to properly execute a ClientManager operation.  It
+// will properly hold a worker thread alive and avoid executing callbacks
+// if the thread is shutting down.
+template<typename Func, typename Arg, typename Resolve, typename Reject>
+void
+StartClientManagerOp(Func aFunc, const Arg& aArg, nsISerialEventTarget* aTarget,
+                     Resolve aResolve, Reject aReject)
+{
+  using mozilla::dom::workers::Closing;
+  using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+  using mozilla::dom::workers::WorkerHolderToken;
+
+  RefPtr<WorkerHolderToken> token;
+  if (!NS_IsMainThread()) {
+    token = WorkerHolderToken::Create(GetCurrentThreadWorkerPrivate(), Closing);
+  }
+
+  RefPtr<ClientOpPromise> promise = aFunc(aArg, aTarget);
+  promise->Then(aTarget, __func__,
+    [aResolve, token](const ClientOpResult& aResult) {
+      if (token && token->IsShuttingDown()) {
+        return;
+      }
+      aResolve(aResult);
+    }, [aReject, token](nsresult aResult) {
+      if (token && token->IsShuttingDown()) {
+        return;
+      }
+      aReject(aResult);
+    });
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientDOMUtil_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/Clients.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Clients.h"
+
+#include "ClientDOMUtil.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "mozilla/SystemGroup.h"
+#include "nsIGlobalObject.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::WorkerPrivate;
+using mozilla::dom::workers::ServiceWorkerManager;
+using mozilla::ipc::PrincipalInfo;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Clients::Clients(nsIGlobalObject* aGlobal)
+  : mGlobal(aGlobal)
+{
+  MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+}
+
+JSObject*
+Clients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  // TODO: Enable when bindings are updated to point to this class.
+#if 0
+  return ClientsBinding::Wrap(aCx, this, aGivenProto);
+#endif
+  return nullptr;
+}
+
+nsIGlobalObject*
+Clients::GetParentObject() const
+{
+  return mGlobal;
+}
+
+already_AddRefed<Promise>
+Clients::Get(const nsAString& aClientID, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  nsID id;
+  if (!id.Parse(NS_ConvertUTF16toUTF8(aClientID).get())) {
+    // Invalid ID means we will definitely not find a match, so just
+    // resolve with undefined indicating "not found".
+    outerPromise->MaybeResolveWithUndefined();
+    return outerPromise.forget();
+  }
+
+  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
+  nsCOMPtr<nsISerialEventTarget> target =
+    mGlobal->EventTargetFor(TaskCategory::Other);
+
+  RefPtr<ClientOpPromise> innerPromise =
+    ClientManager::GetInfoAndState(ClientGetInfoAndStateArgs(id, principalInfo),
+                                   target);
+
+  nsCOMPtr<nsIGlobalObject> global = mGlobal;
+  nsCString scope = workerPrivate->ServiceWorkerScope();
+
+  innerPromise->Then(target, __func__,
+    [outerPromise, global, scope] (const ClientOpResult& aResult) {
+      RefPtr<Client> client = new Client(global, aResult.get_ClientInfoAndState());
+      if (client->GetStorageAccess() == nsContentUtils::StorageAccess::eAllow) {
+        outerPromise->MaybeResolve(Move(client));
+        return;
+      }
+      nsCOMPtr<nsIRunnable> r =
+        NS_NewRunnableFunction("Clients::MatchAll() storage denied",
+        [scope] {
+          ServiceWorkerManager::LocalizeAndReportToAllClients(
+            scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
+        });
+      SystemGroup::Dispatch(TaskCategory::Other, r.forget());
+      outerPromise->MaybeResolveWithUndefined();
+    }, [outerPromise] (nsresult aResult) {
+      outerPromise->MaybeResolveWithUndefined();
+    });
+
+  return outerPromise.forget();
+}
+
+namespace {
+
+class MatchAllComparator final
+{
+public:
+  bool
+  LessThan(Client* aLeft, Client* aRight) const
+  {
+    TimeStamp leftFocusTime = aLeft->LastFocusTime();
+    TimeStamp rightFocusTime = aRight->LastFocusTime();
+    // If the focus times are the same, then default to creation order.
+    // MatchAll should return oldest Clients first.
+    if (leftFocusTime == rightFocusTime) {
+      return aLeft->CreationTime() < aRight->CreationTime();
+    }
+
+    // Otherwise compare focus times.  We reverse the logic here so
+    // that the most recently focused window is first in the list.
+    if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) {
+      return true;
+    }
+    if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) {
+      return false;
+    }
+    return leftFocusTime > rightFocusTime;
+  }
+
+  bool
+  Equals(Client* aLeft, Client* aRight) const
+  {
+    return aLeft->LastFocusTime() == aRight->LastFocusTime() &&
+           aLeft->CreationTime() == aRight->CreationTime();
+  }
+};
+
+} // anonymous namespace
+
+already_AddRefed<Promise>
+Clients::MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  nsCOMPtr<nsIGlobalObject> global = mGlobal;
+  nsCString scope = workerPrivate->ServiceWorkerScope();
+
+  ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(),
+                          aOptions.mType,
+                          aOptions.mIncludeUncontrolled);
+  StartClientManagerOp(&ClientManager::MatchAll, args,
+    mGlobal->EventTargetFor(TaskCategory::Other),
+    [outerPromise, global, scope] (const ClientOpResult& aResult) {
+      nsTArray<RefPtr<Client>> clientList;
+      bool storageDenied = false;
+      for (const ClientInfoAndState& value : aResult.get_ClientList().values()) {
+        RefPtr<Client> client = new Client(global, value);
+        if (client->GetStorageAccess() != nsContentUtils::StorageAccess::eAllow) {
+          storageDenied = true;
+          continue;
+        }
+        clientList.AppendElement(Move(client));
+      }
+      if (storageDenied) {
+        nsCOMPtr<nsIRunnable> r =
+          NS_NewRunnableFunction("Clients::MatchAll() storage denied",
+          [scope] {
+            ServiceWorkerManager::LocalizeAndReportToAllClients(
+              scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
+          });
+        SystemGroup::Dispatch(TaskCategory::Other, r.forget());
+      }
+      clientList.Sort(MatchAllComparator());
+      outerPromise->MaybeResolve(clientList);
+    }, [outerPromise] (nsresult aResult) {
+      outerPromise->MaybeReject(aResult);
+    });
+
+  return outerPromise.forget();
+}
+
+already_AddRefed<Promise>
+Clients::OpenWindow(const nsAString& aURL, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  if (aURL.EqualsLiteral("about:blank")) {
+    // TODO: Improve this error in bug 1412856.
+    outerPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
+    return outerPromise.forget();
+  }
+
+  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+    outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return outerPromise.forget();
+  }
+
+  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
+  nsCString baseURL = workerPrivate->GetLocationInfo().mHref;
+  ClientOpenWindowArgs args(principalInfo, NS_ConvertUTF16toUTF8(aURL),
+                            baseURL);
+
+  nsCOMPtr<nsIGlobalObject> global = mGlobal;
+
+  StartClientManagerOp(&ClientManager::OpenWindow, args,
+    mGlobal->EventTargetFor(TaskCategory::Other),
+    [outerPromise, global] (const ClientOpResult& aResult) {
+      if (aResult.type() != ClientOpResult::TClientInfoAndState) {
+        outerPromise->MaybeResolve(JS::NullHandleValue);
+        return;
+      }
+      RefPtr<Client> client =
+        new Client(global, aResult.get_ClientInfoAndState());
+      outerPromise->MaybeResolve(client);
+    }, [outerPromise] (nsresult aResult) {
+      // TODO: Improve this error in bug 1412856.  Ideally we should throw
+      //       the TypeError in the child process and pass it back to here.
+      outerPromise->MaybeReject(NS_ERROR_TYPE_ERR);
+    });
+
+  return outerPromise.forget();
+}
+
+already_AddRefed<Promise>
+Clients::Claim(ErrorResult& aRv)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+  workerPrivate->AssertIsOnWorkerThread();
+
+  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+  if (aRv.Failed()) {
+    return outerPromise.forget();
+  }
+
+  const ServiceWorkerDescriptor& serviceWorker =
+    workerPrivate->GetServiceWorkerDescriptor();
+
+  if (serviceWorker.State() != ServiceWorkerState::Activating &&
+      serviceWorker.State() != ServiceWorkerState::Activated) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return outerPromise.forget();
+  }
+
+  StartClientManagerOp(&ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()),
+    mGlobal->EventTargetFor(TaskCategory::Other),
+    [outerPromise] (const ClientOpResult& aResult) {
+      outerPromise->MaybeResolveWithUndefined();
+    }, [outerPromise] (nsresult aResult) {
+      outerPromise->MaybeReject(aResult);
+    });
+
+  return outerPromise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/Clients.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_Clients_h
+#define _mozilla_dom_Clients_h
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct ClientQueryOptions;
+class Promise;
+
+class Clients final : public nsISupports
+                    , public nsWrapperCache
+{
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+
+  ~Clients() = default;
+
+public:
+  explicit Clients(nsIGlobalObject* aGlobal);
+
+  // nsWrapperCache interface methods
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // DOM bindings methods
+  nsIGlobalObject*
+  GetParentObject() const;
+
+  already_AddRefed<Promise>
+  Get(const nsAString& aClientID, ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  OpenWindow(const nsAString& aURL, ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Claim(ErrorResult& aRv);
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Clients)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_Clients_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/api/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+  'Client.h',
+  'Clients.h',
+]
+
+UNIFIED_SOURCES += [
+  'Client.cpp',
+  'Clients.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+  '/dom/workers',
+]
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += [
+]
+
+BROWSER_CHROME_MANIFESTS += [
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+]
--- a/dom/clients/moz.build
+++ b/dom/clients/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
+  'api',
   'manager',
 ]
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -134,16 +134,17 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLAT
                                           PRFileDesc,
                                           PR_Close);
 
 namespace dom {
 namespace indexedDB {
 
 using namespace mozilla::dom::quota;
 using namespace mozilla::ipc;
+using mozilla::dom::quota::Client;
 
 namespace {
 
 class ConnectionPool;
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseFile;