/* -*- 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 "ClientSource.h"

#include "ClientManager.h"
#include "ClientManagerChild.h"
#include "ClientSourceChild.h"
#include "ClientState.h"
#include "ClientValidation.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "mozilla/dom/workers/bindings/ServiceWorker.h"
#include "nsContentUtils.h"
#include "nsIDocShell.h"
#include "nsPIDOMWindow.h"

namespace mozilla {
namespace dom {

using mozilla::dom::ipc::StructuredCloneData;
using mozilla::dom::workers::ServiceWorkerInfo;
using mozilla::dom::workers::ServiceWorkerManager;
using mozilla::dom::workers::ServiceWorkerRegistrationInfo;
using mozilla::dom::workers::WorkerPrivate;
using mozilla::ipc::PrincipalInfo;
using mozilla::ipc::PrincipalInfoToPrincipal;

void
ClientSource::Shutdown()
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  if (IsShutdown()) {
    return;
  }

  ShutdownThing();

  mManager = nullptr;
}

void
ClientSource::ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs)
{
  // Fast fail if we don't understand this particular principal/URL combination.
  // This can happen since we use MozURL for validation which does not handle
  // some of the more obscure internal principal/url combinations.  Normal
  // content pages will pass this check.
  if (NS_WARN_IF(!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(),
                                           aArgs.url()))) {
    Shutdown();
    return;
  }

  mClientInfo.SetURL(aArgs.url());
  mClientInfo.SetFrameType(aArgs.frameType());
  MaybeExecute([aArgs](PClientSourceChild* aActor) {
    aActor->SendExecutionReady(aArgs);
  });
}

nsresult
ClientSource::SnapshotWindowState(ClientState* aStateOut)
{
  MOZ_ASSERT(NS_IsMainThread());

  nsPIDOMWindowInner* window = GetInnerWindow();
  if (!window || !window->IsCurrentInnerWindow() ||
      !window->HasActiveDocument()) {
    *aStateOut = ClientState(ClientWindowState(VisibilityState::Hidden,
                                               TimeStamp(),
                                               nsContentUtils::StorageAccess::eDeny,
                                               false));
    return NS_OK;
  }

  nsIDocument* doc = window->GetExtantDoc();
  if (NS_WARN_IF(!doc)) {
    return NS_ERROR_UNEXPECTED;
  }

  ErrorResult rv;
  bool focused = doc->HasFocus(rv);
  if (NS_WARN_IF(rv.Failed())) {
    rv.SuppressException();
    return rv.StealNSResult();
  }

  nsContentUtils::StorageAccess storage =
    nsContentUtils::StorageAllowedForDocument(doc);

  *aStateOut = ClientState(ClientWindowState(doc->VisibilityState(),
                                             doc->LastFocusTime(), storage,
                                             focused));

  return NS_OK;
}

WorkerPrivate*
ClientSource::GetWorkerPrivate() const
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  if (!mOwner.is<WorkerPrivate*>()) {
    return nullptr;
  }
  return mOwner.as<WorkerPrivate*>();
}

nsIDocShell*
ClientSource::GetDocShell() const
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) {
    return nullptr;
  }
  return mOwner.as<nsCOMPtr<nsIDocShell>>();
}

void
ClientSource::MaybeCreateInitialDocument()
{
  nsIDocShell* docshell = GetDocShell();
  if (docshell) {
    // Force the create of the initial document if it does not exist yet.
    Unused << docshell->GetDocument();

    MOZ_DIAGNOSTIC_ASSERT(GetInnerWindow());
  }
}

ClientSource::ClientSource(ClientManager* aManager,
                           nsISerialEventTarget* aEventTarget,
                           const ClientSourceConstructorArgs& aArgs)
  : mManager(aManager)
  , mEventTarget(aEventTarget)
  , mOwner(AsVariant(Nothing()))
  , mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
{
  MOZ_ASSERT(mManager);
  MOZ_ASSERT(mEventTarget);
}

void
ClientSource::Activate(PClientManagerChild* aActor)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  MOZ_ASSERT(!GetActor());

  if (IsShutdown()) {
    return;
  }

  // Fast fail if we don't understand this particular kind of PrincipalInfo.
  // This can happen since we use MozURL for validation which does not handle
  // some of the more obscure internal principal/url combinations.  Normal
  // content pages will pass this check.
  if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
    Shutdown();
    return;
  }

  ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(),
                                   mClientInfo.PrincipalInfo(),
                                   mClientInfo.CreationTime());
  PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args);
  if (!actor) {
    Shutdown();
    return;
  }

  ActivateThing(static_cast<ClientSourceChild*>(actor));
}

ClientSource::~ClientSource()
{
  Shutdown();
}

nsPIDOMWindowInner*
ClientSource::GetInnerWindow() const
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  if (!mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
    return nullptr;
  }
  return mOwner.as<RefPtr<nsPIDOMWindowInner>>();
}

void
ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate)
{
  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
  aWorkerPrivate->AssertIsOnWorkerThread();

  if (IsShutdown()) {
    return;
  }

  // A client without access to storage should never be controlled by
  // a service worker.  Check this here in case we were controlled before
  // execution ready.  We can't reliably determine what our storage policy
  // is before execution ready, unfortunately.
  if (mController.isSome()) {
    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed());
  }

  // Its safe to store the WorkerPrivate* here because the ClientSource
  // is explicitly destroyed by WorkerPrivate before exiting its run loop.
  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
  mOwner = AsVariant(aWorkerPrivate);

  ClientSourceExecutionReadyArgs args(
    aWorkerPrivate->GetLocationInfo().mHref,
    FrameType::None);

  ExecutionReady(args);
}

nsresult
ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow);
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());

  if (IsShutdown()) {
    return NS_OK;
  }

  nsIDocument* doc = aInnerWindow->GetExtantDoc();
  if (NS_WARN_IF(!doc)) {
    return NS_ERROR_UNEXPECTED;
  }

  // A client without access to storage should never be controlled by
  // a service worker.  Check this here in case we were controlled before
  // execution ready.  We can't reliably determine what our storage policy
  // is before execution ready, unfortunately.
  if (mController.isSome()) {
    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
                          nsContentUtils::StorageAccess::eAllow);
  }

  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
  nsCString spec;

  nsIURI* uri = doc->GetOriginalURI();
  if (uri) {
    nsresult rv = uri->GetSpec(spec);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
  if (NS_WARN_IF(!outer)) {
    return NS_ERROR_UNEXPECTED;
  }

  FrameType frameType = FrameType::Top_level;
  if (!outer->IsTopLevelWindow()) {
    frameType = FrameType::Nested;
  } else if(outer->HadOriginalOpener()) {
    frameType = FrameType::Auxiliary;
  }

  // We should either be setting a window execution ready for the
  // first time or setting the same window execution ready again.
  // The secondary calls are due to initial about:blank replacement.
  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>() ||
                        mOwner.is<nsCOMPtr<nsIDocShell>>() ||
                        GetInnerWindow() == aInnerWindow);

  // This creates a cycle with the window.  It is broken when
  // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource.
  mOwner = AsVariant(RefPtr<nsPIDOMWindowInner>(aInnerWindow));

  ClientSourceExecutionReadyArgs args(spec, frameType);
  ExecutionReady(args);

  return NS_OK;
}

nsresult
ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aDocShell);

  if (IsShutdown()) {
    return NS_OK;
  }

  nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
  if (NS_WARN_IF(!outer)) {
    return NS_ERROR_UNEXPECTED;
  }

  // Note: We don't assert storage access for a controlled client.  If
  // the about:blank actually gets used then WindowExecutionReady() will
  // get called which asserts storage access.

  // TODO: dedupe this with WindowExecutionReady
  FrameType frameType = FrameType::Top_level;
  if (!outer->IsTopLevelWindow()) {
    frameType = FrameType::Nested;
  } else if(outer->HadOriginalOpener()) {
    frameType = FrameType::Auxiliary;
  }

  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());

  // This creates a cycle with the docshell.  It is broken when
  // nsDocShell::Destroy() deletes the ClientSource.
  mOwner = AsVariant(nsCOMPtr<nsIDocShell>(aDocShell));

  ClientSourceExecutionReadyArgs args(NS_LITERAL_CSTRING("about:blank"),
                                      frameType);
  ExecutionReady(args);

  return NS_OK;
}

void
ClientSource::Freeze()
{
  MaybeExecute([](PClientSourceChild* aActor) {
    aActor->SendFreeze();
  });
}

void
ClientSource::Thaw()
{
  MaybeExecute([](PClientSourceChild* aActor) {
    aActor->SendThaw();
  });
}

const ClientInfo&
ClientSource::Info() const
{
  return mClientInfo;
}

void
ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);

  if (IsShutdown()) {
    return;
  }

  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
  aWorkerPrivate->AssertIsOnWorkerThread();
  MOZ_DIAGNOSTIC_ASSERT(GetActor());

  GetActor()->SendWorkerSyncPing();
}

void
ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);

  // A client in private browsing mode should never be controlled by
  // a service worker.  The principal origin attributes should guarantee
  // this invariant.
  MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());

  // A client without access to storage should never be controlled a
  // a service worker.  If we are already execution ready with a real
  // window or worker, then verify assert the storage policy is correct.
  if (GetInnerWindow()) {
    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
                          nsContentUtils::StorageAccess::eAllow);
  } else if (GetWorkerPrivate()) {
    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed());
  }

  if (mController.isSome() && mController.ref() == aServiceWorker) {
    return;
  }

  mController.reset();
  mController.emplace(aServiceWorker);

  RefPtr<ServiceWorkerContainer> swc;
  nsPIDOMWindowInner* window = GetInnerWindow();
  if (window) {
    RefPtr<Navigator> navigator =
      static_cast<Navigator*>(window->GetNavigator());
    if (navigator) {
      swc = navigator->ServiceWorker();
    }
  }

  // TODO: Also self.navigator.serviceWorker on workers when its exposed there

  if (swc && nsContentUtils::IsSafeToRunScript()) {
    IgnoredErrorResult ignored;
    swc->ControllerChanged(ignored);
  }
}

RefPtr<ClientOpPromise>
ClientSource::Control(const ClientControlledArgs& aArgs)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);

  SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));

  RefPtr<ClientOpPromise> ref =
    ClientOpPromise::CreateAndResolve(NS_OK, __func__);
  return ref.forget();
}

const Maybe<ServiceWorkerDescriptor>&
ClientSource::GetController() const
{
  return mController;
}

RefPtr<ClientOpPromise>
ClientSource::Focus(const ClientFocusArgs& aArgs)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);

  RefPtr<ClientOpPromise> ref;

  if (mClientInfo.Type() != ClientType::Window) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                                           __func__);
    return ref.forget();
  }
  nsPIDOMWindowOuter* outer = nullptr;

  nsPIDOMWindowInner* inner = GetInnerWindow();
  if (inner) {
    outer = inner->GetOuterWindow();
  } else {
    nsIDocShell* docshell = GetDocShell();
    if (docshell) {
      outer = docshell->GetWindow();
    }
  }

  if (!outer) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                                           __func__);
    return ref.forget();
  }

  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv = nsContentUtils::DispatchFocusChromeEvent(outer);
  if (NS_FAILED(rv)) {
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
    return ref.forget();
  }

  ClientState state;
  rv = SnapshotState(&state);
  if (NS_FAILED(rv)) {
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
    return ref.forget();
  }

  ref = ClientOpPromise::CreateAndResolve(state.ToIPC(), __func__);
  return ref.forget();
}

RefPtr<ClientOpPromise>
ClientSource::PostMessage(const ClientPostMessageArgs& aArgs)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  RefPtr<ClientOpPromise> ref;

  ServiceWorkerDescriptor source(aArgs.serviceWorker());
  const PrincipalInfo& principalInfo = source.PrincipalInfo();

  StructuredCloneData clonedData;
  clonedData.BorrowFromClonedMessageDataForBackgroundChild(aArgs.clonedData());

  // Currently we only support firing these messages on window Clients.
  // Once we expose ServiceWorkerContainer and the ServiceWorker on Worker
  // threads then this will need to change.  See bug 1113522.
  if (mClientInfo.Type() != ClientType::Window) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
    return ref.forget();
  }

  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<ServiceWorkerContainer> target;
  nsCOMPtr<nsIGlobalObject> globalObject;

  // We don't need to force the creation of the about:blank document
  // here because there is no postMessage listener.  If a listener
  // was registered then the document will already be created.
  nsPIDOMWindowInner* window = GetInnerWindow();
  if (window) {
    globalObject = do_QueryInterface(window);
    RefPtr<Navigator> navigator =
      static_cast<Navigator*>(window->GetNavigator());
    if (navigator) {
      target = navigator->ServiceWorker();
    }
  }

  if (NS_WARN_IF(!target)) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                                           __func__);
    return ref.forget();
  }

  // If AutoJSAPI::Init() fails then either global is nullptr or not
  // in a usable state.
  AutoJSAPI jsapi;
  if (!jsapi.Init(globalObject)) {
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
    return ref.forget();
  }

  JSContext* cx = jsapi.cx();

  ErrorResult result;
  JS::Rooted<JS::Value> messageData(cx);
  clonedData.Read(cx, &messageData, result);
  if (result.MaybeSetPendingException(cx)) {
    // We reported the error in the current window context.  Resolve
    // promise instead of rejecting.
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
    return ref.forget();
  }

  RootedDictionary<MessageEventInit> init(cx);

  init.mData = messageData;
  if (!clonedData.TakeTransferredPortsAsSequence(init.mPorts)) {
    // Report the error in the current window context and resolve the
    // promise instead of rejecting.
    xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY);
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
    return ref.forget();
  }

  nsresult rv = NS_OK;
  nsCOMPtr<nsIPrincipal> principal =
    PrincipalInfoToPrincipal(principalInfo, &rv);
  if (NS_FAILED(rv) || !principal) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
    return ref.forget();
  }

  nsAutoCString origin;
  rv = principal->GetOriginNoSuffix(origin);
  if (NS_SUCCEEDED(rv)) {
    CopyUTF8toUTF16(origin, init.mOrigin);
  }

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    // Shutting down. Just don't deliver this message.
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
    return ref.forget();
  }

  RefPtr<ServiceWorkerRegistrationInfo> reg =
    swm->GetRegistration(principal, source.Scope());
  if (reg) {
    RefPtr<ServiceWorkerInfo> serviceWorker = reg->GetByID(source.Id());
    if (serviceWorker) {
      init.mSource.SetValue().SetAsServiceWorker() =
        serviceWorker->GetOrCreateInstance(GetInnerWindow());
    }
  }

  RefPtr<MessageEvent> event =
    MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init);
  event->SetTrusted(true);

  bool preventDefaultCalled = false;
  rv = target->DispatchEvent(static_cast<dom::Event*>(event.get()),
                             &preventDefaultCalled);
  if (NS_FAILED(rv)) {
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
    return ref.forget();
  }

  ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
  return ref.forget();
}

RefPtr<ClientOpPromise>
ClientSource::Claim(const ClientClaimArgs& aArgs)
{
  RefPtr<ClientOpPromise> ref;

  ServiceWorkerDescriptor swd(aArgs.serviceWorker());

  // Today the ServiceWorkerManager maintains its own list of
  // nsIDocument objects controlled by each service worker.  We
  // need to try to update that data structure for now.  If we
  // can't, however, then simply mark the Client as controlled.
  // In the future this will be enough for the SWM as well since
  // it will eventually hold ClientHandle objects instead of
  // nsIDocuments.
  nsPIDOMWindowInner* innerWindow = GetInnerWindow();
  nsIDocument* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
  RefPtr<ServiceWorkerManager> swm = doc ? ServiceWorkerManager::GetInstance()
                                         : nullptr;
  if (!swm || !doc) {
    SetController(swd);
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
    return ref.forget();
  }

  RefPtr<ClientOpPromise::Private> outerPromise =
    new ClientOpPromise::Private(__func__);

  RefPtr<GenericPromise> p = swm->MaybeClaimClient(doc, swd);
  p->Then(mEventTarget, __func__,
    [outerPromise] (bool aResult) {
      outerPromise->Resolve(NS_OK, __func__);
    }, [outerPromise] (nsresult aResult) {
      outerPromise->Reject(aResult, __func__);
    });

  ref = outerPromise;
  return ref.forget();
}

RefPtr<ClientOpPromise>
ClientSource::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
{
  RefPtr<ClientOpPromise> ref;

  ClientState state;
  nsresult rv = SnapshotState(&state);
  if (NS_FAILED(rv)) {
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
    return ref.forget();
  }

  ref = ClientOpPromise::CreateAndResolve(ClientInfoAndState(mClientInfo.ToIPC(),
                                                             state.ToIPC()), __func__);
  return ref.forget();
}

nsresult
ClientSource::SnapshotState(ClientState* aStateOut)
{
  NS_ASSERT_OWNINGTHREAD(ClientSource);
  MOZ_DIAGNOSTIC_ASSERT(aStateOut);

  if (mClientInfo.Type() == ClientType::Window) {
    MaybeCreateInitialDocument();
    nsresult rv = SnapshotWindowState(aStateOut);
    if (NS_FAILED(rv)) {
      return rv;
    }
    return NS_OK;
  }

  WorkerPrivate* workerPrivate = GetWorkerPrivate();
  if (!workerPrivate) {
    return NS_ERROR_DOM_INVALID_STATE_ERR;
  }

  // Workers only keep a boolean for storage access at the moment.
  // Map this back to eAllow or eDeny for now.
  nsContentUtils::StorageAccess storage =
    workerPrivate->IsStorageAllowed() ? nsContentUtils::StorageAccess::eAllow
                                      : nsContentUtils::StorageAccess::eDeny;

  *aStateOut = ClientState(ClientWorkerState(storage));
  return NS_OK;
}

nsISerialEventTarget*
ClientSource::EventTarget() const
{
  return mEventTarget;
}

void
ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
                       const char* aName,
                       uint32_t aFlags)
{
  if (mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
    ImplCycleCollectionTraverse(aCallback,
                                mOwner.as<RefPtr<nsPIDOMWindowInner>>(),
                                aName, aFlags);
  } else if (mOwner.is<nsCOMPtr<nsIDocShell>>()) {
    ImplCycleCollectionTraverse(aCallback,
                                mOwner.as<nsCOMPtr<nsIDocShell>>(),
                                aName, aFlags);
  }
}

void
ClientSource::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
{
  if (mRegisteringScopeList.Contains(aScope)) {
    return;
  }
  mRegisteringScopeList.AppendElement(aScope);
}

bool
ClientSource::CalledRegisterForServiceWorkerScope(const nsACString& aScope)
{
  return mRegisteringScopeList.Contains(aScope);
}

} // namespace dom
} // namespace mozilla
