Bug 887364 - URL API for Workers. r=khuey
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 04 Sep 2013 13:07:34 -0400
changeset 145471 c5fda7e3b3f42c3fc8ea4fe12f9dc0c0a79d4f10
parent 145470 f2199d73aef6c7d68fe54ba360a676d2b2108b3f
child 145472 ecd7cbc7c1790fdc8e74d7db98c27c7f53549803
push id25213
push userkwierso@gmail.com
push dateWed, 04 Sep 2013 23:18:26 +0000
treeherdermozilla-central@dffedf20a02d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs887364
milestone26.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 887364 - URL API for Workers. r=khuey
dom/base/URL.h
dom/workers/DOMBindingInlines.h
dom/workers/URL.cpp
dom/workers/URL.h
dom/workers/test/Makefile.in
dom/workers/test/test_urlApi.html
dom/workers/test/urlApi_worker.js
--- a/dom/base/URL.h
+++ b/dom/base/URL.h
@@ -21,16 +21,20 @@ class ErrorResult;
 class DOMMediaStream;
 
 namespace dom {
 
 class MediaSource;
 class GlobalObject;
 struct objectURLOptions;
 
+namespace workers {
+class URLProxy;
+}
+
 class URL MOZ_FINAL
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(URL)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(URL)
 
   URL(nsPIDOMWindow* aWindow, nsIURI* aURI);
 
@@ -119,14 +123,16 @@ private:
   static void CreateObjectURLInternal(nsISupports* aGlobal, nsISupports* aObject,
                                       const nsACString& aScheme,
                                       const objectURLOptions& aOptions,
                                       nsString& aResult,
                                       ErrorResult& aError);
 
   nsRefPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIURI> mURI;
+
+  friend class mozilla::dom::workers::URLProxy;
 };
 
 }
 }
 
 #endif /* URL_h___ */
--- a/dom/workers/DOMBindingInlines.h
+++ b/dom/workers/DOMBindingInlines.h
@@ -7,25 +7,27 @@
 #define mozilla_dom_workers_dombindinginlines_h__
 
 #include "mozilla/dom/FileReaderSyncBinding.h"
 #include "mozilla/dom/JSSlots.h"
 #include "mozilla/dom/XMLHttpRequestBinding.h"
 #include "mozilla/dom/XMLHttpRequestUploadBinding.h"
 #include "mozilla/dom/WorkerLocationBinding.h"
 #include "mozilla/dom/WorkerNavigatorBinding.h"
+#include "mozilla/dom/URLBinding.h"
 #include "jsfriendapi.h"
 
 BEGIN_WORKERS_NAMESPACE
 
 class FileReaderSync;
 class XMLHttpRequest;
 class XMLHttpRequestUpload;
 class WorkerLocation;
 class WorkerNavigator;
+class URL;
 
 namespace {
 
 template <class T>
 struct WrapPrototypeTraits
 { };
 
 // XXX I kinda hate this, but we decided it wasn't worth generating this in the
@@ -49,16 +51,17 @@ struct WrapPrototypeTraits
     }                                                                          \
   };
 
 SPECIALIZE_PROTO_TRAITS(FileReaderSync)
 SPECIALIZE_PROTO_TRAITS(XMLHttpRequest)
 SPECIALIZE_PROTO_TRAITS(XMLHttpRequestUpload)
 SPECIALIZE_PROTO_TRAITS(WorkerLocation)
 SPECIALIZE_PROTO_TRAITS(WorkerNavigator)
+SPECIALIZE_PROTO_TRAITS(URL)
 
 #undef SPECIALIZE_PROTO_TRAITS
 
 } // anonymous namespace
 
 template <class T>
 inline JSObject*
 Wrap(JSContext* aCx, JSObject* aGlobal, nsRefPtr<T>& aConcreteObject)
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -9,24 +9,66 @@
 #include "nsTraceRefcnt.h"
 
 #include "WorkerPrivate.h"
 #include "nsThreadUtils.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsGlobalWindow.h"
 #include "nsHostObjectProtocolHandler.h"
+#include "nsServiceManagerUtils.h"
 
 #include "nsIDocument.h"
 #include "nsIDOMFile.h"
 
-USING_WORKERS_NAMESPACE
+#include "DOMBindingInlines.h"
+#include "mozilla/dom/URL.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+
+BEGIN_WORKERS_NAMESPACE
 using mozilla::dom::GlobalObject;
 
-// Base class for the Revoke and Create runnable objects.
+class URLProxy MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
+
+  URLProxy(mozilla::dom::URL* aURL)
+    : mURL(aURL)
+  {
+    AssertIsOnMainThread();
+  }
+
+  ~URLProxy()
+  {
+     MOZ_ASSERT(!mURL);
+  }
+
+  mozilla::dom::URL* URL()
+  {
+    return mURL;
+  }
+
+  nsIURI* URI()
+  {
+    return mURL->GetURI();
+  }
+
+  void ReleaseURI()
+  {
+    AssertIsOnMainThread();
+    mURL = nullptr;
+  }
+
+private:
+  nsRefPtr<mozilla::dom::URL> mURL;
+};
+
+// Base class for the URL runnable objects.
 class URLRunnable : public nsRunnable
 {
 protected:
   WorkerPrivate* mWorkerPrivate;
   uint32_t mSyncQueueKey;
 
 private:
   class ResponseRunnable : public WorkerSyncRunnable
@@ -220,137 +262,639 @@ public:
     }
 
     if (!window) {
       mWorkerPrivate->UnregisterHostObjectURI(url);
     }
   }
 };
 
+// This class creates a URL object on the main thread.
+class ConstructorRunnable : public URLRunnable
+{
+private:
+  const nsString mURL;
+
+  const nsString mBase;
+  nsRefPtr<URLProxy> mBaseProxy;
+  mozilla::ErrorResult& mRv;
+
+  nsRefPtr<URLProxy> mRetval;
+
+public:
+  ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+                      const nsAString& aURL, const nsAString& aBase,
+                      mozilla::ErrorResult& aRv)
+  : URLRunnable(aWorkerPrivate)
+  , mURL(aURL)
+  , mBase(aBase)
+  , mRv(aRv)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+                      const nsAString& aURL, URLProxy* aBaseProxy,
+                      mozilla::ErrorResult& aRv)
+  : URLRunnable(aWorkerPrivate)
+  , mURL(aURL)
+  , mBaseProxy(aBaseProxy)
+  , mRv(aRv)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  void
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    nsresult rv;
+    nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
+    if (NS_FAILED(rv)) {
+      mRv.Throw(rv);
+      return;
+    }
+
+    nsCOMPtr<nsIURI> baseURL;
+
+    if (!mBaseProxy) {
+      rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mBase), nullptr, nullptr,
+                             getter_AddRefs(baseURL));
+      if (NS_FAILED(rv)) {
+        mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return;
+      }
+    } else {
+      baseURL = mBaseProxy->URI();
+    }
+
+    nsCOMPtr<nsIURI> url;
+    rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mURL), nullptr, baseURL,
+                           getter_AddRefs(url));
+    if (NS_FAILED(rv)) {
+      mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return;
+    }
+
+    mRetval = new URLProxy(new mozilla::dom::URL(nullptr, url));
+  }
+
+  URLProxy*
+  GetURLProxy()
+  {
+    return mRetval;
+  }
+};
+
+class TeardownRunnable : public nsRunnable
+{
+public:
+  TeardownRunnable(URLProxy* aURLProxy)
+    : mURLProxy(aURLProxy)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+
+    mURLProxy->ReleaseURI();
+    mURLProxy = nullptr;
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<URLProxy> mURLProxy;
+};
+
+// This class is the generic getter for any URL property.
+class GetterRunnable : public URLRunnable
+{
+public:
+  enum GetterType {
+    GetterHref,
+    GetterOrigin,
+    GetterProtocol,
+    GetterUsername,
+    GetterPassword,
+    GetterHost,
+    GetterHostname,
+    GetterPort,
+    GetterPathname,
+    GetterSearch,
+    GetterHash,
+  };
+
+  GetterRunnable(WorkerPrivate* aWorkerPrivate,
+                 GetterType aType, nsString& aValue,
+                 URLProxy* aURLProxy)
+  : URLRunnable(aWorkerPrivate)
+  , mValue(aValue)
+  , mType(aType)
+  , mURLProxy(aURLProxy)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  void
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    switch (mType) {
+      case GetterHref:
+        mURLProxy->URL()->GetHref(mValue);
+        break;
+
+      case GetterOrigin:
+        mURLProxy->URL()->GetOrigin(mValue);
+        break;
+
+      case GetterProtocol:
+        mURLProxy->URL()->GetProtocol(mValue);
+        break;
+
+      case GetterUsername:
+        mURLProxy->URL()->GetUsername(mValue);
+        break;
+
+      case GetterPassword:
+        mURLProxy->URL()->GetPassword(mValue);
+        break;
+
+      case GetterHost:
+        mURLProxy->URL()->GetHost(mValue);
+        break;
+
+      case GetterHostname:
+        mURLProxy->URL()->GetHostname(mValue);
+        break;
+
+      case GetterPort:
+        mURLProxy->URL()->GetPort(mValue);
+        break;
+
+      case GetterPathname:
+        mURLProxy->URL()->GetPathname(mValue);
+        break;
+
+      case GetterSearch:
+        mURLProxy->URL()->GetSearch(mValue);
+        break;
+
+      case GetterHash:
+        mURLProxy->URL()->GetHash(mValue);
+        break;
+    }
+  }
+
+private:
+  nsString& mValue;
+  GetterType mType;
+  nsRefPtr<URLProxy> mURLProxy;
+};
+
+// This class is the generic setter for any URL property.
+class SetterRunnable : public URLRunnable
+{
+public:
+  enum SetterType {
+    SetterHref,
+    SetterProtocol,
+    SetterUsername,
+    SetterPassword,
+    SetterHost,
+    SetterHostname,
+    SetterPort,
+    SetterPathname,
+    SetterSearch,
+    SetterHash,
+  };
+
+  SetterRunnable(WorkerPrivate* aWorkerPrivate,
+                 SetterType aType, const nsAString& aValue,
+                 URLProxy* aURLProxy, mozilla::ErrorResult& aRv)
+  : URLRunnable(aWorkerPrivate)
+  , mValue(aValue)
+  , mType(aType)
+  , mURLProxy(aURLProxy)
+  , mRv(aRv)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  void
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    switch (mType) {
+      case SetterHref:
+        mURLProxy->URL()->SetHref(mValue, mRv);
+        break;
+
+      case SetterProtocol:
+        mURLProxy->URL()->SetProtocol(mValue);
+        break;
+
+      case SetterUsername:
+        mURLProxy->URL()->SetUsername(mValue);
+        break;
+
+      case SetterPassword:
+        mURLProxy->URL()->SetPassword(mValue);
+        break;
+
+      case SetterHost:
+        mURLProxy->URL()->SetHost(mValue);
+        break;
+
+      case SetterHostname:
+        mURLProxy->URL()->SetHostname(mValue);
+        break;
+
+      case SetterPort:
+        mURLProxy->URL()->SetPort(mValue);
+        break;
+
+      case SetterPathname:
+        mURLProxy->URL()->SetPathname(mValue);
+        break;
+
+      case SetterSearch:
+        mURLProxy->URL()->SetSearch(mValue);
+        break;
+
+      case SetterHash:
+        mURLProxy->URL()->SetHash(mValue);
+        break;
+    }
+  }
+
+private:
+  const nsString mValue;
+  SetterType mType;
+  nsRefPtr<URLProxy> mURLProxy;
+  mozilla::ErrorResult& mRv;
+};
+
 // static
 URL*
 URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
                  URL& aBase, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return nullptr;
+  JSContext* cx = aGlobal.GetContext();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+  nsRefPtr<ConstructorRunnable> runnable =
+    new ConstructorRunnable(workerPrivate, aUrl, aBase.GetURLProxy(), aRv);
+
+  if (!runnable->Dispatch(cx)) {
+    JS_ReportPendingException(cx);
+  }
+
+  nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
+  if (!proxy) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<URL> url = new URL(workerPrivate, proxy);
+
+  if (!Wrap(aGlobal.GetContext(), aGlobal.Get(), url)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  return url;
 }
 
 // static
 URL*
 URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
                  const nsAString& aBase, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return nullptr;
+  JSContext* cx = aGlobal.GetContext();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+  nsRefPtr<ConstructorRunnable> runnable =
+    new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv);
+
+  if (!runnable->Dispatch(cx)) {
+    JS_ReportPendingException(cx);
+  }
+
+  nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
+  if (!proxy) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<URL> url = new URL(workerPrivate, proxy);
+
+  if (!Wrap(aGlobal.GetContext(), aGlobal.Get(), url)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  return url;
+}
+
+URL::URL(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy)
+  : DOMBindingBase(aWorkerPrivate->GetJSContext())
+  , mWorkerPrivate(aWorkerPrivate)
+  , mURLProxy(aURLProxy)
+{
+}
+
+URL::~URL()
+{
+  if (mURLProxy) {
+    nsRefPtr<TeardownRunnable> runnable = new TeardownRunnable(mURLProxy);
+    mURLProxy = nullptr;
+
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      NS_ERROR("Failed to dispatch teardown runnable!");
+    }
+  }
+}
+
+void
+URL::_trace(JSTracer* aTrc)
+{
+  DOMBindingBase::_trace(aTrc);
+}
+
+void
+URL::_finalize(JSFreeOp* aFop)
+{
+  DOMBindingBase::_finalize(aFop);
 }
 
 void
 URL::GetHref(nsString& aHref) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHref, aHref,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetHref(const nsAString& aHref, ErrorResult& aRv)
 {
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHref, aHref,
+                       mURLProxy, aRv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetOrigin(nsString& aOrigin) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterOrigin, aOrigin,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetProtocol(nsString& aProtocol) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterProtocol, aProtocol,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetProtocol(const nsAString& aProtocol)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterProtocol,
+                       aProtocol, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetUsername(nsString& aUsername) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterUsername, aUsername,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetUsername(const nsAString& aUsername)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterUsername,
+                       aUsername, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetPassword(nsString& aPassword) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPassword, aPassword,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetPassword(const nsAString& aPassword)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPassword,
+                       aPassword, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetHost(nsString& aHost) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHost, aHost,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetHost(const nsAString& aHost)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHost,
+                       aHost, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetHostname(nsString& aHostname) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHostname, aHostname,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetHostname(const nsAString& aHostname)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHostname,
+                       aHostname, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetPort(nsString& aPort) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPort, aPort,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetPort(const nsAString& aPort)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPort,
+                       aPort, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetPathname(nsString& aPathname) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPathname, aPathname,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetPathname(const nsAString& aPathname)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPathname,
+                       aPathname, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetSearch(nsString& aSearch) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterSearch, aSearch,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetSearch(const nsAString& aSearch)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterSearch,
+                       aSearch, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::GetHash(nsString& aHash) const
 {
+  nsRefPtr<GetterRunnable> runnable =
+    new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHash, aHash,
+                       mURLProxy);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 void
 URL::SetHash(const nsAString& aHash)
 {
+  ErrorResult rv;
+  nsRefPtr<SetterRunnable> runnable =
+    new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHash,
+                       aHash, mURLProxy, rv);
+
+  if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
+    JS_ReportPendingException(mWorkerPrivate->GetJSContext());
+  }
 }
 
 // static
 void
 URL::CreateObjectURL(const GlobalObject& aGlobal, JSObject* aBlob,
                      const mozilla::dom::objectURLOptions& aOptions,
                      nsString& aResult, mozilla::ErrorResult& aRv)
 {
@@ -394,8 +938,9 @@ URL::RevokeObjectURL(const GlobalObject&
   nsRefPtr<RevokeURLRunnable> runnable =
     new RevokeURLRunnable(workerPrivate, aUrl);
 
   if (!runnable->Dispatch(cx)) {
     JS_ReportPendingException(cx);
   }
 }
 
+END_WORKERS_NAMESPACE
--- a/dom/workers/URL.h
+++ b/dom/workers/URL.h
@@ -2,25 +2,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/workers/bindings/DOMBindingBase.h"
 #include "mozilla/dom/URLBinding.h"
 
 #include "EventTarget.h"
 
 BEGIN_WORKERS_NAMESPACE
 
-class URL : public EventTarget
+class URLProxy;
+
+class URL MOZ_FINAL : public DOMBindingBase
 {
-public: // Methods for WebIDL
+public:
+
+  URL(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy);
+  ~URL();
+
+  virtual void
+  _trace(JSTracer* aTrc) MOZ_OVERRIDE;
+
+  virtual void
+  _finalize(JSFreeOp* aFop) MOZ_OVERRIDE;
+
+  // Methods for WebIDL
+
   static URL*
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               URL& aBase, ErrorResult& aRv);
   static URL*
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               const nsAString& aBase, ErrorResult& aRv);
 
   static void
@@ -74,19 +89,20 @@ public: // Methods for WebIDL
 
   void SetSearch(const nsAString& aSearch);
 
   void GetHash(nsString& aHost) const;
 
   void SetHash(const nsAString& aHash);
 
 private:
-  mozilla::dom::URL* GetURL() const
+  URLProxy* GetURLProxy() const
   {
-    return mURL;
+    return mURLProxy;
   }
 
-  nsRefPtr<mozilla::dom::URL> mURL;
+  WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<URLProxy> mURLProxy;
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_url_h__ */
--- a/dom/workers/test/Makefile.in
+++ b/dom/workers/test/Makefile.in
@@ -103,16 +103,18 @@ MOCHITEST_FILES = \
   test_errorwarning.html \
   errorwarning_worker.js \
   test_contentWorker.html \
   content_worker.js \
   test_url.html \
   url_worker.js \
   test_bug911085.html \
   bug911085_worker.js \
+  test_urlApi.html \
+  urlApi_worker.js \
   $(NULL)
 
 # Bug 842386 - Disabled on OSX due to intermittent failures.
 ifneq ($(OS_ARCH), Darwin)
 MOCHITEST_FILES += \
   test_xhr_timeout.html \
   $(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_urlApi.html
@@ -0,0 +1,45 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for URL API 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("urlApi_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);
+    }
+  };
+
+  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/urlApi_worker.js
@@ -0,0 +1,270 @@
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
+  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+onmessage = function() {
+  status = false;
+  try {
+    if ((URL instanceof Object)) {
+      status = true;
+    }
+  } catch(e) {
+  }
+
+  var tests = [
+    { url: 'http://www.abc.com',
+      base: undefined,
+      error: false,
+      href: 'http://www.abc.com/',
+      origin: 'http://www.abc.com',
+      protocol: 'http:',
+      username: '',
+      password: '',
+      host: 'www.abc.com',
+      hostname: 'www.abc.com',
+      port: '',
+      pathname: '/',
+      search: '',
+      hash: ''
+    },
+    { url: 'ftp://auser:apw@www.abc.com',
+      base: undefined,
+      error: false,
+      href: 'ftp://auser:apw@www.abc.com/',
+      origin: 'ftp://www.abc.com',
+      protocol: 'ftp:',
+      username: 'auser',
+      password: 'apw',
+      host: 'www.abc.com',
+      hostname: 'www.abc.com',
+      port: '',
+      pathname: '/',
+      search: '',
+      hash: ''
+    },
+    { url: 'http://www.abc.com:90/apath/',
+      base: undefined,
+      error: false,
+      href: 'http://www.abc.com:90/apath/',
+      origin: 'http://www.abc.com:90',
+      protocol: 'http:',
+      username: '',
+      password: '',
+      host: 'www.abc.com:90',
+      hostname: 'www.abc.com',
+      port: '90',
+      pathname: '/apath/',
+      search: '',
+      hash: ''
+    },
+    { url: 'http://www.abc.com/apath/afile.txt#ahash',
+      base: undefined,
+      error: false,
+      href: 'http://www.abc.com/apath/afile.txt#ahash',
+      origin: 'http://www.abc.com',
+      protocol: 'http:',
+      username: '',
+      password: '',
+      host: 'www.abc.com',
+      hostname: 'www.abc.com',
+      port: '',
+      pathname: '/apath/afile.txt',
+      search: '',
+      hash: '#ahash'
+    },
+    { url: 'http://example.com/?test#hash',
+      base: undefined,
+      error: false,
+      href: 'http://example.com/?test#hash',
+      origin: 'http://example.com',
+      protocol: 'http:',
+      username: '',
+      password: '',
+      host: 'example.com',
+      hostname: 'example.com',
+      port: '',
+      pathname: '/',
+      search: '?test',
+      hash: '#hash'
+    },
+    { url: 'http://example.com/?test',
+      base: undefined,
+      error: false,
+      href: 'http://example.com/?test',
+      origin: 'http://example.com',
+      protocol: 'http:',
+      username: '',
+      password: '',
+      host: 'example.com',
+      hostname: 'example.com',
+      port: '',
+      pathname: '/',
+      search: '?test',
+      hash: ''
+    },
+    { url: 'http://example.com/carrot#question%3f',
+      base: undefined,
+      error: false,
+      hash: '#question?'
+    },
+    { url: 'https://example.com:4443?',
+      base: undefined,
+      error: false,
+      protocol: 'https:',
+      port: '4443',
+      pathname: '/',
+      hash: '',
+      search: ''
+    },
+    { url: 'http://www.abc.com/apath/afile.txt#ahash?asearch',
+      base: undefined,
+      error: false,
+      href: 'http://www.abc.com/apath/afile.txt#ahash?asearch',
+      protocol: 'http:',
+      pathname: '/apath/afile.txt',
+      hash: '#ahash?asearch',
+      search: ''
+    },
+    { url: 'http://www.abc.com/apath/afile.txt?asearch#ahash',
+      base: undefined,
+      error: false,
+      href: 'http://www.abc.com/apath/afile.txt?asearch#ahash',
+      protocol: 'http:',
+      pathname: '/apath/afile.txt',
+      hash: '#ahash',
+      search: '?asearch'
+    },
+    { url: 'http://abc.com/apath/afile.txt?#ahash',
+      base: undefined,
+      error: false,
+      pathname: '/apath/afile.txt',
+      hash: '#ahash',
+      search: ''
+    },
+    { url: 'http://auser:apassword@www.abc.com:90/apath/afile.txt?asearch#ahash',
+      base: undefined,
+      error: false,
+      protocol: 'http:',
+      username: 'auser',
+      password: 'apassword',
+      host: 'www.abc.com:90',
+      hostname: 'www.abc.com',
+      port: '90',
+      pathname: '/apath/afile.txt',
+      hash: '#ahash',
+      search: '?asearch',
+      origin: 'http://www.abc.com:90'
+    },
+
+    { url: '/foo#bar',
+      base: 'www.test.org',
+      error: true,
+    },
+    { url: '/foo#bar',
+      base: null,
+      error: true,
+    },
+    { url: '/foo#bar',
+      base: 42,
+      error: true,
+    },
+    { url: 'ftp://ftp.something.net',
+      base: undefined,
+      error: false,
+      protocol: 'ftp:',
+    },
+    { url: 'file:///tmp/file',
+      base: undefined,
+      error: false,
+      protocol: 'file:',
+    },
+    { url: 'gopher://gopher.something.net',
+      base: undefined,
+      error: false,
+      protocol: 'gopher:',
+    },
+    { url: 'ws://ws.something.net',
+      base: undefined,
+      error: false,
+      protocol: 'ws:',
+    },
+    { url: 'wss://ws.something.net',
+      base: undefined,
+      error: false,
+      protocol: 'wss:',
+    },
+    { url: 'foo://foo.something.net',
+      base: undefined,
+      error: false,
+      protocol: 'foo:',
+    },
+  ];
+
+  while(tests.length) {
+    var test = tests.shift();
+
+    var error = false;
+    var url;
+    try {
+      if (test.base) {
+        url = new URL(test.url, test.base);
+      } else {
+        url = new URL(test.url);
+      }
+    } catch(e) {
+      error = true;
+    }
+
+    is(test.error, error, "Error creating URL");
+    if (test.error) {
+      continue;
+    }
+
+    if ('href' in test) is(url.href, test.href, "href");
+    if ('origin' in test) is(url.origin, test.origin, "origin");
+    if ('protocol' in test) is(url.protocol, test.protocol, "protocol");
+    if ('username' in test) is(url.username, test.username, "username");
+    if ('password' in test) is(url.password, test.password, "password");
+    if ('host' in test) is(url.host, test.host, "host");
+    if ('hostname' in test) is(url.hostname, test.hostname, "hostname");
+    if ('port' in test) is(url.port, test.port, "port");
+    if ('pathname' in test) is(url.pathname, test.pathname, "pathname");
+    if ('search' in test) is(url.search, test.search, "search");
+    if ('hash' in test) is(url.hash, test.hash, "hash");
+
+    url = new URL('https://www.example.net/what#foo?bar');
+    ok(url, "Url exists!");
+
+    if ('href' in test) url.href = test.href;
+    if ('protocol' in test) url.protocol = test.protocol;
+    if ('username' in test && test.username) url.username = test.username;
+    if ('password' in test && test.password) url.password = test.password;
+    if ('host' in test) url.host = test.host;
+    if ('hostname' in test) url.hostname = test.hostname;
+    if ('port' in test) url.port = test.port;
+    if ('pathname' in test) url.pathname = test.pathname;
+    if ('search' in test) url.search = test.search;
+    if ('hash' in test) url.hash = test.hash;
+
+    if ('href' in test) is(url.href, test.href, "href");
+    if ('origin' in test) is(url.origin, test.origin, "origin");
+    if ('protocol' in test) is(url.protocol, test.protocol, "protocol");
+    if ('username' in test) is(url.username, test.username, "username");
+    if ('password' in test) is(url.password, test.password, "password");
+    if ('host' in test) is(url.host, test.host, "host");
+    if ('hostname' in test) is(test.hostname, url.hostname, "hostname");
+    if ('port' in test) is(test.port, url.port, "port");
+    if ('pathname' in test) is(test.pathname, url.pathname, "pathname");
+    if ('search' in test) is(test.search, url.search, "search");
+    if ('hash' in test) is(test.hash, url.hash, "hash");
+  }
+
+  postMessage({type: 'finish' });
+}
+