From 5300eb05e8e76b2c9cc80b60f163c59f7872fe98 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Wed, 4 Sep 2013 13:07:34 -0400 Subject: [PATCH] Bug 887364 - URL API for Workers. r=khuey --- dom/base/URL.h | 6 + dom/workers/DOMBindingInlines.h | 3 + dom/workers/URL.cpp | 557 +++++++++++++++++++++++++++++- dom/workers/URL.h | 26 +- dom/workers/test/Makefile.in | 2 + dom/workers/test/test_urlApi.html | 45 +++ dom/workers/test/urlApi_worker.js | 270 +++++++++++++++ 7 files changed, 898 insertions(+), 11 deletions(-) create mode 100644 dom/workers/test/test_urlApi.html create mode 100644 dom/workers/test/urlApi_worker.js diff --git a/dom/base/URL.h b/dom/base/URL.h index 814d2330781..461ab0c19b9 100644 --- a/dom/base/URL.h +++ b/dom/base/URL.h @@ -26,6 +26,10 @@ class MediaSource; class GlobalObject; struct objectURLOptions; +namespace workers { +class URLProxy; +} + class URL MOZ_FINAL { public: @@ -124,6 +128,8 @@ private: nsRefPtr mWindow; nsCOMPtr mURI; + + friend class mozilla::dom::workers::URLProxy; }; } diff --git a/dom/workers/DOMBindingInlines.h b/dom/workers/DOMBindingInlines.h index e242086c347..493ea38a1f8 100644 --- a/dom/workers/DOMBindingInlines.h +++ b/dom/workers/DOMBindingInlines.h @@ -12,6 +12,7 @@ #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 @@ -21,6 +22,7 @@ class XMLHttpRequest; class XMLHttpRequestUpload; class WorkerLocation; class WorkerNavigator; +class URL; namespace { @@ -54,6 +56,7 @@ SPECIALIZE_PROTO_TRAITS(XMLHttpRequest) SPECIALIZE_PROTO_TRAITS(XMLHttpRequestUpload) SPECIALIZE_PROTO_TRAITS(WorkerLocation) SPECIALIZE_PROTO_TRAITS(WorkerNavigator) +SPECIALIZE_PROTO_TRAITS(URL) #undef SPECIALIZE_PROTO_TRAITS diff --git a/dom/workers/URL.cpp b/dom/workers/URL.cpp index abf89803fb1..25527e560db 100644 --- a/dom/workers/URL.cpp +++ b/dom/workers/URL.cpp @@ -14,14 +14,56 @@ #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 mURL; +}; + +// Base class for the URL runnable objects. class URLRunnable : public nsRunnable { protected: @@ -225,13 +267,306 @@ public: } }; +// This class creates a URL object on the main thread. +class ConstructorRunnable : public URLRunnable +{ +private: + const nsString mURL; + + const nsString mBase; + nsRefPtr mBaseProxy; + mozilla::ErrorResult& mRv; + + nsRefPtr 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 ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + mRv.Throw(rv); + return; + } + + nsCOMPtr 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 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 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 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 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 runnable = + new ConstructorRunnable(workerPrivate, aUrl, aBase.GetURLProxy(), aRv); + + if (!runnable->Dispatch(cx)) { + JS_ReportPendingException(cx); + } + + nsRefPtr proxy = runnable->GetURLProxy(); + if (!proxy) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return nullptr; + } + + nsRefPtr url = new URL(workerPrivate, proxy); + + if (!Wrap(aGlobal.GetContext(), aGlobal.Get(), url)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + return url; } // static @@ -239,113 +574,322 @@ 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 runnable = + new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv); + + if (!runnable->Dispatch(cx)) { + JS_ReportPendingException(cx); + } + + nsRefPtr proxy = runnable->GetURLProxy(); + if (!proxy) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return nullptr; + } + + nsRefPtr 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 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 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 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 runnable = + new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterOrigin, aOrigin, + mURLProxy); + + if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { + JS_ReportPendingException(mWorkerPrivate->GetJSContext()); + } } void URL::GetProtocol(nsString& aProtocol) const { + nsRefPtr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 runnable = + new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHash, + aHash, mURLProxy, rv); + + if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { + JS_ReportPendingException(mWorkerPrivate->GetJSContext()); + } } // static @@ -399,3 +943,4 @@ URL::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aUrl) } } +END_WORKERS_NAMESPACE diff --git a/dom/workers/URL.h b/dom/workers/URL.h index e40905bd883..0ea62e7a324 100644 --- a/dom/workers/URL.h +++ b/dom/workers/URL.h @@ -7,15 +7,30 @@ #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); @@ -79,12 +94,13 @@ public: // Methods for WebIDL void SetHash(const nsAString& aHash); private: - mozilla::dom::URL* GetURL() const + URLProxy* GetURLProxy() const { - return mURL; + return mURLProxy; } - nsRefPtr mURL; + WorkerPrivate* mWorkerPrivate; + nsRefPtr mURLProxy; }; END_WORKERS_NAMESPACE diff --git a/dom/workers/test/Makefile.in b/dom/workers/test/Makefile.in index 932633f4c8f..a9a4d73b6c1 100644 --- a/dom/workers/test/Makefile.in +++ b/dom/workers/test/Makefile.in @@ -108,6 +108,8 @@ MOCHITEST_FILES = \ 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. diff --git a/dom/workers/test/test_urlApi.html b/dom/workers/test/test_urlApi.html new file mode 100644 index 00000000000..654d2a19745 --- /dev/null +++ b/dom/workers/test/test_urlApi.html @@ -0,0 +1,45 @@ + + + + + Test for URL API object in workers + + + + +

+ +

+
+
+
+
+
+
diff --git a/dom/workers/test/urlApi_worker.js b/dom/workers/test/urlApi_worker.js
new file mode 100644
index 00000000000..5a4633b6ba3
--- /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' });
+}
+