Bug 813253 - URL.createobjectURL in WebWorker. r=bent
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 05 Feb 2013 18:17:17 -0500
changeset 131622 7f82f74de11f43c1b7b11187b82339f92263dd69
parent 131621 af8e5720ccf297c2c222c6c2071bbc97dae1a3a9
child 131623 8f6b848a136ce82809a5d71732efcd4261558710
push id317
push userbbajaj@mozilla.com
push dateTue, 07 May 2013 01:20:33 +0000
treeherdermozilla-release@159a10910249 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs813253
milestone21.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 813253 - URL.createobjectURL in WebWorker. r=bent
dom/bindings/Bindings.conf
dom/workers/Makefile.in
dom/workers/URL.cpp
dom/workers/URL.h
dom/workers/WorkerScope.cpp
dom/workers/test/Makefile.in
dom/workers/test/test_url.html
dom/workers/test/url_worker.js
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -521,16 +521,25 @@ DOMInterfaces = {
 'Location': {
     # NOTE: Before you turn on codegen for Location, make sure all the
     # Unforgeable stuff is dealt with.
     'nativeType': 'nsIDOMLocation',
     'skipGen': True,
     'register': False
 },
 
+'MediaStream': [{
+    'nativeType': 'nsIDOMMediaStream',
+},
+{
+    'nativeType': 'JSObject',
+    'workers': True,
+    'skipGen': True
+}],
+
 'MediaStreamList': {
     'headerFile': 'MediaStreamList.h',
     'wrapperCache': False,
     'nativeOwnership': 'owned',
     'resultNotAddRefed': [ '__indexedGetter' ],
     'binaryNames': { '__indexedGetter': 'IndexedGetter' }
 },
 
@@ -865,19 +874,23 @@ DOMInterfaces = {
 {
     'implicitJSContext': [ 'encode' ],
 },
 {
     'workers': True,
     'implicitJSContext': [ 'encode' ],
 }],
 
-'URL' : {
+'URL' : [{
     'concrete': False,
 },
+{
+    'implicitJSContext': [ 'createObjectURL', 'revokeObjectURL' ],
+    'workers': True,
+}],
 
 'WebGLActiveInfo': {
    'nativeType': 'mozilla::WebGLActiveInfo',
    'headerFile': 'WebGLContext.h',
    'wrapperCache': False
 },
 
 'WebGLBuffer': {
@@ -1227,17 +1240,16 @@ addExternalIface('DOMRequest')
 addExternalIface('DOMStringList')
 addExternalIface('File')
 addExternalIface('HitRegionOptions', nativeType='nsISupports')
 addExternalIface('HTMLHeadElement', nativeType='mozilla::dom::Element')
 addExternalIface('HTMLCanvasElement', nativeType='mozilla::dom::HTMLCanvasElement')
 addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
 addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
 addExternalIface('LockedFile')
-addExternalIface('MediaStream')
 addExternalIface('MozBoxObject', nativeType='nsIBoxObject')
 addExternalIface('MozControllers', nativeType='nsIControllers')
 addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True)
 addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource',
                  notflattened=True)
 addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True)
 addExternalIface('MozXULTemplateBuilder', nativeType='nsIXULTemplateBuilder')
 addExternalIface('NamedNodeMap')
--- a/dom/workers/Makefile.in
+++ b/dom/workers/Makefile.in
@@ -27,16 +27,17 @@ CPPSRCS = \
   File.cpp \
   FileReaderSync.cpp \
   ImageData.cpp \
   Location.cpp \
   Navigator.cpp \
   Principal.cpp \
   RuntimeService.cpp \
   ScriptLoader.cpp \
+  URL.cpp \
   TextDecoder.cpp \
   TextEncoder.cpp \
   Worker.cpp \
   WorkerPrivate.cpp \
   WorkerScope.cpp \
   XMLHttpRequestEventTarget.cpp \
   XMLHttpRequestUpload.cpp \
   XMLHttpRequest.cpp \
@@ -51,16 +52,17 @@ EXPORTS_NAMESPACES = \
 EXPORTS_mozilla/dom/workers = Workers.h
 
 # Stuff needed for the bindings, not really public though.
 EXPORTS_mozilla/dom/workers/bindings = \
   DOMBindingBase.h \
   EventListenerManager.h \
   EventTarget.h \
   FileReaderSync.h \
+  URL.h \
   TextDecoder.h \
   TextEncoder.h \
   WorkerFeature.h \
   XMLHttpRequestEventTarget.h \
   XMLHttpRequestUpload.h \
   XMLHttpRequest.h \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/URL.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* 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 "URL.h"
+#include "File.h"
+
+#include "nsTraceRefcnt.h"
+
+#include "WorkerPrivate.h"
+#include "nsThreadUtils.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindow.h"
+#include "nsHostObjectProtocolHandler.h"
+
+#include "nsIDOMFile.h"
+
+USING_WORKERS_NAMESPACE
+using mozilla::dom::WorkerGlobalObject;
+
+// Base class for the Revoke and Create runnable objects.
+class URLRunnable : public nsRunnable
+{
+protected:
+  WorkerPrivate* mWorkerPrivate;
+  uint32_t mSyncQueueKey;
+
+private:
+  class ResponseRunnable : public WorkerSyncRunnable
+  {
+    uint32_t mSyncQueueKey;
+
+  public:
+    ResponseRunnable(WorkerPrivate* aWorkerPrivate,
+                     uint32_t aSyncQueueKey)
+    : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, false),
+      mSyncQueueKey(aSyncQueueKey)
+    {
+      NS_ASSERTION(aWorkerPrivate, "Don't hand me a null WorkerPrivate!");
+    }
+
+    bool
+    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+    {
+      aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
+      return true;
+    }
+
+    bool
+    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+    {
+      AssertIsOnMainThread();
+      return true;
+    }
+
+    void
+    PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                 bool aDispatchResult)
+    {
+      AssertIsOnMainThread();
+    }
+  };
+
+protected:
+  URLRunnable(WorkerPrivate* aWorkerPrivate)
+  : mWorkerPrivate(aWorkerPrivate)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+public:
+  bool
+  Dispatch(JSContext* aCx)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mSyncQueueKey = mWorkerPrivate->CreateNewSyncLoop();
+
+    if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
+      JS_ReportError(aCx, "Failed to dispatch to main thread!");
+      mWorkerPrivate->StopSyncLoop(mSyncQueueKey, false);
+      return false;
+    }
+
+    return mWorkerPrivate->RunSyncLoop(aCx, mSyncQueueKey);
+  }
+
+private:
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+
+    MainThreadRun();
+
+    nsRefPtr<ResponseRunnable> response =
+      new ResponseRunnable(mWorkerPrivate, mSyncQueueKey);
+    if (!response->Dispatch(nullptr)) {
+      NS_WARNING("Failed to dispatch response!");
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  virtual void
+  MainThreadRun() = 0;
+};
+
+// This class creates an URL from a DOM Blob on the main thread.
+class CreateURLRunnable : public URLRunnable
+{
+private:
+  nsIDOMBlob* mBlob;
+  nsString& mURL;
+
+public:
+  CreateURLRunnable(WorkerPrivate* aWorkerPrivate, nsIDOMBlob* aBlob,
+                    const mozilla::dom::objectURLOptionsWorkers& aOptions,
+                    nsString& aURL)
+  : URLRunnable(aWorkerPrivate),
+    mBlob(aBlob),
+    mURL(aURL)
+  {
+    MOZ_ASSERT(aBlob);
+  }
+
+  void
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
+    nsIDocument* doc = window->GetExtantDoc();
+    if (!doc) {
+      SetDOMStringToNull(mURL);
+      return;
+    }
+
+    nsCString url;
+    nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(
+        NS_LITERAL_CSTRING(BLOBURI_SCHEME),
+        mBlob, doc->NodePrincipal(), url);
+
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to add data entry for the blob!");
+      SetDOMStringToNull(mURL);
+      return;
+    }
+
+    doc->RegisterHostObjectUri(url);
+    mURL = NS_ConvertUTF8toUTF16(url);
+  }
+};
+
+// This class revokes an URL on the main thread.
+class RevokeURLRunnable : public URLRunnable
+{
+private:
+  const nsString mURL;
+
+public:
+  RevokeURLRunnable(WorkerPrivate* aWorkerPrivate,
+                    const nsAString& aURL)
+  : URLRunnable(aWorkerPrivate),
+    mURL(aURL)
+  {}
+
+  void
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
+    nsIDocument* doc = window->GetExtantDoc();
+    if (!doc) {
+      return;
+    }
+
+    NS_ConvertUTF16toUTF8 url(mURL);
+
+    nsIPrincipal* principal =
+      nsHostObjectProtocolHandler::GetDataEntryPrincipal(url);
+
+    bool subsumes;
+    if (principal &&
+        NS_SUCCEEDED(doc->NodePrincipal()->Subsumes(principal, &subsumes)) &&
+        subsumes) {
+      doc->UnregisterHostObjectUri(url);
+      nsHostObjectProtocolHandler::RemoveDataEntry(url);
+    }
+  }
+};
+
+// static
+void
+URL::CreateObjectURL(const WorkerGlobalObject& aGlobal, JSObject* aBlob,
+                     const mozilla::dom::objectURLOptionsWorkers& aOptions,
+                     nsString& aResult, mozilla::ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.GetContext();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+  nsCOMPtr<nsIDOMBlob> blob = file::GetDOMBlobFromJSObject(aBlob);
+  if (!blob) {
+    SetDOMStringToNull(aResult);
+    aRv.Throw(NS_ERROR_TYPE_ERR);
+    return;
+  }
+
+  nsRefPtr<CreateURLRunnable> runnable =
+    new CreateURLRunnable(workerPrivate, blob, aOptions, aResult);
+
+  if (!runnable->Dispatch(cx)) {
+    JS_ReportPendingException(cx);
+  }
+}
+
+// static
+void
+URL::RevokeObjectURL(const WorkerGlobalObject& aGlobal, const nsAString& aUrl)
+{
+  JSContext* cx = aGlobal.GetContext();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+  nsRefPtr<RevokeURLRunnable> runnable =
+    new RevokeURLRunnable(workerPrivate, aUrl);
+
+  if (!runnable->Dispatch(cx)) {
+    JS_ReportPendingException(cx);
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/URL.h
@@ -0,0 +1,30 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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
+ * url, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_url_h__
+#define mozilla_dom_workers_url_h__
+
+#include "mozilla/dom/URLBinding.h"
+
+#include "EventTarget.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+class URL : public EventTarget
+{
+public: // Methods for WebIDL
+  static void
+  CreateObjectURL(const WorkerGlobalObject& aGlobal,
+                  JSObject* aArg, const objectURLOptionsWorkers& aOptions,
+                  nsString& aResult, ErrorResult& aRv);
+
+  static void
+  RevokeObjectURL(const WorkerGlobalObject& aGlobal, const nsAString& aUrl);
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_url_h__ */
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/EventTargetBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/FileReaderSyncBinding.h"
 #include "mozilla/dom/TextDecoderBinding.h"
 #include "mozilla/dom/TextEncoderBinding.h"
 #include "mozilla/dom/XMLHttpRequestBinding.h"
 #include "mozilla/dom/XMLHttpRequestUploadBinding.h"
+#include "mozilla/dom/URLBinding.h"
 #include "mozilla/OSFileConstants.h"
 #include "nsTraceRefcnt.h"
 #include "xpcpublic.h"
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 
@@ -981,17 +982,18 @@ CreateDedicatedWorkerGlobalScope(JSConte
     return NULL;
   }
 
   // Init other paris-bindings.
   if (!FileReaderSyncBinding_workers::GetConstructorObject(aCx, global) ||
       !TextDecoderBinding_workers::GetConstructorObject(aCx, global) ||
       !TextEncoderBinding_workers::GetConstructorObject(aCx, global) ||
       !XMLHttpRequestBinding_workers::GetConstructorObject(aCx, global) ||
-      !XMLHttpRequestUploadBinding_workers::GetConstructorObject(aCx, global)) {
+      !XMLHttpRequestUploadBinding_workers::GetConstructorObject(aCx, global) ||
+      !URLBinding_workers::GetConstructorObject(aCx, global)) {
     return NULL;
   }
 
   if (!JS_DefineProfilingFunctions(aCx, global)) {
     return NULL;
   }
 
   return global;
--- a/dom/workers/test/Makefile.in
+++ b/dom/workers/test/Makefile.in
@@ -103,16 +103,18 @@ MOCHITEST_FILES = \
   test_csp.html^headers^ \
   csp_worker.js \
   test_transferable.html \
   transferable_worker.js \
   test_errorwarning.html \
   errorwarning_worker.js \
   test_contentWorker.html \
   content_worker.js \
+  test_url.html \
+  url_worker.js \
   $(NULL)
 
 _SUBDIRMOCHITEST_FILES = \
   relativeLoad_sub_worker.js \
   relativeLoad_sub_worker2.js \
   relativeLoad_sub_import.js \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_url.html
@@ -0,0 +1,53 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for URL object in workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var worker = new Worker("url_worker.js");
+
+  worker.onmessage = function(event) {
+    is(event.target, worker);
+
+    if (event.data.type == 'finish') {
+      SimpleTest.finish();
+    } else if (event.data.type == 'status') {
+      ok(event.data.status, event.data.msg);
+    } else if (event.data.type == 'url') {
+      var xhr = new XMLHttpRequest();
+      xhr.open('GET', event.data.url, false);
+      xhr.onreadystatechange = function() {
+        if (xhr.readyState == 4) {
+          ok(true, "Blob readable!");
+        }
+      }
+      xhr.send();
+    }
+  };
+
+  worker.onerror = function(event) {
+    is(event.target, worker);
+    ok(false, "Worker had an error: " + event.data);
+    SimpleTest.finish();
+  };
+
+  worker.postMessage(true);
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/url_worker.js
@@ -0,0 +1,63 @@
+onmessage = function() {
+  status = false;
+  try {
+    if ((URL instanceof Object)) {
+      status = true;
+    }
+  } catch(e) {
+  }
+
+  postMessage({type: 'status', status: status, msg: 'URL object:' + URL});
+
+  status = false;
+  var blob = null;
+  try {
+    blob = new Blob([]);
+    status = true;
+  } catch(e) {
+  }
+
+  postMessage({type: 'status', status: status, msg: 'Blob:' + blob});
+
+  status = false;
+  var url = null;
+  try {
+    url = URL.createObjectURL(blob);
+    status = true;
+  } catch(e) {
+  }
+
+  postMessage({type: 'status', status: status, msg: 'Blob URL:' + url});
+
+  status = false;
+  try {
+    URL.revokeObjectURL(url);
+    status = true;
+  } catch(e) {
+  }
+
+  postMessage({type: 'status', status: status, msg: 'Blob Revoke URL'});
+
+  status = false;
+  var url = null;
+  try {
+    url = URL.createObjectURL(true);
+  } catch(e) {
+    status = true;
+  }
+
+  postMessage({type: 'status', status: status, msg: 'CreateObjectURL should fail if the arg is not a blob'});
+
+  status = false;
+  var url = null;
+  try {
+    url = URL.createObjectURL(blob);
+    status = true;
+  } catch(e) {
+  }
+
+  postMessage({type: 'status', status: status, msg: 'Blob URL2:' + url});
+  postMessage({type: 'url', url: url});
+
+  postMessage({type: 'finish' });
+}