Bug 1151495 - Support permission prompting from workers for IDB, r=bent
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 06 May 2015 09:07:57 +0100
changeset 272439 9641952c788f9a6ba52f7d773b2887d6d0493641
parent 272438 4bfe96dd2a319d180def80d327defd7da4f8e6bb
child 272440 e7f7dc49cf08a6e833ab0a03aa7889ae14fcf00a
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1151495
milestone40.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 1151495 - Support permission prompting from workers for IDB, r=bent
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/test/browser.ini
dom/indexedDB/test/browser_permissionsPromptWorker.js
dom/indexedDB/test/browser_permissionsSharedWorker.html
dom/indexedDB/test/browser_permissionsSharedWorker.js
dom/indexedDB/test/browser_permissionsWorker.html
dom/indexedDB/test/browser_permissionsWorker.js
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -35,16 +35,18 @@
 #include "nsIDOMEvent.h"
 #include "nsIEventTarget.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 #ifdef DEBUG
 #include "IndexedDatabaseManager.h"
 #endif
 
 #define GC_ON_IPC_MESSAGES 0
 
 #if defined(DEBUG) || GC_ON_IPC_MESSAGES
@@ -52,17 +54,23 @@
 #include "js/GCAPI.h"
 #include "nsJSEnvironment.h"
 
 #define BUILD_GC_ON_IPC_MESSAGES
 
 #endif // DEBUG || GC_ON_IPC_MESSAGES
 
 namespace mozilla {
+
+using ipc::PrincipalInfo;
+
 namespace dom {
+
+using namespace workers;
+
 namespace indexedDB {
 
 /*******************************************************************************
  * ThreadLocal
  ******************************************************************************/
 
 ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId)
   : mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1)
@@ -772,16 +780,245 @@ DispatchSuccessEvent(ResultHelper* aResu
 
   if (transaction &&
       transaction->IsOpen() &&
       internalEvent->mFlags.mExceptionHasBeenRisen) {
     transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
   }
 }
 
+class WorkerPermissionChallenge;
+
+// This class calles WorkerPermissionChallenge::OperationCompleted() in the
+// worker thread.
+class WorkerPermissionOperationCompleted final : public WorkerRunnable
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
+                                     WorkerPermissionChallenge* aChallenge)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+};
+
+// This class used to do prompting in the main thread and main process.
+class WorkerPermissionRequest final : public PermissionRequestBase
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  WorkerPermissionRequest(Element* aElement,
+                          nsIPrincipal* aPrincipal,
+                          WorkerPermissionChallenge* aChallenge)
+    : PermissionRequestBase(aElement, aPrincipal)
+    , mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aChallenge);
+  }
+
+private:
+  ~WorkerPermissionRequest()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual void
+  OnPromptComplete(PermissionValue aPermissionValue) override;
+};
+
+// This class is used in the main thread of all child processes.
+class WorkerPermissionRequestChildProcessActor final
+  : public PIndexedDBPermissionRequestChild
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  explicit WorkerPermissionRequestChildProcessActor(
+                                          WorkerPermissionChallenge* aChallenge)
+    : mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aChallenge);
+  }
+
+protected:
+  ~WorkerPermissionRequestChildProcessActor()
+  {}
+
+  virtual bool
+  Recv__delete__(const uint32_t& aPermission) override;
+};
+
+class WorkerPermissionChallenge final : public nsRunnable
+                                      , public WorkerFeature
+{
+public:
+  WorkerPermissionChallenge(WorkerPrivate* aWorkerPrivate,
+                            BackgroundFactoryRequestChild* aActor,
+                            IDBFactory* aFactory,
+                            const PrincipalInfo& aPrincipalInfo)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mActor(aActor)
+    , mFactory(aFactory)
+    , mPrincipalInfo(aPrincipalInfo)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    MOZ_ASSERT(aActor);
+    MOZ_ASSERT(aFactory);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    bool completed = RunInternal();
+    if (completed) {
+      OperationCompleted();
+    }
+
+    return NS_OK;
+  }
+
+  virtual bool
+  Notify(JSContext* aCx, workers::Status aStatus) override
+  {
+    // We don't care about the notification. We just want to keep the
+    // mWorkerPrivate alive.
+    return true;
+  }
+
+  void
+  OperationCompleted()
+  {
+    if (NS_IsMainThread()) {
+      nsRefPtr<WorkerPermissionOperationCompleted> runnable =
+        new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
+
+      if (!runnable->Dispatch(nullptr)) {
+        NS_WARNING("Failed to dispatch a runnable to the worker thread.");
+        return;
+      }
+
+      return;
+    }
+
+    MOZ_ASSERT(mActor);
+    mActor->AssertIsOnOwningThread();
+
+    MaybeCollectGarbageOnIPCMessage();
+
+    nsRefPtr<IDBFactory> factory;
+    mFactory.swap(factory);
+
+    mActor->SendPermissionRetry();
+    mActor = nullptr;
+
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+    mWorkerPrivate->RemoveFeature(cx, this);
+  }
+
+private:
+  bool
+  RunInternal()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Walk up to our containing page
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    nsPIDOMWindow* window = wp->GetWindow();
+    if (!window) {
+      return true;
+    }
+
+    nsresult rv;
+    nsCOMPtr<nsIPrincipal> principal =
+      mozilla::ipc::PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return true;
+    }
+
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+      nsCOMPtr<Element> ownerElement =
+        do_QueryInterface(window->GetChromeEventHandler());
+      if (NS_WARN_IF(!ownerElement)) {
+        return true;
+      }
+
+      nsRefPtr<WorkerPermissionRequest> helper =
+        new WorkerPermissionRequest(ownerElement, principal, this);
+
+      PermissionRequestBase::PermissionValue permission;
+      if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
+        return true;
+      }
+
+      MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
+                 permission == PermissionRequestBase::kPermissionDenied ||
+                 permission == PermissionRequestBase::kPermissionPrompt);
+
+      return permission != PermissionRequestBase::kPermissionPrompt;
+    }
+
+    TabChild* tabChild = TabChild::GetFrom(window);
+    MOZ_ASSERT(tabChild);
+
+    IPC::Principal ipcPrincipal(principal);
+
+    auto* actor = new WorkerPermissionRequestChildProcessActor(this);
+    tabChild->SendPIndexedDBPermissionRequestConstructor(actor, ipcPrincipal);
+    return false;
+  }
+
+private:
+  WorkerPrivate* mWorkerPrivate;
+  BackgroundFactoryRequestChild* mActor;
+  nsRefPtr<IDBFactory> mFactory;
+  PrincipalInfo mPrincipalInfo;
+};
+
+void
+WorkerPermissionRequest::OnPromptComplete(PermissionValue aPermissionValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mChallenge->OperationCompleted();
+}
+
+bool
+WorkerPermissionOperationCompleted::WorkerRun(JSContext* aCx,
+                                              WorkerPrivate* aWorkerPrivate)
+{
+  aWorkerPrivate->AssertIsOnWorkerThread();
+  mChallenge->OperationCompleted();
+  return true;
+}
+
+bool
+WorkerPermissionRequestChildProcessActor::Recv__delete__(
+                                              const uint32_t& /* aPermission */)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mChallenge->OperationCompleted();
+  return true;
+}
+
 } // anonymous namespace
 
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 void
 PermissionRequestMainProcessHelper::OnPromptComplete(
@@ -1119,17 +1356,33 @@ bool
 BackgroundFactoryRequestChild::RecvPermissionChallenge(
                                             const PrincipalInfo& aPrincipalInfo)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
   if (!NS_IsMainThread()) {
-    MOZ_CRASH("Implement me for workers!");
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+
+    nsRefPtr<WorkerPermissionChallenge> challenge =
+      new WorkerPermissionChallenge(workerPrivate, this, mFactory,
+                                    aPrincipalInfo);
+
+    JSContext* cx = workerPrivate->GetJSContext();
+    MOZ_ASSERT(cx);
+
+    if (!workerPrivate->AddFeature(cx, challenge)) {
+      return false;
+    }
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(challenge)));
+    return true;
   }
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
     mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
--- a/dom/indexedDB/test/browser.ini
+++ b/dom/indexedDB/test/browser.ini
@@ -1,16 +1,21 @@
 [DEFAULT]
 skip-if = (buildapp != "browser") || e10s
 support-files =
   head.js
   browser_forgetThisSiteAdd.html
   browser_forgetThisSiteGet.html
   browserHelpers.js
   browser_permissionsPrompt.html
+  browser_permissionsSharedWorker.html
+  browser_permissionsSharedWorker.js
+  browser_permissionsWorker.html
+  browser_permissionsWorker.js
   bug839193.js
   bug839193.xul
 
 [browser_forgetThisSite.js]
 [browser_permissionsPromptAllow.js]
 [browser_permissionsPromptDeny.js]
+[browser_permissionsPromptWorker.js]
 [browser_perwindow_privateBrowsing.js]
 [browser_bug839193.js]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsPromptWorker.js
@@ -0,0 +1,91 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testWorkerURL = "http://mochi.test:8888/browser/" +
+  "dom/indexedDB/test/browser_permissionsWorker.html";
+const testSharedWorkerURL = "http://mochi.test:8888/browser/" +
+  "dom/indexedDB/test/browser_permissionsSharedWorker.html";
+const notificationID = "indexedDB-permissions-prompt";
+
+function test()
+{
+  waitForExplicitFinish();
+  executeSoon(test1);
+}
+
+function test1()
+{
+  // We want a prompt.
+  removePermission(testWorkerURL, "indexedDB");
+
+  info("creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  gBrowser.selectedBrowser.addEventListener("load", function () {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    setFinishedCallback(function(isIDBDatabase, exception) {
+      ok(isIDBDatabase, "First database creation was successful");
+      ok(!exception, "No exception");
+      is(getPermission(testWorkerURL, "indexedDB"),
+         Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
+         "Correct permission set");
+      gBrowser.removeCurrentTab();
+      executeSoon(test2);
+    });
+
+    registerPopupEventHandler("popupshowing", function () {
+      ok(true, "prompt showing");
+    });
+    registerPopupEventHandler("popupshown", function () {
+      ok(true, "prompt shown");
+      triggerMainCommand(this);
+    });
+    registerPopupEventHandler("popuphidden", function () {
+      ok(true, "prompt hidden");
+    });
+
+  }, true);
+
+  info("loading test page: " + testWorkerURL);
+  content.location = testWorkerURL;
+}
+
+function test2()
+{
+  // We want a prompt.
+  removePermission(testSharedWorkerURL, "indexedDB");
+
+  info("creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  gBrowser.selectedBrowser.addEventListener("load", function () {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    setFinishedCallback(function(isIDBDatabase, exception) {
+      ok(!isIDBDatabase, "First database creation was successful");
+      ok(exception, "No exception");
+      is(getPermission(testSharedWorkerURL, "indexedDB"),
+         Components.interfaces.nsIPermissionManager.UNKNOWN_ACTION,
+         "Correct permission set");
+      gBrowser.removeCurrentTab();
+      executeSoon(finish);
+    });
+
+    registerPopupEventHandler("popupshowing", function () {
+      ok(false, "prompt showing");
+    });
+    registerPopupEventHandler("popupshown", function () {
+      ok(false, "prompt shown");
+    });
+    registerPopupEventHandler("popuphidden", function () {
+      ok(false, "prompt hidden");
+    });
+
+  }, true);
+
+  info("loading test page: " + testSharedWorkerURL);
+  content.location = testSharedWorkerURL;
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.html
@@ -0,0 +1,34 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <title>Indexed Database Test</title>
+
+    <script type="text/javascript;version=1.7">
+    let testIsIDBDatabase;
+    let testException;
+
+    function runTest() {
+      let w = new SharedWorker('browser_permissionsSharedWorker.js');
+      w.port.onmessage = function(e) {
+        if (e.data.status == 'success') {
+          testIsIDBDatabase = e.data.isIDBDatabase;
+        } else {
+          testException = e.data.error;
+        }
+
+        setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
+      }
+
+      const name = window.location.pathname + "_sharedWorker";
+      w.port.postMessage(name);
+    }
+    </script>
+
+  </head>
+
+  <body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.js
@@ -0,0 +1,14 @@
+onconnect = function(e) {
+  e.ports[0].onmessage = function(e) {
+    var request = indexedDB.open(e.data, { version: 1,
+                                           storage: "persistent" });
+    request.onsuccess = function(event) {
+      e.target.postMessage({ status: 'success',
+                             isIDBDatabase: (event.target.result instanceof IDBDatabase) });
+    }
+
+    request.onerror = function(event) {
+      e.target.postMessage({ status: 'error', error: event.target.error.name });
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsWorker.html
@@ -0,0 +1,34 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <title>Indexed Database Test</title>
+
+    <script type="text/javascript;version=1.7">
+    let testIsIDBDatabase;
+    let testException;
+
+    function runTest() {
+      let w = new Worker('browser_permissionsWorker.js');
+      w.onmessage = function(e) {
+        if (e.data.status == 'success') {
+          testIsIDBDatabase = e.data.isIDBDatabase;
+        } else {
+          testException = e.data.error;
+        }
+
+        setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
+      }
+
+      const name = window.location.pathname;
+      w.postMessage(name);
+    }
+    </script>
+
+  </head>
+
+  <body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsWorker.js
@@ -0,0 +1,12 @@
+onmessage = function(e) {
+  var request = indexedDB.open(e.data, { version: 1,
+                                         storage: "persistent" });
+  request.onsuccess = function(event) {
+    postMessage({ status: 'success',
+                  isIDBDatabase: (event.target.result instanceof IDBDatabase) });
+  }
+
+  request.onerror = function(event) {
+    postMessage({ status: 'error', error: event.target.error.name });
+  }
+}