Bug 1218148 - Implement WindowClient.navigate() r=baku
authorAndreas Farre <farre@mozilla.com>
Tue, 24 May 2016 09:05:17 +0200
changeset 303887 aa2088d0085a579f1f16bbffb6ea5729b2428a51
parent 303886 6891d6a48f5fb522b0844ff48d4ce109a812b08a
child 303888 7d290ccf62a7f3cd10eee80c6fda1e55b1b48923
push id30407
push usercbook@mozilla.com
push dateThu, 07 Jul 2016 09:41:54 +0000
treeherdermozilla-central@4764b9f8e6d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1218148
milestone50.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 1218148 - Implement WindowClient.navigate() r=baku MozReview-Commit-ID: 9FJNYkwsZ0o
dom/webidl/Client.webidl
dom/workers/ServiceWorkerWindowClient.cpp
dom/workers/ServiceWorkerWindowClient.h
--- a/dom/webidl/Client.webidl
+++ b/dom/webidl/Client.webidl
@@ -20,16 +20,19 @@ interface Client {
 
 [Exposed=ServiceWorker]
 interface WindowClient : Client {
   readonly attribute VisibilityState visibilityState;
   readonly attribute boolean focused;
 
   [Throws, NewObject]
   Promise<WindowClient> focus();
+
+  [Throws, NewObject]
+  Promise<WindowClient> navigate(USVString url);
 };
 
 enum FrameType {
   "auxiliary",
   "top-level",
   "nested",
   "none"
 };
--- a/dom/workers/ServiceWorkerWindowClient.cpp
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -2,23 +2,38 @@
 /* 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 "ServiceWorkerWindowClient.h"
 
+#include "js/Value.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/dom/ClientBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/UniquePtr.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocument.h"
+#include "nsIGlobalObject.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "ServiceWorker.h"
+#include "ServiceWorkerInfo.h"
+#include "ServiceWorkerManager.h"
 #include "WorkerPrivate.h"
 #include "WorkerScope.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::workers;
 
 using mozilla::UniquePtr;
@@ -26,48 +41,65 @@ using mozilla::UniquePtr;
 JSObject*
 ServiceWorkerWindowClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return WindowClientBinding::Wrap(aCx, this, aGivenProto);
 }
 
 namespace {
 
-// Passing a null clientInfo will reject the promise with InvalidAccessError.
 class ResolveOrRejectPromiseRunnable final : public WorkerRunnable
 {
   RefPtr<PromiseWorkerProxy> mPromiseProxy;
   UniquePtr<ServiceWorkerClientInfo> mClientInfo;
+  nsresult mRv;
 
 public:
-  ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate,
-                                 PromiseWorkerProxy* aPromiseProxy,
-                                 UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+  // Passing a null clientInfo will resolve the promise with a null value.
+  ResolveOrRejectPromiseRunnable(
+    WorkerPrivate* aWorkerPrivate, PromiseWorkerProxy* aPromiseProxy,
+    UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
     : WorkerRunnable(aWorkerPrivate)
     , mPromiseProxy(aPromiseProxy)
     , mClientInfo(Move(aClientInfo))
+    , mRv(NS_OK)
   {
     AssertIsOnMainThread();
   }
 
+  // Reject the promise with passed nsresult.
+  ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate,
+                                 PromiseWorkerProxy* aPromiseProxy,
+                                 nsresult aRv)
+    : WorkerRunnable(aWorkerPrivate)
+    , mPromiseProxy(aPromiseProxy)
+    , mClientInfo(nullptr)
+    , mRv(aRv)
+  {
+    MOZ_ASSERT(NS_FAILED(aRv));
+    AssertIsOnMainThread();
+  }
+
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
     MOZ_ASSERT(promise);
 
-    if (mClientInfo) {
+    if (NS_WARN_IF(NS_FAILED(mRv))) {
+      promise->MaybeReject(mRv);
+    } else if (mClientInfo) {
       RefPtr<ServiceWorkerWindowClient> client =
         new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo);
       promise->MaybeResolve(client);
     } else {
-      promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+      promise->MaybeResolve(JS::NullHandleValue);
     }
 
     // Release the reference on the worker thread.
     mPromiseProxy->CleanUp();
 
     return true;
   }
 };
@@ -109,19 +141,25 @@ private:
   DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
   {
     AssertIsOnMainThread();
     MutexAutoLock lock(mPromiseProxy->Lock());
     if (mPromiseProxy->CleanedUp()) {
       return;
     }
 
-    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
-      new ResolveOrRejectPromiseRunnable(mPromiseProxy->GetWorkerPrivate(),
-                                         mPromiseProxy, Move(aClientInfo));
+    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+    if (aClientInfo) {
+      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+        mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, Move(aClientInfo));
+    } else {
+      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+        mPromiseProxy->GetWorkerPrivate(), mPromiseProxy,
+        NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    }
 
     resolveRunnable->Dispatch();
   }
 };
 
 } // namespace
 
 already_AddRefed<Promise>
@@ -139,20 +177,349 @@ ServiceWorkerWindowClient::Focus(ErrorRe
     return nullptr;
   }
 
   if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
     RefPtr<PromiseWorkerProxy> promiseProxy =
       PromiseWorkerProxy::Create(workerPrivate, promise);
     if (promiseProxy) {
       RefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
-                                                                promiseProxy);
+                                                              promiseProxy);
       MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
     } else {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     }
 
   } else {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
   return promise.forget();
 }
+
+class WebProgressListener final : public nsIWebProgressListener,
+                                  public nsSupportsWeakReference
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener,
+                                           nsIWebProgressListener)
+
+  WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
+                      nsPIDOMWindowOuter* aWindow, nsIURI* aBaseURI)
+    : mPromiseProxy(aPromiseProxy)
+    , mWindow(aWindow)
+    , mBaseURI(aBaseURI)
+  {
+    MOZ_ASSERT(aPromiseProxy);
+    MOZ_ASSERT(aWindow);
+    MOZ_ASSERT(aWindow->IsOuterWindow());
+    MOZ_ASSERT(aBaseURI);
+    AssertIsOnMainThread();
+
+    mPromiseProxy->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;
+    }
+
+    aWebProgress->RemoveProgressListener(this);
+
+    WorkerPrivate* workerPrivate;
+
+    {
+      MutexAutoLock lock(mPromiseProxy->Lock());
+      if (mPromiseProxy->CleanedUp()) {
+        return NS_OK;
+      }
+
+      workerPrivate = mPromiseProxy->GetWorkerPrivate();
+    }
+
+    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+
+    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+    UniquePtr<ServiceWorkerClientInfo> clientInfo;
+    if (!doc) {
+      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+        workerPrivate, mPromiseProxy, NS_ERROR_TYPE_ERR);
+      resolveRunnable->Dispatch();
+
+      return NS_OK;
+    }
+
+    // Check same origin.
+    nsCOMPtr<nsIScriptSecurityManager> securityManager =
+      nsContentUtils::GetSecurityManager();
+    nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+                                                      mBaseURI, false);
+
+    if (NS_SUCCEEDED(rv)) {
+      nsContentUtils::DispatchFocusChromeEvent(mWindow->GetOuterWindow());
+      clientInfo.reset(new ServiceWorkerClientInfo(doc));
+    }
+
+    resolveRunnable = new ResolveOrRejectPromiseRunnable(
+      workerPrivate, mPromiseProxy, Move(clientInfo));
+    resolveRunnable->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_CRASH("Unexpected notification.");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+                   nsIURI* aLocation, uint32_t aFlags) override
+  {
+    MOZ_CRASH("Unexpected notification.");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+                 nsresult aStatus, const char16_t* aMessage) override
+  {
+    MOZ_CRASH("Unexpected notification.");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+                   uint32_t aState) override
+  {
+    MOZ_CRASH("Unexpected notification.");
+    return NS_OK;
+  }
+
+private:
+  ~WebProgressListener() {}
+
+  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+  nsCOMPtr<nsIURI> mBaseURI;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
+                         mWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
+  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+class ClientNavigateRunnable final : public Runnable
+{
+  uint64_t mWindowId;
+  nsString mUrl;
+  nsCString mBaseUrl;
+  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+  WorkerPrivate* mWorkerPrivate;
+
+public:
+  ClientNavigateRunnable(uint64_t aWindowId, const nsAString& aUrl,
+                         PromiseWorkerProxy* aPromiseProxy)
+    : mWindowId(aWindowId)
+    , mUrl(aUrl)
+    , mPromiseProxy(aPromiseProxy)
+  {
+    MOZ_ASSERT(aPromiseProxy);
+    MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
+    aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsIPrincipal> principal;
+
+    {
+      MutexAutoLock lock(mPromiseProxy->Lock());
+      if (mPromiseProxy->CleanedUp()) {
+        return NS_OK;
+      }
+
+      mWorkerPrivate = mPromiseProxy->GetWorkerPrivate();
+      WorkerPrivate::LocationInfo& info = mWorkerPrivate->GetLocationInfo();
+      mBaseUrl = info.mHref;
+      principal = mWorkerPrivate->GetPrincipal();
+    }
+
+    nsCOMPtr<nsIURI> baseUrl;
+    nsCOMPtr<nsIURI> url;
+    nsresult rv = ParseUrl(getter_AddRefs(baseUrl), getter_AddRefs(url));
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return RejectPromise(NS_ERROR_TYPE_ERR);
+    }
+
+    rv = principal->CheckMayLoad(url, true, false);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return RejectPromise(rv);
+    }
+
+    nsGlobalWindow* window;
+    rv = Navigate(url, principal, &window);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return RejectPromise(rv);
+    }
+
+    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+    nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+    if (NS_WARN_IF(!webProgress)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsCOMPtr<nsIWebProgressListener> listener =
+      new WebProgressListener(mPromiseProxy, window->GetOuterWindow(), baseUrl);
+
+    rv = webProgress->AddProgressListener(
+      listener, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return RejectPromise(rv);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  nsresult
+  RejectPromise(nsresult aRv)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+      new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy, aRv);
+
+    resolveRunnable->Dispatch();
+    return NS_OK;
+  }
+
+  nsresult
+  ResolvePromise(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+      new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy,
+                                         Move(aClientInfo));
+
+    resolveRunnable->Dispatch();
+    return NS_OK;
+  }
+
+  nsresult
+  ParseUrl(nsIURI** aBaseUrl, nsIURI** aUrl)
+  {
+    MOZ_ASSERT(aBaseUrl);
+    MOZ_ASSERT(aUrl);
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsIURI> baseUrl;
+    nsresult rv = NS_NewURI(getter_AddRefs(baseUrl), mBaseUrl);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIURI> url;
+    rv = NS_NewURI(getter_AddRefs(url), mUrl, nullptr, baseUrl);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    baseUrl.forget(aBaseUrl);
+    url.forget(aUrl);
+
+    return NS_OK;
+  }
+
+  nsresult
+  Navigate(nsIURI* aUrl, nsIPrincipal* aPrincipal, nsGlobalWindow** aWindow)
+  {
+    MOZ_ASSERT(aWindow);
+
+    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+    if (NS_WARN_IF(!window)) {
+      return NS_ERROR_TYPE_ERR;
+    }
+
+    nsCOMPtr<nsIDocument> doc = window->GetDocument();
+    if (NS_WARN_IF(!doc)) {
+      return NS_ERROR_TYPE_ERR;
+    }
+
+    if (NS_WARN_IF(!doc->IsActive())) {
+      return NS_ERROR_TYPE_ERR;
+    }
+
+    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+    if (NS_WARN_IF(!docShell)) {
+      return NS_ERROR_TYPE_ERR;
+    }
+
+    nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+    nsresult rv = docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    loadInfo->SetOwner(aPrincipal);
+    loadInfo->SetReferrer(doc->GetOriginalURI());
+    loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
+    loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace);
+    loadInfo->SetSourceDocShell(docShell);
+    rv =
+      docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    *aWindow = window;
+    return NS_OK;
+  }
+};
+
+already_AddRefed<Promise>
+ServiceWorkerWindowClient::Navigate(const nsAString& aUrl, ErrorResult& aRv)
+{
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+  workerPrivate->AssertIsOnWorkerThread();
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  if (aUrl.EqualsLiteral("about:blank")) {
+    promise->MaybeReject(NS_ERROR_TYPE_ERR);
+    return promise.forget();
+  }
+
+  RefPtr<PromiseWorkerProxy> promiseProxy =
+    PromiseWorkerProxy::Create(workerPrivate, promise);
+  if (promiseProxy) {
+    RefPtr<ClientNavigateRunnable> r =
+      new ClientNavigateRunnable(mWindowId, aUrl, promiseProxy);
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
+  } else {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
+
+  return promise.forget();
+}
--- a/dom/workers/ServiceWorkerWindowClient.h
+++ b/dom/workers/ServiceWorkerWindowClient.h
@@ -41,16 +41,19 @@ public:
   Focused() const
   {
     return mFocused;
   }
 
   already_AddRefed<Promise>
   Focus(ErrorResult& aRv) const;
 
+  already_AddRefed<Promise>
+  Navigate(const nsAString& aUrl,  ErrorResult& aRv);
+
 private:
   ~ServiceWorkerWindowClient()
   { }
 
   mozilla::dom::VisibilityState mVisibilityState;
   bool mFocused;
 };