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

#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"

#include "ServiceWorkerClient.h"
#include "ServiceWorkerManager.h"
#include "ServiceWorkerPrivate.h"
#include "ServiceWorkerWindowClient.h"

#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"

#include "nsContentUtils.h"
#include "nsIBrowserDOMWindow.h"
#include "nsIDocShell.h"
#include "nsIDOMChromeWindow.h"
#include "nsIDOMWindow.h"
#include "nsIWebNavigation.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsIWindowMediator.h"
#include "nsIWindowWatcher.h"
#include "nsNetUtil.h"
#include "nsPIWindowWatcher.h"
#include "nsWindowWatcher.h"
#include "nsWeakReference.h"

#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#endif

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::workers;

NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClients)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClients)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClients, mWorkerScope)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClients)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

ServiceWorkerClients::ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope)
  : mWorkerScope(aWorkerScope)
{
  MOZ_ASSERT(mWorkerScope);
}

JSObject*
ServiceWorkerClients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return ClientsBinding::Wrap(aCx, this, aGivenProto);
}

namespace {

class GetRunnable final : public Runnable
{
  class ResolvePromiseWorkerRunnable final : public WorkerRunnable
  {
    RefPtr<PromiseWorkerProxy> mPromiseProxy;
    UniquePtr<ServiceWorkerClientInfo> mValue;
    nsresult mRv;

  public:
    ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                 PromiseWorkerProxy* aPromiseProxy,
                                 UniquePtr<ServiceWorkerClientInfo>&& aValue,
                                 nsresult aRv)
      : WorkerRunnable(aWorkerPrivate),
        mPromiseProxy(aPromiseProxy),
        mValue(Move(aValue)),
        mRv(Move(aRv))
    {
      AssertIsOnMainThread();
    }

    bool
    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
    {
      MOZ_ASSERT(aWorkerPrivate);
      aWorkerPrivate->AssertIsOnWorkerThread();

      Promise* promise = mPromiseProxy->WorkerPromise();
      MOZ_ASSERT(promise);

      if (NS_FAILED(mRv)) {
        promise->MaybeReject(mRv);
      } else if (mValue) {
        RefPtr<ServiceWorkerWindowClient> windowClient =
          new ServiceWorkerWindowClient(promise->GetParentObject(), *mValue);
        promise->MaybeResolve(windowClient.get());
      } else {
        promise->MaybeResolveWithUndefined();
      }
      mPromiseProxy->CleanUp();
      return true;
    }
  };

  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  nsString mClientId;
public:
  GetRunnable(PromiseWorkerProxy* aPromiseProxy,
              const nsAString& aClientId)
    : mPromiseProxy(aPromiseProxy),
      mClientId(aClientId)
  {
  }

  NS_IMETHOD
  Run() override
  {
    AssertIsOnMainThread();

    MutexAutoLock lock(mPromiseProxy->Lock());
    if (mPromiseProxy->CleanedUp()) {
      return NS_OK;
    }

    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
    MOZ_ASSERT(workerPrivate);

    UniquePtr<ServiceWorkerClientInfo> result;
    ErrorResult rv;

    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (!swm) {
      rv = NS_ERROR_FAILURE;
    } else {
      result = swm->GetClient(workerPrivate->GetPrincipal(), mClientId, rv);
    }

    RefPtr<ResolvePromiseWorkerRunnable> r =
      new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
                                       mPromiseProxy, Move(result),
                                       rv.StealNSResult());
    rv.SuppressException();

    r->Dispatch();
    return NS_OK;
  }
};

class MatchAllRunnable final : public Runnable
{
  class ResolvePromiseWorkerRunnable final : public WorkerRunnable
  {
    RefPtr<PromiseWorkerProxy> mPromiseProxy;
    nsTArray<ServiceWorkerClientInfo> mValue;

  public:
    ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                 PromiseWorkerProxy* aPromiseProxy,
                                 nsTArray<ServiceWorkerClientInfo>& aValue)
      : WorkerRunnable(aWorkerPrivate),
        mPromiseProxy(aPromiseProxy)
    {
      AssertIsOnMainThread();
      mValue.SwapElements(aValue);
    }

    bool
    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
    {
      MOZ_ASSERT(aWorkerPrivate);
      aWorkerPrivate->AssertIsOnWorkerThread();

      Promise* promise = mPromiseProxy->WorkerPromise();
      MOZ_ASSERT(promise);

      nsTArray<RefPtr<ServiceWorkerClient>> ret;
      for (size_t i = 0; i < mValue.Length(); i++) {
        ret.AppendElement(RefPtr<ServiceWorkerClient>(
              new ServiceWorkerWindowClient(promise->GetParentObject(),
                                            mValue.ElementAt(i))));
      }

      promise->MaybeResolve(ret);
      mPromiseProxy->CleanUp();
      return true;
    }
  };

  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  nsCString mScope;
  bool mIncludeUncontrolled;
public:
  MatchAllRunnable(PromiseWorkerProxy* aPromiseProxy,
                   const nsCString& aScope,
                   bool aIncludeUncontrolled)
    : mPromiseProxy(aPromiseProxy),
      mScope(aScope),
      mIncludeUncontrolled(aIncludeUncontrolled)
  {
    MOZ_ASSERT(mPromiseProxy);
  }

  NS_IMETHOD
  Run() override
  {
    AssertIsOnMainThread();

    MutexAutoLock lock(mPromiseProxy->Lock());
    if (mPromiseProxy->CleanedUp()) {
      return NS_OK;
    }

    nsTArray<ServiceWorkerClientInfo> result;
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (swm) {
      swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(),
                         mScope, mIncludeUncontrolled, result);
    }
    RefPtr<ResolvePromiseWorkerRunnable> r =
      new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
                                       mPromiseProxy, result);

    r->Dispatch();
    return NS_OK;
  }
};

class ResolveClaimRunnable final : public WorkerRunnable
{
  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  nsresult mResult;

public:
  ResolveClaimRunnable(WorkerPrivate* aWorkerPrivate,
                       PromiseWorkerProxy* aPromiseProxy,
                       nsresult aResult)
    : WorkerRunnable(aWorkerPrivate)
    , mPromiseProxy(aPromiseProxy)
    , mResult(aResult)
  {
    AssertIsOnMainThread();
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();

    RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
    MOZ_ASSERT(promise);

    if (NS_SUCCEEDED(mResult)) {
      promise->MaybeResolveWithUndefined();
    } else {
      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
    }

    mPromiseProxy->CleanUp();
    return true;
  }
};

class ClaimRunnable final : public Runnable
{
  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  nsCString mScope;
  uint64_t mServiceWorkerID;

public:
  ClaimRunnable(PromiseWorkerProxy* aPromiseProxy, const nsCString& aScope)
    : mPromiseProxy(aPromiseProxy)
    , mScope(aScope)
    // Safe to call GetWorkerPrivate() since we are being called on the worker
    // thread via script (so no clean up has occured yet).
    , mServiceWorkerID(aPromiseProxy->GetWorkerPrivate()->ServiceWorkerID())
  {
    MOZ_ASSERT(aPromiseProxy);
  }

  NS_IMETHOD
  Run() override
  {
    MutexAutoLock lock(mPromiseProxy->Lock());
    if (mPromiseProxy->CleanedUp()) {
      return NS_OK;
    }

    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
    MOZ_ASSERT(workerPrivate);

    nsresult rv = NS_OK;
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (!swm) {
      // browser shutdown
      rv = NS_ERROR_FAILURE;
    } else {
      rv = swm->ClaimClients(workerPrivate->GetPrincipal(), mScope,
                             mServiceWorkerID);
    }

    RefPtr<ResolveClaimRunnable> r =
      new ResolveClaimRunnable(workerPrivate, mPromiseProxy, rv);

    r->Dispatch();
    return NS_OK;
  }
};

class ResolveOpenWindowRunnable final : public WorkerRunnable
{
public:
  ResolveOpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
                            UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                            const nsresult aStatus)
  : WorkerRunnable(aPromiseProxy->GetWorkerPrivate())
  , mPromiseProxy(aPromiseProxy)
  , mClientInfo(Move(aClientInfo))
  , mStatus(aStatus)
  {
    AssertIsOnMainThread();
    MOZ_ASSERT(aPromiseProxy);
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();

    Promise* promise = mPromiseProxy->WorkerPromise();
    if (NS_WARN_IF(NS_FAILED(mStatus))) {
      promise->MaybeReject(mStatus);
    } else if (mClientInfo) {
      RefPtr<ServiceWorkerWindowClient> client =
        new ServiceWorkerWindowClient(promise->GetParentObject(),
                                      *mClientInfo);
      promise->MaybeResolve(client);
    } else {
      promise->MaybeResolve(JS::NullHandleValue);
    }

    mPromiseProxy->CleanUp();
    return true;
  }

private:
  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  UniquePtr<ServiceWorkerClientInfo> mClientInfo;
  const nsresult mStatus;
};

class WebProgressListener final : public nsIWebProgressListener,
                                  public nsSupportsWeakReference
{
public:
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener, nsIWebProgressListener)

  WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
                      ServiceWorkerPrivate* aServiceWorkerPrivate,
                      nsPIDOMWindowOuter* aWindow,
                      nsIURI* aBaseURI)
  : mPromiseProxy(aPromiseProxy)
  , mServiceWorkerPrivate(aServiceWorkerPrivate)
  , mWindow(aWindow)
  , mBaseURI(aBaseURI)
  {
    MOZ_ASSERT(aPromiseProxy);
    MOZ_ASSERT(aServiceWorkerPrivate);
    MOZ_ASSERT(aWindow);
    MOZ_ASSERT(aWindow->IsOuterWindow());
    MOZ_ASSERT(aBaseURI);
    AssertIsOnMainThread();

    mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this));
  }

  NS_IMETHOD
  OnStateChange(nsIWebProgress* aWebProgress,
                nsIRequest* aRequest,
                uint32_t aStateFlags, nsresult aStatus) override
  {
    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
         !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
      return NS_OK;
    }

    // Our caller keeps a strong reference, so it is safe to remove the listener
    // from ServiceWorkerPrivate.
    mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this));
    aWebProgress->RemoveProgressListener(this);

    MutexAutoLock lock(mPromiseProxy->Lock());
    if (mPromiseProxy->CleanedUp()) {
      return NS_OK;
    }

    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
    UniquePtr<ServiceWorkerClientInfo> clientInfo;
    if (doc) {
      // Check same origin.
      nsCOMPtr<nsIScriptSecurityManager> securityManager =
        nsContentUtils::GetSecurityManager();
      nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
                                                        mBaseURI, false);
      if (NS_SUCCEEDED(rv)) {
        clientInfo.reset(new ServiceWorkerClientInfo(doc));
      }
    }

    RefPtr<ResolveOpenWindowRunnable> r =
      new ResolveOpenWindowRunnable(mPromiseProxy,
                                    Move(clientInfo),
                                    NS_OK);
    r->Dispatch();

    return NS_OK;
  }

  NS_IMETHOD
  OnProgressChange(nsIWebProgress* aWebProgress,
                   nsIRequest* aRequest,
                   int32_t aCurSelfProgress,
                   int32_t aMaxSelfProgress,
                   int32_t aCurTotalProgress,
                   int32_t aMaxTotalProgress) override
  {
    MOZ_ASSERT(false, "Unexpected notification.");
    return NS_OK;
  }

  NS_IMETHOD
  OnLocationChange(nsIWebProgress* aWebProgress,
                   nsIRequest* aRequest,
                   nsIURI* aLocation,
                   uint32_t aFlags) override
  {
    MOZ_ASSERT(false, "Unexpected notification.");
    return NS_OK;
  }

  NS_IMETHOD
  OnStatusChange(nsIWebProgress* aWebProgress,
                 nsIRequest* aRequest,
                 nsresult aStatus, const char16_t* aMessage) override
  {
    MOZ_ASSERT(false, "Unexpected notification.");
    return NS_OK;
  }

  NS_IMETHOD
  OnSecurityChange(nsIWebProgress* aWebProgress,
                   nsIRequest* aRequest,
                   uint32_t aState) override
  {
    MOZ_ASSERT(false, "Unexpected notification.");
    return NS_OK;
  }

private:
  ~WebProgressListener()
  { }

  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
  nsCOMPtr<nsIURI> mBaseURI;
};

NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
                         mServiceWorkerPrivate, mWindow)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

class OpenWindowRunnable final : public Runnable
{
  RefPtr<PromiseWorkerProxy> mPromiseProxy;
  nsString mUrl;
  nsString mScope;

public:
  OpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
                     const nsAString& aUrl,
                     const nsAString& aScope)
    : mPromiseProxy(aPromiseProxy)
    , mUrl(aUrl)
    , mScope(aScope)
  {
    MOZ_ASSERT(aPromiseProxy);
    MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
    aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
  }

  NS_IMETHOD
  Run() override
  {
    AssertIsOnMainThread();

    MutexAutoLock lock(mPromiseProxy->Lock());
    if (mPromiseProxy->CleanedUp()) {
      return NS_OK;
    }

#ifdef MOZ_WIDGET_ANDROID
    // This fires an intent that will start launching Fennec and foreground it,
    // if necessary.
    java::GeckoAppShell::OpenWindowForNotification();
#endif

    nsCOMPtr<nsPIDOMWindowOuter> window;
    nsresult rv = OpenWindow(getter_AddRefs(window));
    if (NS_SUCCEEDED(rv)) {
      MOZ_ASSERT(window);

      rv = nsContentUtils::DispatchFocusChromeEvent(window);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
      MOZ_ASSERT(workerPrivate);

      WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();
      nsCOMPtr<nsIURI> baseURI;
      nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return NS_ERROR_FAILURE;
      }

      nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
      nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);

      if (!webProgress) {
        return NS_ERROR_FAILURE;
      }

      RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
      if (!swm) {
        // browser shutdown
        return NS_ERROR_FAILURE;
      }

      nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
      MOZ_ASSERT(principal);
      RefPtr<ServiceWorkerRegistrationInfo> registration =
        swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
      if (NS_WARN_IF(!registration)) {
        return NS_ERROR_FAILURE;
      }
      RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
        registration->GetServiceWorkerInfoById(workerPrivate->ServiceWorkerID());
      if (NS_WARN_IF(!serviceWorkerInfo)) {
        return NS_ERROR_FAILURE;
      }

      nsCOMPtr<nsIWebProgressListener> listener =
        new WebProgressListener(mPromiseProxy, serviceWorkerInfo->WorkerPrivate(),
                                window, baseURI);

      rv = webProgress->AddProgressListener(listener,
                                            nsIWebProgress::NOTIFY_STATE_DOCUMENT);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
      return NS_OK;
    }
#ifdef MOZ_WIDGET_ANDROID
    else if (rv == NS_ERROR_NOT_AVAILABLE) {
      // We couldn't get a browser window, so Fennec must not be running.
      // Send an Intent to launch Fennec and wait for "BrowserChrome:Ready"
      // to try opening a window again.
      nsCOMPtr<nsIObserverService> os = services::GetObserverService();
      NS_ENSURE_STATE(os);

      WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
      MOZ_ASSERT(workerPrivate);

      RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
      if (!swm) {
        // browser shutdown
        return NS_ERROR_FAILURE;
      }

      nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
      MOZ_ASSERT(principal);

      RefPtr<ServiceWorkerRegistrationInfo> registration =
        swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
      if (NS_WARN_IF(!registration)) {
        return NS_ERROR_FAILURE;
      }

      RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
        registration->GetServiceWorkerInfoById(workerPrivate->ServiceWorkerID());
      if (NS_WARN_IF(!serviceWorkerInfo)) {
        return NS_ERROR_FAILURE;
      }

      os->AddObserver(static_cast<nsIObserver*>(serviceWorkerInfo->WorkerPrivate()),
                      "BrowserChrome:Ready", true);
      serviceWorkerInfo->WorkerPrivate()->AddPendingWindow(this);
      return NS_OK;
    }
#endif

    RefPtr<ResolveOpenWindowRunnable> resolveRunnable =
      new ResolveOpenWindowRunnable(mPromiseProxy, nullptr, rv);

    Unused << NS_WARN_IF(!resolveRunnable->Dispatch());

    return NS_OK;
  }

private:
  nsresult
  OpenWindow(nsPIDOMWindowOuter** aWindow)
  {
    MOZ_DIAGNOSTIC_ASSERT(aWindow);
    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();

    // [[1. Let url be the result of parsing url with entry settings object's API
    //   base URL.]]
    nsCOMPtr<nsIURI> uri;
    WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();

    nsCOMPtr<nsIURI> baseURI;
    nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return NS_ERROR_TYPE_ERR;
    }

    rv = NS_NewURI(getter_AddRefs(uri), mUrl, nullptr, baseURI);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return NS_ERROR_TYPE_ERR;
    }

    // [[6.1 Open Window]]
    nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID,
                                                   &rv);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    if (XRE_IsContentProcess()) {
      // ContentProcess
      nsCOMPtr<nsIWindowWatcher> wwatch =
        do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
      NS_ENSURE_STATE(pwwatch);

      nsCString spec;
      rv = uri->GetSpec(spec);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      nsCOMPtr<mozIDOMWindowProxy> newWindow;
      rv = pwwatch->OpenWindow2(nullptr,
                                spec.get(),
                                nullptr,
                                nullptr,
                                false, false, true, nullptr,
                                // Not a spammy popup; we got permission, we swear!
                                /* aIsPopupSpam = */ false,
                                // Don't force noopener.  We're not passing in an
                                // opener anyway, and we _do_ want the returned
                                // window.
                                /* aForceNoOpener = */ false,
                                /* aLoadInfp = */ nullptr,
                                getter_AddRefs(newWindow));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
      pwindow.forget(aWindow);
      MOZ_DIAGNOSTIC_ASSERT(*aWindow);
      return NS_OK;
    }

    // Find the most recent browser window and open a new tab in it.
    nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
      nsContentUtils::GetMostRecentNonPBWindow();
    if (!browserWindow) {
      // It is possible to be running without a browser window on Mac OS, so
      // we need to open a new chrome window.
      // TODO(catalinb): open new chrome window. Bug 1218080
      return NS_ERROR_NOT_AVAILABLE;
    }

    nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
    if (NS_WARN_IF(!chromeWin)) {
      return NS_ERROR_FAILURE;
    }

    nsCOMPtr<nsIBrowserDOMWindow> bwin;
    chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));

    if (NS_WARN_IF(!bwin)) {
      return NS_ERROR_FAILURE;
    }

    nsCOMPtr<mozIDOMWindowProxy> win;
    rv = bwin->OpenURI(uri, nullptr,
                       nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
                       nsIBrowserDOMWindow::OPEN_NEW,
                       getter_AddRefs(win));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    NS_ENSURE_STATE(win);

    nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
    pWin.forget(aWindow);
    MOZ_DIAGNOSTIC_ASSERT(*aWindow);

    return NS_OK;
  }
};

} // namespace

already_AddRefed<Promise>
ServiceWorkerClients::Get(const nsAString& aClientId, ErrorResult& aRv)
{
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(workerPrivate);
  workerPrivate->AssertIsOnWorkerThread();

  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<PromiseWorkerProxy> promiseProxy =
    PromiseWorkerProxy::Create(workerPrivate, promise);
  if (!promiseProxy) {
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    return promise.forget();
  }

  RefPtr<GetRunnable> r =
    new GetRunnable(promiseProxy, aClientId);
  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
  return promise.forget();
}

already_AddRefed<Promise>
ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions,
                               ErrorResult& aRv)
{
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(workerPrivate);
  workerPrivate->AssertIsOnWorkerThread();

  nsString scope;
  mWorkerScope->GetScope(scope);

  if (aOptions.mType != ClientType::Window) {
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<PromiseWorkerProxy> promiseProxy =
    PromiseWorkerProxy::Create(workerPrivate, promise);
  if (!promiseProxy) {
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    return promise.forget();
  }

  RefPtr<MatchAllRunnable> r =
    new MatchAllRunnable(promiseProxy,
                         NS_ConvertUTF16toUTF8(scope),
                         aOptions.mIncludeUncontrolled);
  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
  return promise.forget();
}

already_AddRefed<Promise>
ServiceWorkerClients::OpenWindow(const nsAString& aUrl,
                                 ErrorResult& aRv)
{
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(workerPrivate);

  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  if (aUrl.EqualsLiteral("about:blank")) {
    promise->MaybeReject(NS_ERROR_TYPE_ERR);
    return promise.forget();
  }

  // [[4. If this algorithm is not allowed to show a popup ..]]
  // In Gecko the service worker is allowed to show a popup only if the user
  // just clicked on a notification.
  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
    return promise.forget();
  }

  RefPtr<PromiseWorkerProxy> promiseProxy =
    PromiseWorkerProxy::Create(workerPrivate, promise);

  if (!promiseProxy) {
    return nullptr;
  }

  nsString scope;
  mWorkerScope->GetScope(scope);

  RefPtr<OpenWindowRunnable> r = new OpenWindowRunnable(promiseProxy,
                                                        aUrl, scope);
  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));

  return promise.forget();
}

already_AddRefed<Promise>
ServiceWorkerClients::Claim(ErrorResult& aRv)
{
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(workerPrivate);

  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<PromiseWorkerProxy> promiseProxy =
    PromiseWorkerProxy::Create(workerPrivate, promise);
  if (!promiseProxy) {
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    return promise.forget();
  }

  nsString scope;
  mWorkerScope->GetScope(scope);

  RefPtr<ClaimRunnable> runnable =
    new ClaimRunnable(promiseProxy, NS_ConvertUTF16toUTF8(scope));

  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(runnable.forget()));
  return promise.forget();
}
