summaryrefslogtreecommitdiffstats
path: root/dom/url
diff options
context:
space:
mode:
Diffstat (limited to 'dom/url')
-rw-r--r--dom/url/URL.cpp442
-rw-r--r--dom/url/URL.h147
-rw-r--r--dom/url/URLMainThread.cpp102
-rw-r--r--dom/url/URLMainThread.h31
-rw-r--r--dom/url/URLSearchParams.cpp246
-rw-r--r--dom/url/URLSearchParams.h129
-rw-r--r--dom/url/URLWorker.cpp160
-rw-r--r--dom/url/URLWorker.h45
-rw-r--r--dom/url/moz.build35
-rw-r--r--dom/url/tests/browser.ini5
-rw-r--r--dom/url/tests/browser_download_after_revoke.js94
-rw-r--r--dom/url/tests/chrome.ini12
-rw-r--r--dom/url/tests/empty.html2
-rw-r--r--dom/url/tests/file_url.jsm24
-rw-r--r--dom/url/tests/file_worker_url.jsm23
-rw-r--r--dom/url/tests/jsm_url_worker.js84
-rw-r--r--dom/url/tests/mochitest.ini25
-rw-r--r--dom/url/tests/protocol_worker.js25
-rw-r--r--dom/url/tests/test_bloburl_location.html35
-rw-r--r--dom/url/tests/test_bug883784.jsm38
-rw-r--r--dom/url/tests/test_bug883784.xhtml34
-rw-r--r--dom/url/tests/test_unknown_url_origin.html17
-rw-r--r--dom/url/tests/test_url.html507
-rw-r--r--dom/url/tests/test_url.xhtml24
-rw-r--r--dom/url/tests/test_urlExceptions.html56
-rw-r--r--dom/url/tests/test_urlSearchParams.html59
-rw-r--r--dom/url/tests/test_urlSearchParams_sorting.html63
-rw-r--r--dom/url/tests/test_urlSearchParams_utf8.html39
-rw-r--r--dom/url/tests/test_url_data.html45
-rw-r--r--dom/url/tests/test_url_empty_port.html53
-rw-r--r--dom/url/tests/test_url_malformedHost.html48
-rw-r--r--dom/url/tests/test_urlutils_stringify.html38
-rw-r--r--dom/url/tests/test_worker_protocol.html41
-rw-r--r--dom/url/tests/test_worker_url.html66
-rw-r--r--dom/url/tests/test_worker_url.xhtml33
-rw-r--r--dom/url/tests/test_worker_urlApi.html43
-rw-r--r--dom/url/tests/test_worker_urlSearchParams.html43
-rw-r--r--dom/url/tests/test_worker_url_exceptions.html42
-rw-r--r--dom/url/tests/urlApi_worker.js354
-rw-r--r--dom/url/tests/urlSearchParams_commons.js376
-rw-r--r--dom/url/tests/urlSearchParams_worker.js48
-rw-r--r--dom/url/tests/url_exceptions_worker.js38
-rw-r--r--dom/url/tests/url_worker.js98
43 files changed, 3869 insertions, 0 deletions
diff --git a/dom/url/URL.cpp b/dom/url/URL.cpp
new file mode 100644
index 0000000000..dc548e7c1c
--- /dev/null
+++ b/dom/url/URL.cpp
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "URL.h"
+#include "URLMainThread.h"
+#include "URLWorker.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/URLBinding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URL, mParent, mSearchParams)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(URL)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(URL)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* URL::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return URL_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<URL> URL::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aURL,
+ const Optional<nsAString>& aBase,
+ ErrorResult& aRv) {
+ if (aBase.WasPassed()) {
+ return Constructor(aGlobal.GetAsSupports(), aURL, aBase.Value(), aRv);
+ }
+
+ return Constructor(aGlobal.GetAsSupports(), aURL, nullptr, aRv);
+}
+
+/* static */
+already_AddRefed<URL> URL::Constructor(nsISupports* aParent,
+ const nsAString& aURL,
+ const nsAString& aBase,
+ ErrorResult& aRv) {
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString base;
+ if (!AppendUTF16toUTF8(aBase, base, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> baseUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseUri), base);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(base);
+ return nullptr;
+ }
+
+ return Constructor(aParent, aURL, baseUri, aRv);
+}
+
+/* static */
+already_AddRefed<URL> URL::Constructor(nsISupports* aParent,
+ const nsAString& aURL, nsIURI* aBase,
+ ErrorResult& aRv) {
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString urlStr;
+ if (!AppendUTF16toUTF8(aURL, urlStr, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), urlStr, nullptr, aBase);
+ if (NS_FAILED(rv)) {
+ // No need to warn in this case. It's common to use the URL constructor
+ // to determine if a URL is valid and an exception will be propagated.
+ aRv.ThrowTypeError<MSG_INVALID_URL>(urlStr);
+ return nullptr;
+ }
+
+ return MakeAndAddRef<URL>(aParent, std::move(uri));
+}
+
+already_AddRefed<URL> URL::FromURI(GlobalObject& aGlobal, nsIURI* aURI) {
+ return MakeAndAddRef<URL>(aGlobal.GetAsSupports(), aURI);
+}
+
+void URL::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, ErrorResult& aRv) {
+ if (NS_IsMainThread()) {
+ URLMainThread::CreateObjectURL(aGlobal, aBlob, aResult, aRv);
+ } else {
+ URLWorker::CreateObjectURL(aGlobal, aBlob, aResult, aRv);
+ }
+}
+
+void URL::CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ nsAString& aResult, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ URLMainThread::CreateObjectURL(aGlobal, aSource, aResult, aRv);
+}
+
+void URL::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+ ErrorResult& aRv) {
+ if (aURL.Contains('#')) {
+ // Don't revoke URLs that contain fragments.
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ URLMainThread::RevokeObjectURL(aGlobal, aURL, aRv);
+ } else {
+ URLWorker::RevokeObjectURL(aGlobal, aURL, aRv);
+ }
+}
+
+bool URL::IsValidObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+ ErrorResult& aRv) {
+ if (NS_IsMainThread()) {
+ return URLMainThread::IsValidObjectURL(aGlobal, aURL, aRv);
+ }
+ return URLWorker::IsValidObjectURL(aGlobal, aURL, aRv);
+}
+
+bool URL::CanParse(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional<nsAString>& aBase) {
+ nsCOMPtr<nsIURI> baseUri;
+ if (aBase.WasPassed()) {
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString base;
+ if (!AppendUTF16toUTF8(aBase.Value(), base, fallible)) {
+ // Just return false with OOM errors as no ErrorResult.
+ return false;
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(baseUri), base);
+ if (NS_FAILED(rv)) {
+ // Invalid base URL, return false.
+ return false;
+ }
+ }
+
+ nsAutoCString urlStr;
+ if (!AppendUTF16toUTF8(aURL, urlStr, fallible)) {
+ // Just return false with OOM errors as no ErrorResult.
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ return NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), urlStr, nullptr, baseUri));
+}
+
+URLSearchParams* URL::SearchParams() {
+ CreateSearchParamsIfNeeded();
+ return mSearchParams;
+}
+
+bool IsChromeURI(nsIURI* aURI) { return aURI->SchemeIs("chrome"); }
+
+void URL::CreateSearchParamsIfNeeded() {
+ if (!mSearchParams) {
+ mSearchParams = new URLSearchParams(mParent, this);
+ UpdateURLSearchParams();
+ }
+}
+
+void URL::SetSearch(const nsAString& aSearch) {
+ SetSearchInternal(aSearch);
+ UpdateURLSearchParams();
+}
+
+void URL::URLSearchParamsUpdated(URLSearchParams* aSearchParams) {
+ MOZ_ASSERT(mSearchParams);
+ MOZ_ASSERT(mSearchParams == aSearchParams);
+
+ nsAutoString search;
+ mSearchParams->Serialize(search);
+
+ SetSearchInternal(search);
+}
+
+#define URL_GETTER(value, func) \
+ MOZ_ASSERT(mURI); \
+ value.Truncate(); \
+ nsAutoCString tmp; \
+ nsresult rv = mURI->func(tmp); \
+ if (NS_SUCCEEDED(rv)) { \
+ CopyUTF8toUTF16(tmp, value); \
+ }
+
+void URL::GetHref(nsAString& aHref) const { URL_GETTER(aHref, GetSpec); }
+
+void URL::SetHref(const nsAString& aHref, ErrorResult& aRv) {
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString href;
+ if (!AppendUTF16toUTF8(aHref, href, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), href);
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(href);
+ return;
+ }
+
+ mURI = std::move(uri);
+ UpdateURLSearchParams();
+}
+
+void URL::GetOrigin(nsAString& aOrigin) const {
+ nsresult rv = nsContentUtils::GetUTFOrigin(URI(), aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aOrigin.Truncate();
+ }
+}
+
+void URL::GetProtocol(nsAString& aProtocol) const {
+ URL_GETTER(aProtocol, GetScheme);
+ aProtocol.Append(char16_t(':'));
+}
+
+void URL::SetProtocol(const nsAString& aProtocol) {
+ nsAString::const_iterator start;
+ aProtocol.BeginReading(start);
+
+ nsAString::const_iterator end;
+ aProtocol.EndReading(end);
+
+ nsAString::const_iterator iter(start);
+ FindCharInReadable(':', iter, end);
+
+ // Changing the protocol of a URL, changes the "nature" of the URI
+ // implementation. In order to do this properly, we have to serialize the
+ // existing URL and reparse it in a new object.
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = NS_MutateURI(URI())
+ .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
+ .Finalize(clone);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsAutoCString href;
+ rv = clone->GetSpec(href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ mURI = std::move(uri);
+}
+
+void URL::GetUsername(nsAString& aUsername) const {
+ URL_GETTER(aUsername, GetUsername);
+}
+
+void URL::SetUsername(const nsAString& aUsername) {
+ MOZ_ASSERT(mURI);
+
+ Unused << NS_MutateURI(mURI)
+ .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
+ .Finalize(mURI);
+}
+
+void URL::GetPassword(nsAString& aPassword) const {
+ URL_GETTER(aPassword, GetPassword);
+}
+
+void URL::SetPassword(const nsAString& aPassword) {
+ MOZ_ASSERT(mURI);
+
+ Unused << NS_MutateURI(mURI)
+ .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
+ .Finalize(mURI);
+}
+
+void URL::GetHost(nsAString& aHost) const { URL_GETTER(aHost, GetHostPort); }
+
+void URL::SetHost(const nsAString& aHost) {
+ MOZ_ASSERT(mURI);
+
+ Unused << NS_MutateURI(mURI)
+ .SetHostPort(NS_ConvertUTF16toUTF8(aHost))
+ .Finalize(mURI);
+}
+
+void URL::GetHostname(nsAString& aHostname) const {
+ MOZ_ASSERT(mURI);
+
+ aHostname.Truncate();
+ nsContentUtils::GetHostOrIPv6WithBrackets(mURI, aHostname);
+}
+
+void URL::SetHostname(const nsAString& aHostname) {
+ MOZ_ASSERT(mURI);
+
+ // nsStandardURL returns NS_ERROR_UNEXPECTED for an empty hostname
+ // The return code is silently ignored
+ mozilla::Unused << NS_MutateURI(mURI)
+ .SetHost(NS_ConvertUTF16toUTF8(aHostname))
+ .Finalize(mURI);
+}
+
+void URL::GetPort(nsAString& aPort) const {
+ MOZ_ASSERT(mURI);
+
+ aPort.Truncate();
+
+ int32_t port;
+ nsresult rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv) && port != -1) {
+ nsAutoString portStr;
+ portStr.AppendInt(port, 10);
+ aPort.Assign(portStr);
+ }
+}
+
+void URL::SetPort(const nsAString& aPort) {
+ nsresult rv;
+ nsAutoString portStr(aPort);
+ int32_t port = -1;
+
+ // nsIURI uses -1 as default value.
+ if (!portStr.IsEmpty()) {
+ port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ Unused << NS_MutateURI(mURI).SetPort(port).Finalize(mURI);
+}
+
+void URL::GetPathname(nsAString& aPathname) const {
+ MOZ_ASSERT(mURI);
+
+ aPathname.Truncate();
+
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+
+ nsAutoCString file;
+ nsresult rv = mURI->GetFilePath(file);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(file, aPathname);
+ }
+}
+
+void URL::SetPathname(const nsAString& aPathname) {
+ MOZ_ASSERT(mURI);
+
+ // Do not throw!
+
+ Unused << NS_MutateURI(mURI)
+ .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
+ .Finalize(mURI);
+}
+
+void URL::GetSearch(nsAString& aSearch) const {
+ MOZ_ASSERT(mURI);
+
+ aSearch.Truncate();
+
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+
+ nsAutoCString search;
+ nsresult rv;
+
+ rv = mURI->GetQuery(search);
+ if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
+ aSearch.Assign(u'?');
+ AppendUTF8toUTF16(search, aSearch);
+ }
+}
+
+void URL::GetHash(nsAString& aHash) const {
+ MOZ_ASSERT(mURI);
+
+ aHash.Truncate();
+
+ nsAutoCString ref;
+ nsresult rv = mURI->GetRef(ref);
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ aHash.Assign(char16_t('#'));
+ AppendUTF8toUTF16(ref, aHash);
+ }
+}
+
+void URL::SetHash(const nsAString& aHash) {
+ MOZ_ASSERT(mURI);
+
+ Unused
+ << NS_MutateURI(mURI).SetRef(NS_ConvertUTF16toUTF8(aHash)).Finalize(mURI);
+}
+
+void URL::SetSearchInternal(const nsAString& aSearch) {
+ MOZ_ASSERT(mURI);
+
+ // Ignore failures to be compatible with NS4.
+
+ Unused << NS_MutateURI(mURI)
+ .SetQuery(NS_ConvertUTF16toUTF8(aSearch))
+ .Finalize(mURI);
+}
+
+void URL::UpdateURLSearchParams() {
+ if (!mSearchParams) {
+ return;
+ }
+
+ nsAutoCString search;
+ nsresult rv = URI()->GetQuery(search);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ search.Truncate();
+ }
+
+ mSearchParams->ParseInput(search);
+}
+
+nsIURI* URL::URI() const {
+ MOZ_ASSERT(mURI);
+ return mURI;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/url/URL.h b/dom/url/URL.h
new file mode 100644
index 0000000000..7c2563d095
--- /dev/null
+++ b/dom/url/URL.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_URL_h
+#define mozilla_dom_URL_h
+
+#include "mozilla/dom/URLSearchParams.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsISupports;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Blob;
+class MediaSource;
+class GlobalObject;
+template <typename T>
+class Optional;
+
+class URL final : public URLSearchParamsObserver, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(URL)
+
+ explicit URL(nsISupports* aParent, nsCOMPtr<nsIURI> aURI)
+ : mParent(aParent), mURI(std::move(aURI)) {
+ MOZ_ASSERT(mURI);
+ }
+
+ // WebIDL methods
+ nsISupports* GetParentObject() const { return mParent; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<URL> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aURL,
+ const Optional<nsAString>& aBase,
+ ErrorResult& aRv);
+
+ static already_AddRefed<URL> Constructor(nsISupports* aParent,
+ const nsAString& aURL,
+ const nsAString& aBase,
+ ErrorResult& aRv);
+
+ static already_AddRefed<URL> Constructor(nsISupports* aParent,
+ const nsAString& aURL, nsIURI* aBase,
+ ErrorResult& aRv);
+
+ static void CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv);
+
+ static bool IsValidObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv);
+
+ static bool CanParse(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional<nsAString>& aBase);
+
+ void GetHref(nsAString& aHref) const;
+
+ void SetHref(const nsAString& aHref, ErrorResult& aRv);
+
+ void GetOrigin(nsAString& aOrigin) const;
+
+ void GetProtocol(nsAString& aProtocol) const;
+
+ void SetProtocol(const nsAString& aProtocol);
+
+ void GetUsername(nsAString& aUsername) const;
+
+ void SetUsername(const nsAString& aUsername);
+
+ void GetPassword(nsAString& aPassword) const;
+
+ void SetPassword(const nsAString& aPassword);
+
+ void GetHost(nsAString& aHost) const;
+
+ void SetHost(const nsAString& aHost);
+
+ void GetHostname(nsAString& aHostname) const;
+
+ void SetHostname(const nsAString& aHostname);
+
+ void GetPort(nsAString& aPort) const;
+
+ void SetPort(const nsAString& aPort);
+
+ void GetPathname(nsAString& aPathname) const;
+
+ void SetPathname(const nsAString& aPathname);
+
+ void GetSearch(nsAString& aSearch) const;
+
+ void SetSearch(const nsAString& aSearch);
+
+ URLSearchParams* SearchParams();
+
+ void GetHash(nsAString& aHost) const;
+
+ void SetHash(const nsAString& aHash);
+
+ void ToJSON(nsAString& aResult) const { GetHref(aResult); }
+
+ // URLSearchParamsObserver
+ void URLSearchParamsUpdated(URLSearchParams* aSearchParams) override;
+
+ nsIURI* URI() const;
+ static already_AddRefed<URL> FromURI(GlobalObject&, nsIURI*);
+
+ private:
+ ~URL() = default;
+
+ void UpdateURLSearchParams();
+
+ private:
+ void SetSearchInternal(const nsAString& aSearch);
+
+ void CreateSearchParamsIfNeeded();
+
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<URLSearchParams> mSearchParams;
+ nsCOMPtr<nsIURI> mURI;
+};
+
+bool IsChromeURI(nsIURI* aURI);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_URL_h */
diff --git a/dom/url/URLMainThread.cpp b/dom/url/URLMainThread.cpp
new file mode 100644
index 0000000000..641e6d2a69
--- /dev/null
+++ b/dom/url/URLMainThread.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "URLMainThread.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Blob.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+/* static */
+void URLMainThread::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+ nsAutoCString url;
+ aRv = BlobURLProtocolHandler::AddDataEntry(aBlob.Impl(), principal,
+ global->GetAgentClusterId(), url);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ global->RegisterHostObjectURI(url);
+ CopyASCIItoUTF16(url, aResult);
+}
+
+/* static */
+void URLMainThread::CreateObjectURL(const GlobalObject& aGlobal,
+ MediaSource& aSource, nsAString& aResult,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+ nsAutoCString url;
+ aRv = BlobURLProtocolHandler::AddDataEntry(&aSource, principal,
+ global->GetAgentClusterId(), url);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> revocation = NS_NewRunnableFunction(
+ "dom::URLMainThread::CreateObjectURL",
+ [url] { BlobURLProtocolHandler::RemoveDataEntry(url); });
+
+ nsContentUtils::RunInStableState(revocation.forget());
+
+ CopyASCIItoUTF16(url, aResult);
+}
+
+/* static */
+void URLMainThread::RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ NS_LossyConvertUTF16toASCII asciiurl(aURL);
+
+ if (BlobURLProtocolHandler::RemoveDataEntry(
+ asciiurl, nsContentUtils::ObjectPrincipal(aGlobal.Get()),
+ global->GetAgentClusterId())) {
+ global->UnregisterHostObjectURI(asciiurl);
+ }
+}
+
+/* static */
+bool URLMainThread::IsValidObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_LossyConvertUTF16toASCII asciiurl(aURL);
+ return BlobURLProtocolHandler::HasDataEntry(asciiurl);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/url/URLMainThread.h b/dom/url/URLMainThread.h
new file mode 100644
index 0000000000..5314f2b3e0
--- /dev/null
+++ b/dom/url/URLMainThread.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_URLMainThread_h
+#define mozilla_dom_URLMainThread_h
+
+#include "URL.h"
+
+namespace mozilla::dom {
+
+class URLMainThread final {
+ public:
+ static void CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv);
+
+ static bool IsValidObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_URLMainThread_h
diff --git a/dom/url/URLSearchParams.cpp b/dom/url/URLSearchParams.cpp
new file mode 100644
index 0000000000..3bd08a9020
--- /dev/null
+++ b/dom/url/URLSearchParams.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/dom/URLSearchParams.h"
+
+// XXX encoding_rs.h is not self-contained, this order is required
+#include "mozilla/Encoding.h"
+#include "encoding_rs.h"
+
+#include <new>
+#include <type_traits>
+#include <utility>
+#include "js/StructuredClone.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Record.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/URLSearchParamsBinding.h"
+#include "mozilla/fallible.h"
+#include "nsDOMString.h"
+#include "nsError.h"
+#include "nsIGlobalObject.h"
+#include "nsLiteralString.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsStringIterator.h"
+#include "nsStringStream.h"
+#include "nsURLHelper.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mParent, mObserver)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+URLSearchParams::URLSearchParams(nsISupports* aParent,
+ URLSearchParamsObserver* aObserver)
+ : mParams(new URLParams()), mParent(aParent), mObserver(aObserver) {}
+
+URLSearchParams::~URLSearchParams() { DeleteAll(); }
+
+JSObject* URLSearchParams::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return URLSearchParams_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<URLSearchParams> URLSearchParams::Constructor(
+ const GlobalObject& aGlobal,
+ const USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString& aInit,
+ ErrorResult& aRv) {
+ RefPtr<URLSearchParams> sp =
+ new URLSearchParams(aGlobal.GetAsSupports(), nullptr);
+
+ if (aInit.IsUSVString()) {
+ NS_ConvertUTF16toUTF8 input(aInit.GetAsUSVString());
+ if (StringBeginsWith(input, "?"_ns)) {
+ sp->ParseInput(Substring(input, 1, input.Length() - 1));
+ } else {
+ sp->ParseInput(input);
+ }
+ } else if (aInit.IsUSVStringSequenceSequence()) {
+ const Sequence<Sequence<nsString>>& list =
+ aInit.GetAsUSVStringSequenceSequence();
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ const Sequence<nsString>& item = list[i];
+ if (item.Length() != 2) {
+ nsPrintfCString err("Expected 2 items in pair but got %zu",
+ item.Length());
+ aRv.ThrowTypeError(err);
+ return nullptr;
+ }
+ sp->Append(item[0], item[1]);
+ }
+ } else if (aInit.IsUSVStringUSVStringRecord()) {
+ const Record<nsString, nsString>& record =
+ aInit.GetAsUSVStringUSVStringRecord();
+ for (auto& entry : record.Entries()) {
+ sp->Append(entry.mKey, entry.mValue);
+ }
+ } else {
+ MOZ_CRASH("This should not happen.");
+ }
+
+ return sp.forget();
+}
+
+void URLSearchParams::ParseInput(const nsACString& aInput) {
+ mParams->ParseInput(aInput);
+}
+
+uint32_t URLSearchParams::Size() const { return mParams->Length(); }
+
+void URLSearchParams::Get(const nsAString& aName, nsString& aRetval) {
+ return mParams->Get(aName, aRetval);
+}
+
+void URLSearchParams::GetAll(const nsAString& aName,
+ nsTArray<nsString>& aRetval) {
+ return mParams->GetAll(aName, aRetval);
+}
+
+void URLSearchParams::Set(const nsAString& aName, const nsAString& aValue) {
+ mParams->Set(aName, aValue);
+ NotifyObserver();
+}
+
+void URLSearchParams::Append(const nsAString& aName, const nsAString& aValue) {
+ mParams->Append(aName, aValue);
+ NotifyObserver();
+}
+
+bool URLSearchParams::Has(const nsAString& aName,
+ const Optional<nsAString>& aValue) {
+ if (!aValue.WasPassed()) {
+ return mParams->Has(aName);
+ }
+ return mParams->Has(aName, aValue.Value());
+}
+
+void URLSearchParams::Delete(const nsAString& aName,
+ const Optional<nsAString>& aValue) {
+ if (!aValue.WasPassed()) {
+ mParams->Delete(aName);
+ NotifyObserver();
+ return;
+ }
+ mParams->Delete(aName, aValue.Value());
+ NotifyObserver();
+}
+
+void URLSearchParams::DeleteAll() { mParams->DeleteAll(); }
+
+void URLSearchParams::Serialize(nsAString& aValue) const {
+ mParams->Serialize(aValue, true);
+}
+
+void URLSearchParams::NotifyObserver() {
+ if (mObserver) {
+ mObserver->URLSearchParamsUpdated(this);
+ }
+}
+
+uint32_t URLSearchParams::GetIterableLength() const {
+ return mParams->Length();
+}
+
+const nsAString& URLSearchParams::GetKeyAtIndex(uint32_t aIndex) const {
+ return mParams->GetKeyAtIndex(aIndex);
+}
+
+const nsAString& URLSearchParams::GetValueAtIndex(uint32_t aIndex) const {
+ return mParams->GetValueAtIndex(aIndex);
+}
+
+void URLSearchParams::Sort(ErrorResult& aRv) {
+ mParams->Sort();
+ NotifyObserver();
+}
+
+bool URLSearchParams::WriteStructuredClone(
+ JSStructuredCloneWriter* aWriter) const {
+ const uint32_t& nParams = mParams->Length();
+ if (!JS_WriteUint32Pair(aWriter, nParams, 0)) {
+ return false;
+ }
+ for (uint32_t i = 0; i < nParams; ++i) {
+ if (!StructuredCloneHolder::WriteString(aWriter,
+ mParams->GetKeyAtIndex(i)) ||
+ !StructuredCloneHolder::WriteString(aWriter,
+ mParams->GetValueAtIndex(i))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader) {
+ MOZ_ASSERT(aReader);
+
+ DeleteAll();
+
+ uint32_t nParams, zero;
+ nsAutoString key, value;
+ if (!JS_ReadUint32Pair(aReader, &nParams, &zero) || zero != 0) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < nParams; ++i) {
+ if (!StructuredCloneHolder::ReadString(aReader, key) ||
+ !StructuredCloneHolder::ReadString(aReader, value)) {
+ return false;
+ }
+ Append(key, value);
+ }
+ return true;
+}
+
+bool URLSearchParams::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ return WriteStructuredClone(aWriter);
+}
+
+// static
+already_AddRefed<URLSearchParams> URLSearchParams::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ RefPtr<URLSearchParams> params = new URLSearchParams(aGlobal);
+ if (!params->ReadStructuredClone(aReader)) {
+ return nullptr;
+ }
+ return params.forget();
+}
+
+// contentTypeWithCharset can be set to the contentType or
+// contentType+charset based on what the spec says.
+// See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+nsresult URLSearchParams::GetSendInfo(nsIInputStream** aBody,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const {
+ aContentTypeWithCharset.AssignLiteral(
+ "application/x-www-form-urlencoded;charset=UTF-8");
+ aCharset.AssignLiteral("UTF-8");
+
+ nsAutoString serialized;
+ Serialize(serialized);
+ NS_ConvertUTF16toUTF8 converted(serialized);
+ *aContentLength = converted.Length();
+ return NS_NewCStringInputStream(aBody, std::move(converted));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/url/URLSearchParams.h b/dom/url/URLSearchParams.h
new file mode 100644
index 0000000000..4e8b09bc8c
--- /dev/null
+++ b/dom/url/URLSearchParams.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_URLSearchParams_h
+#define mozilla_dom_URLSearchParams_h
+
+#include <cstdint>
+#include "ErrorList.h"
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class JSObject;
+class nsIGlobalObject;
+class nsIInputStream;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+
+class ErrorResult;
+class URLParams;
+
+namespace dom {
+
+class GlobalObject;
+class URLSearchParams;
+class USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString;
+template <typename T>
+class Optional;
+
+class URLSearchParamsObserver : public nsISupports {
+ public:
+ virtual ~URLSearchParamsObserver() = default;
+
+ virtual void URLSearchParamsUpdated(URLSearchParams* aFromThis) = 0;
+};
+
+class URLSearchParams final : public nsISupports, public nsWrapperCache {
+ ~URLSearchParams();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(URLSearchParams)
+
+ explicit URLSearchParams(nsISupports* aParent,
+ URLSearchParamsObserver* aObserver = nullptr);
+
+ // WebIDL methods
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<URLSearchParams> Constructor(
+ const GlobalObject& aGlobal,
+ const USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString&
+ aInit,
+ ErrorResult& aRv);
+
+ void ParseInput(const nsACString& aInput);
+
+ void Serialize(nsAString& aValue) const;
+
+ uint32_t Size() const;
+
+ void Get(const nsAString& aName, nsString& aRetval);
+
+ void GetAll(const nsAString& aName, nsTArray<nsString>& aRetval);
+
+ void Set(const nsAString& aName, const nsAString& aValue);
+
+ void Append(const nsAString& aName, const nsAString& aValue);
+
+ bool Has(const nsAString& aName, const Optional<nsAString>& aValue);
+
+ void Delete(const nsAString& aName, const Optional<nsAString>& aValue);
+
+ uint32_t GetIterableLength() const;
+ const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
+ const nsAString& GetValueAtIndex(uint32_t aIndex) const;
+
+ void Sort(ErrorResult& aRv);
+
+ void Stringify(nsString& aRetval) const { Serialize(aRetval); }
+
+ static already_AddRefed<URLSearchParams> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ nsresult GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const;
+
+ private:
+ bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+ bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
+
+ void AppendInternal(const nsAString& aName, const nsAString& aValue);
+
+ void DeleteAll();
+
+ void NotifyObserver();
+
+ UniquePtr<URLParams> mParams;
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<URLSearchParamsObserver> mObserver;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_URLSearchParams_h */
diff --git a/dom/url/URLWorker.cpp b/dom/url/URLWorker.cpp
new file mode 100644
index 0000000000..2ecddd35a2
--- /dev/null
+++ b/dom/url/URLWorker.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "URLWorker.h"
+
+#include "mozilla/dom/Blob.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla::dom {
+
+// This class creates an URL from a DOM Blob on the main thread.
+class CreateURLRunnable : public WorkerMainThreadRunnable {
+ private:
+ BlobImpl* mBlobImpl;
+ nsAString& mURL;
+
+ public:
+ CreateURLRunnable(WorkerPrivate* aWorkerPrivate, BlobImpl* aBlobImpl,
+ nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "URL :: CreateURL"_ns),
+ mBlobImpl(aBlobImpl),
+ mURL(aURL) {
+ MOZ_ASSERT(aBlobImpl);
+ }
+
+ bool MainThreadRun() override {
+ using namespace mozilla::ipc;
+
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
+
+ nsAutoCString url;
+ nsresult rv = BlobURLProtocolHandler::AddDataEntry(
+ mBlobImpl, principal, Some(mWorkerPrivate->AgentClusterId()), url);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to add data entry for the blob!");
+ SetDOMStringToNull(mURL);
+ return false;
+ }
+
+ CopyUTF8toUTF16(url, mURL);
+ return true;
+ }
+};
+
+// This class revokes an URL on the main thread.
+class RevokeURLRunnable : public WorkerMainThreadRunnable {
+ private:
+ const nsString mURL;
+
+ public:
+ RevokeURLRunnable(WorkerPrivate* aWorkerPrivate, const nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "URL :: RevokeURL"_ns),
+ mURL(aURL) {}
+
+ bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ NS_ConvertUTF16toUTF8 url(mURL);
+
+ BlobURLProtocolHandler::RemoveDataEntry(
+ url, mWorkerPrivate->GetPrincipal(),
+ Some(mWorkerPrivate->AgentClusterId()));
+ return true;
+ }
+};
+
+// This class checks if an URL is valid on the main thread.
+class IsValidURLRunnable : public WorkerMainThreadRunnable {
+ private:
+ const nsString mURL;
+ bool mValid;
+
+ public:
+ IsValidURLRunnable(WorkerPrivate* aWorkerPrivate, const nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "URL :: IsValidURL"_ns),
+ mURL(aURL),
+ mValid(false) {}
+
+ bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ NS_ConvertUTF16toUTF8 url(mURL);
+ mValid = BlobURLProtocolHandler::HasDataEntry(url);
+
+ return true;
+ }
+
+ bool IsValidURL() const { return mValid; }
+};
+
+/* static */
+void URLWorker::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, mozilla::ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr<BlobImpl> blobImpl = aBlob.Impl();
+ MOZ_ASSERT(blobImpl);
+
+ RefPtr<CreateURLRunnable> runnable =
+ new CreateURLRunnable(workerPrivate, blobImpl, aResult);
+
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ WorkerGlobalScope* scope = workerPrivate->GlobalScope();
+ MOZ_ASSERT(scope);
+
+ scope->RegisterHostObjectURI(NS_ConvertUTF16toUTF8(aResult));
+}
+
+/* static */
+void URLWorker::RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aUrl, ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr<RevokeURLRunnable> runnable =
+ new RevokeURLRunnable(workerPrivate, aUrl);
+
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ WorkerGlobalScope* scope = workerPrivate->GlobalScope();
+ MOZ_ASSERT(scope);
+
+ scope->UnregisterHostObjectURI(NS_ConvertUTF16toUTF8(aUrl));
+}
+
+/* static */
+bool URLWorker::IsValidObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aUrl, ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr<IsValidURLRunnable> runnable =
+ new IsValidURLRunnable(workerPrivate, aUrl);
+
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+
+ return runnable->IsValidURL();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/url/URLWorker.h b/dom/url/URLWorker.h
new file mode 100644
index 0000000000..be8ab512a1
--- /dev/null
+++ b/dom/url/URLWorker.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_URLWorker_h
+#define mozilla_dom_URLWorker_h
+
+#include "URL.h"
+#include "URLMainThread.h"
+
+namespace mozilla {
+
+namespace net {
+class nsStandardURL;
+}
+
+namespace dom {
+
+class URLWorker final {
+ public:
+ static already_AddRefed<URLWorker> Constructor(
+ const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional<nsAString>& aBase, ErrorResult& aRv);
+
+ static already_AddRefed<URLWorker> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aURL,
+ const nsAString& aBase,
+ ErrorResult& aRv);
+
+ static void CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ nsAString& aResult, mozilla::ErrorResult& aRv);
+
+ static void RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aUrl, ErrorResult& aRv);
+
+ static bool IsValidObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aUrl, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_URLWorker_h
diff --git a/dom/url/moz.build b/dom/url/moz.build
new file mode 100644
index 0000000000..2f3807e6aa
--- /dev/null
+++ b/dom/url/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Networking")
+
+EXPORTS.mozilla.dom += [
+ "URL.h",
+ "URLSearchParams.h",
+]
+
+UNIFIED_SOURCES += [
+ "URL.cpp",
+ "URLMainThread.cpp",
+ "URLSearchParams.cpp",
+ "URLWorker.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["tests/chrome.ini"]
+BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/dom/url/tests/browser.ini b/dom/url/tests/browser.ini
new file mode 100644
index 0000000000..ad58be9034
--- /dev/null
+++ b/dom/url/tests/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ empty.html
+
+[browser_download_after_revoke.js]
diff --git a/dom/url/tests/browser_download_after_revoke.js b/dom/url/tests/browser_download_after_revoke.js
new file mode 100644
index 0000000000..be8ad96e70
--- /dev/null
+++ b/dom/url/tests/browser_download_after_revoke.js
@@ -0,0 +1,94 @@
+async function test() {
+ waitForExplicitFinish();
+ const target = "http://example.com/browser/dom/url/tests/empty.html";
+ info("Loading download page...");
+ let tab = BrowserTestUtils.addTab(gBrowser, target);
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+ gBrowser.selectedTab = tab;
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, target).then(
+ async () => {
+ info("Page loaded.");
+ let allDownloads = await Downloads.getList(Downloads.ALL);
+ let started = new Promise(resolve => {
+ if (
+ Services.prefs.getBoolPref(
+ "browser.download.always_ask_before_handling_new_types",
+ false
+ )
+ ) {
+ // If the download modal is enabled, wait for it to open and declare the
+ // download to have begun when we see it.
+ let listener = {
+ onOpenWindow(aXULWindow) {
+ info("Download modal shown...");
+ Services.wm.removeListener(listener);
+
+ let domwindow = aXULWindow.docShell.domWindow;
+ function onModalLoad() {
+ domwindow.removeEventListener("load", onModalLoad, true);
+
+ is(
+ domwindow.document.location.href,
+ "chrome://mozapps/content/downloads/unknownContentType.xhtml",
+ "Download modal loaded..."
+ );
+
+ domwindow.close();
+ info("Download modal closed.");
+ resolve();
+ }
+
+ domwindow.addEventListener("load", onModalLoad, true);
+ },
+ onCloseWindow(aXULWindow) {},
+ };
+
+ Services.wm.addListener(listener);
+ } else {
+ // With no download modal, the download will begin on its own, so we need
+ // to wait to be notified by the downloads list when that happens.
+ let downloadView = {
+ onDownloadAdded(download) {
+ ok(true, "Download was started.");
+ download.cancel();
+ allDownloads.removeView(this);
+ allDownloads.removeFinished();
+ resolve();
+ },
+ };
+ allDownloads.addView(downloadView);
+ }
+ });
+
+ let revoked = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () =>
+ new Promise(resolve => {
+ info("Creating BlobURL...");
+ let blob = new content.Blob(["test"], { type: "text/plain" });
+ let url = content.URL.createObjectURL(blob);
+
+ let link = content.document.createElement("a");
+ link.href = url;
+ link.download = "example.txt";
+ content.document.body.appendChild(link);
+ info("Clicking HTMLAnchorElement...");
+ link.click();
+
+ content.URL.revokeObjectURL(url);
+ info("BlobURL revoked.");
+ resolve();
+ })
+ );
+
+ info("Waiting for async activities...");
+ await Promise.all([revoked, started]);
+ ok(true, "Exiting test.");
+ finish();
+ }
+ );
+}
diff --git a/dom/url/tests/chrome.ini b/dom/url/tests/chrome.ini
new file mode 100644
index 0000000000..1f1b9022c9
--- /dev/null
+++ b/dom/url/tests/chrome.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ file_url.jsm
+ file_worker_url.jsm
+ test_bug883784.jsm
+ jsm_url_worker.js
+ !/dom/workers/test/dom_worker_helper.js
+
+[test_bug883784.xhtml]
+[test_url.xhtml]
+[test_worker_url.xhtml]
diff --git a/dom/url/tests/empty.html b/dom/url/tests/empty.html
new file mode 100644
index 0000000000..358db717dd
--- /dev/null
+++ b/dom/url/tests/empty.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html><body></body></html>
diff --git a/dom/url/tests/file_url.jsm b/dom/url/tests/file_url.jsm
new file mode 100644
index 0000000000..6be99f2931
--- /dev/null
+++ b/dom/url/tests/file_url.jsm
@@ -0,0 +1,24 @@
+var EXPORTED_SYMBOLS = ["checkFromJSM"];
+
+function checkFromJSM(ok, is) {
+ var url = new URL("http://www.example.com");
+ is(url.href, "http://www.example.com/", "JSM should have URL");
+
+ var url2 = new URL("/foobar", url);
+ is(
+ url2.href,
+ "http://www.example.com/foobar",
+ "JSM should have URL - based on another URL"
+ );
+
+ var blob = new Blob(["a"]);
+ url = URL.createObjectURL(blob);
+ ok(url, "URL is created!");
+
+ var u = new URL(url);
+ ok(u, "URL created");
+ is(u.origin, "null", "Url doesn't have an origin if created in a JSM");
+
+ URL.revokeObjectURL(url);
+ ok(true, "URL is revoked");
+}
diff --git a/dom/url/tests/file_worker_url.jsm b/dom/url/tests/file_worker_url.jsm
new file mode 100644
index 0000000000..30a072ef9d
--- /dev/null
+++ b/dom/url/tests/file_worker_url.jsm
@@ -0,0 +1,23 @@
+var EXPORTED_SYMBOLS = ["checkFromJSM"];
+
+function checkFromJSM(ok, is, finish) {
+ let worker = new ChromeWorker("jsm_url_worker.js");
+ worker.onmessage = function (event) {
+ if (event.data.type == "finish") {
+ finish();
+ } else if (event.data.type == "url") {
+ URL.revokeObjectURL(event.data.url);
+ } 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);
+ worker.terminate();
+ finish();
+ };
+
+ worker.postMessage(0);
+}
diff --git a/dom/url/tests/jsm_url_worker.js b/dom/url/tests/jsm_url_worker.js
new file mode 100644
index 0000000000..d4cabe4113
--- /dev/null
+++ b/dom/url/tests/jsm_url_worker.js
@@ -0,0 +1,84 @@
+/* eslint-env worker */
+
+onmessage = function (event) {
+ if (event.data != 0) {
+ var worker = new Worker("jsm_url_worker.js");
+ worker.onmessage = function (ev) {
+ postMessage(ev.data);
+ };
+
+ worker.postMessage(event.data - 1);
+ return;
+ }
+
+ let status = false;
+ try {
+ if (URL instanceof Object) {
+ status = true;
+ }
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "URL object:" + URL });
+
+ status = false;
+ var blob = null;
+ try {
+ blob = new Blob([]);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob:" + blob });
+
+ status = false;
+ var url = null;
+ try {
+ url = URL.createObjectURL(blob);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob URL:" + url });
+
+ status = false;
+ try {
+ URL.revokeObjectURL(url);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob Revoke URL" });
+
+ status = false;
+ url = null;
+ try {
+ url = URL.createObjectURL(true);
+ } catch (e) {
+ status = true;
+ }
+
+ postMessage({
+ type: "status",
+ status,
+ msg: "CreateObjectURL should fail if the arg is not a blob",
+ });
+
+ status = false;
+ url = null;
+ try {
+ url = URL.createObjectURL(blob);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob URL2:" + url });
+
+ status = false;
+ try {
+ URL.createObjectURL({});
+ } catch (e) {
+ status = true;
+ }
+
+ postMessage({ type: "status", status, msg: "Exception wanted" });
+
+ postMessage({ type: "url", url });
+
+ postMessage({ type: "finish" });
+};
diff --git a/dom/url/tests/mochitest.ini b/dom/url/tests/mochitest.ini
new file mode 100644
index 0000000000..0e14b90ea9
--- /dev/null
+++ b/dom/url/tests/mochitest.ini
@@ -0,0 +1,25 @@
+[DEFAULT]
+support-files =
+ url_worker.js
+ urlApi_worker.js
+ urlSearchParams_commons.js
+ urlSearchParams_worker.js
+ url_exceptions_worker.js
+
+[test_url.html]
+[test_url_data.html]
+[test_url_empty_port.html]
+[test_url_malformedHost.html]
+[test_urlExceptions.html]
+[test_urlSearchParams.html]
+[test_urlSearchParams_sorting.html]
+[test_urlSearchParams_utf8.html]
+[test_urlutils_stringify.html]
+[test_worker_url.html]
+[test_worker_urlApi.html]
+[test_worker_url_exceptions.html]
+[test_worker_urlSearchParams.html]
+[test_unknown_url_origin.html]
+[test_bloburl_location.html]
+[test_worker_protocol.html]
+support-files = protocol_worker.js
diff --git a/dom/url/tests/protocol_worker.js b/dom/url/tests/protocol_worker.js
new file mode 100644
index 0000000000..c038254a4c
--- /dev/null
+++ b/dom/url/tests/protocol_worker.js
@@ -0,0 +1,25 @@
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({ type: "finish" });
+}
+
+let url = new URL("http://example.com");
+is(url.protocol, "http:", "http: expected");
+
+url.protocol = "https:";
+is(url.protocol, "https:", "http: -> https:");
+
+url.protocol = "ftp:";
+is(url.protocol, "ftp:", "https: -> ftp:");
+
+url.protocol = "https:";
+is(url.protocol, "https:", "ftp: -> https:");
+
+finish();
diff --git a/dom/url/tests/test_bloburl_location.html b/dom/url/tests/test_bloburl_location.html
new file mode 100644
index 0000000000..f8442cffc5
--- /dev/null
+++ b/dom/url/tests/test_bloburl_location.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for blobURL in location</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+var expectedData = null;
+onmessage = function(e) {
+ if (expectedData === null) {
+ expectedData = e.data;
+ } else {
+ is(e.data, expectedData, "Pathname should be not be changed");
+ SimpleTest.finish();
+ }
+};
+
+SpecialPowers.pushPrefEnv({
+ "set": [["privacy.partition.bloburl_per_agent_cluster", false]]
+}).then(() => {
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+
+ ifr.src = "data:text/html,<script>location=URL.createObjectURL(new%20Blob(['<script>parent.postMessage(location.pathname,\"*\");location.pathname=\"foo\";parent.postMessage(location.pathname,\"*\");<\/s' +'cript>'], {type:\"text/html\"}));<\/script>";
+});
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_bug883784.jsm b/dom/url/tests/test_bug883784.jsm
new file mode 100644
index 0000000000..1790007680
--- /dev/null
+++ b/dom/url/tests/test_bug883784.jsm
@@ -0,0 +1,38 @@
+var EXPORTED_SYMBOLS = ["Test"];
+
+var Test = {
+ start(ok, is, finish) {
+ let worker = new ChromeWorker("jsm_url_worker.js");
+ worker.onmessage = function (event) {
+ 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!");
+ URL.revokeObjectURL(event.data.url);
+ finish();
+ }
+ };
+ xhr.onerror = function () {
+ ok(false, "Blob unreadable, should not happen!");
+ URL.revokeObjectURL(event.data.url);
+ finish();
+ };
+ xhr.send();
+ }
+ };
+
+ var self = this;
+ worker.onerror = function (event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.data);
+ self.worker.terminate();
+ finish();
+ };
+
+ worker.postMessage(0);
+ },
+};
diff --git a/dom/url/tests/test_bug883784.xhtml b/dom/url/tests/test_bug883784.xhtml
new file mode 100644
index 0000000000..381eb08a37
--- /dev/null
+++ b/dom/url/tests/test_bug883784.xhtml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="chrome://mochitests/content/chrome/dom/workers/test/dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ /* import-globals-from ../../workers/test/dom_worker_helper.js */
+ function test()
+ {
+ waitForWorkerFinish();
+
+ const {Test} = ChromeUtils.import("chrome://mochitests/content/chrome/dom/url/tests/test_bug883784.jsm");
+ Test.start(ok, is, finish);
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/url/tests/test_unknown_url_origin.html b/dom/url/tests/test_unknown_url_origin.html
new file mode 100644
index 0000000000..1c5571e9e4
--- /dev/null
+++ b/dom/url/tests/test_unknown_url_origin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for unknwon URL.origin</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+ is((new URL("blob:http://foo.com/bar")).origin, "http://foo.com");
+ is((new URL("blob:blob:http://foo.com/bar")).origin, "http://foo.com");
+
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_url.html b/dom/url/tests/test_url.html
new file mode 100644
index 0000000000..85f39e54b9
--- /dev/null
+++ b/dom/url/tests/test_url.html
@@ -0,0 +1,507 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test URL API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887364">Mozilla Bug 887364</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=991471">Mozilla Bug 991471</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=996055">Mozilla Bug 996055</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script>
+
+ /** Test for Bug 887364 **/
+ ok("URL" in window, "window.URL exists");
+
+ 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%3f",
+ },
+ { 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:",
+ },
+
+ { url: "about:blank",
+ base: undefined,
+ error: false,
+ protocol: "about:",
+ pathname: "blank",
+ skip_setters: false,
+ },
+
+ { url: "foo:bar?what#yeah",
+ base: undefined,
+ error: false,
+ protocol: "foo:",
+ pathname: "bar",
+ search: "?what",
+ hash: "#yeah",
+ skip_setters: false,
+ },
+
+ { url: "http://sub2.xn--lt-uia.mochi.test:8888/foo",
+ base: undefined,
+ error: false,
+ href: "http://sub2.xn--lt-uia.mochi.test:8888/foo",
+ origin: "http://sub2.xn--lt-uia.mochi.test:8888",
+ protocol: "http:",
+ username: "",
+ password: "",
+ host: "sub2.xn--lt-uia.mochi.test:8888",
+ hostname: "sub2.xn--lt-uia.mochi.test",
+ port: "8888",
+ pathname: "/foo",
+ search: "",
+ hash: "",
+ },
+ { url: "http://sub2.ält.mochi.test:8888/foo",
+ base: undefined,
+ error: false,
+ href: "http://sub2.xn--lt-uia.mochi.test:8888/foo",
+ origin: "http://sub2.xn--lt-uia.mochi.test:8888",
+ protocol: "http:",
+ username: "",
+ password: "",
+ host: "sub2.xn--lt-uia.mochi.test:8888",
+ hostname: "sub2.xn--lt-uia.mochi.test",
+ port: "8888",
+ pathname: "/foo",
+ search: "",
+ hash: "",
+ },
+ ];
+
+ 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");
+
+ {
+ let uri = SpecialPowers.wrap(url).URI;
+ is(url.href, uri.spec, "Conversion to nsIURI");
+ is(SpecialPowers.wrap(URL).fromURI(uri).href, uri.spec, "Conversion from nsIURI");
+ }
+
+ if ("skip_setters" in test && test.skip_setters === false) {
+ info("Skip setter methods for URL: " + test);
+ continue;
+ }
+
+ 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");
+
+ if ("href" in test) is(test.href, url + "", "stringify works");
+ }
+
+ </script>
+
+ <script>
+ /** Test for Bug 991471 **/
+ var url = new URL("http://localhost/");
+ url.hostname = "";
+ url.username = "tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt";
+ url.hostname = "www.mozilla.org";
+ url.username = "";
+ url.hostname = "www.mozilla.org";
+ is(url.href, "http://www.mozilla.org/", "No parsing error with empty host");
+ </script>
+
+ <script>
+ /** Test for Bug 996055 **/
+ var url = new URL("http://localhost/");
+ url.hostname = "";
+ is(url.href, "http://localhost/", "Empty hostname is ignored");
+ </script>
+
+ <script>
+ /** Test for Bug 960014 **/
+ var url = new URL("http://localhost/");
+ url.hostname = "[2001::1]";
+ is(url.hostname, "[2001::1]", "IPv6 hostname");
+ is(url.href, "http://[2001::1]/");
+
+ url.hostname = "[::192.9.5.5]";
+ is(url.hostname, "[::c009:505]", "IPv6 hostname");
+ is(url.href, "http://[::c009:505]/");
+
+ url = new URL("http://localhost/");
+ url.hostname = "[::]";
+ is(url.hostname, "[::]", "IPv6 hostname");
+
+ url = new URL("http://localhost/");
+ url.host = "[2001::1]:30";
+ is(url.hostname, "[2001::1]", "IPv6 hostname");
+ is(url.port, "30", "Port");
+ is(url.host, "[2001::1]:30", "IPv6 host");
+
+ url = new URL("http://localhost/");
+ // This should silently fail since it's missing the brackets
+ url.hostname = "2001::1";
+ is(url.hostname, "localhost", "Setting bad hostname fails");
+ </script>
+
+ <script>
+ var blob = new Blob(["a"]);
+ var url = URL.createObjectURL(blob);
+
+ var u = new URL(url);
+ is(u.origin, location.origin, "The URL generated from a blob URI has an origin");
+ </script>
+
+ <script>
+ var blob = new Blob(["a"]);
+ var url = URL.createObjectURL(blob);
+
+ var a = document.createElement("A");
+ a.href = url;
+ is(a.origin, location.origin, "The 'a' element has the correct origin");
+ </script>
+
+ <script>
+ var blob = new Blob(["a"]);
+ var url = URL.createObjectURL(blob);
+ URL.revokeObjectURL(url);
+ URL.revokeObjectURL(url);
+ ok(true, "Calling revokeObjectURL twice should be ok");
+ </script>
+
+ <script>
+ URL.revokeObjectURL("blob:something");
+ ok(true, "This should not throw.");
+ </script>
+
+ <script>
+ var base = new URL("http:\\\\test.com\\path/to\\file?query\\backslash#hash\\");
+ is(base.href, "http://test.com/path/to/file?query\\backslash#hash\\");
+
+ var url = new URL("..\\", base);
+ is(url.href, "http://test.com/path/");
+
+ url = new URL("\\test", base);
+ is(url.href, "http://test.com/test");
+
+ url = new URL("\\test\\", base);
+ is(url.href, "http://test.com/test/");
+
+ url = new URL("http://example.org/test", base);
+ is(url.href, "http://example.org/test");
+
+ url = new URL("ftp://tmp/test", base);
+ is(url.href, "ftp://tmp/test");
+
+ url = new URL("ftp:\\\\tmp\\test", base);
+ is(url.href, "ftp://tmp/test");
+
+ url = new URL("scheme://tmp\\test", base);
+ is(url.href, "scheme://tmp\\test");
+ </script>
+
+ <script>
+ /** Test for Bug 1275746 **/
+ SimpleTest.doesThrow(() => { new URL("http:"); }, "http: is not a valid URL");
+ SimpleTest.doesThrow(() => { new URL("http:///"); }, "http: is not a valid URL");
+
+ var url = new URL("file:");
+ is(url.href, "file:///", "Parsing file: should work.");
+
+ url = new URL("file:///");
+ is(url.href, "file:///", "Parsing file:/// should work.");
+ </script>
+
+ <script>
+ var url = new URL("scheme:path/to/file?query#hash");
+ is(url.href, "scheme:path/to/file?query#hash");
+ is(url.pathname, "path/to/file");
+ is(url.search, "?query");
+ is(url.hash, "#hash");
+
+ // pathname cannot be overwritten.
+ url.pathname = "new/path?newquery#newhash";
+ is(url.href, "scheme:path/to/file?query#hash");
+
+ // don't escape '#' until we implement a spec-compliant parser.
+ url.search = "?newquery#newhash";
+ is(url.href, "scheme:path/to/file?newquery#newhash#hash");
+
+ // nulls get encoded, whitespace gets stripped
+ url = new URL("scheme:pa\0\nth/to/fi\0\nle?qu\0\nery#ha\0\nsh");
+ is(url.href, "scheme:pa%00th/to/fi%00le?qu%00ery#ha%00sh");
+
+ url.search = "new\0\nquery";
+ is(url.href, "scheme:pa%00th/to/fi%00le?new%00%0Aquery#ha%00sh");
+ url.hash = "new\0\nhash";
+ is(url.href, "scheme:pa%00th/to/fi%00le?new%00%0Aquery#new%00%0Ahash");
+
+ url = new URL("scheme:path#hash");
+ is(url.href, "scheme:path#hash");
+ url.search = "query";
+ is(url.href, "scheme:path?query#hash");
+ url.hash = "";
+ is(url.href, "scheme:path?query");
+ url.hash = "newhash";
+ is(url.href, "scheme:path?query#newhash");
+ url.search = "";
+ is(url.href, "scheme:path#newhash");
+
+ // we don't implement a spec-compliant parser yet.
+ // make sure we are bug compatible with existing implementations.
+ url = new URL("data:text/html,<a href=\"http://example.org/?q\">Link</a>");
+ is(url.href, "data:text/html,<a href=\"http://example.org/?q\">Link</a>");
+ </script>
+
+ <script>
+ var u = new URL("http://www.example.org");
+ is(u.toJSON(), "http://www.example.org/", "URL.toJSON()");
+ is(JSON.stringify(u), "\"http://www.example.org/\"", "JSON.stringify(u) works");
+ </script>
+
+ <script>
+ // bug 1648493
+ var url = new URL("http://example.com/");
+ url.protocol = 'file'
+ url.protocol = 'resource'
+ url.password = 'ê'
+ url.username = 'ç'
+ url.protocol = 't'
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_url.xhtml b/dom/url/tests/test_url.xhtml
new file mode 100644
index 0000000000..10978c1f72
--- /dev/null
+++ b/dom/url/tests/test_url.xhtml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="Test for URL API"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for URL API. **/
+ // Import our test JSM. We first strip the filename off
+ // the chrome url, then append the jsm filename.
+ var base = /.*\//.exec(window.location.href)[0];
+ const {checkFromJSM} = ChromeUtils.import(base + "file_url.jsm");
+
+ checkFromJSM(ok, is);
+
+ ]]></script>
+</window>
diff --git a/dom/url/tests/test_urlExceptions.html b/dom/url/tests/test_urlExceptions.html
new file mode 100644
index 0000000000..4feb30a948
--- /dev/null
+++ b/dom/url/tests/test_urlExceptions.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=926890
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 926890</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=926890">Mozilla Bug 926890</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ // URL.href throws
+ var url = new URL("http://www.example.com");
+ ok(url, "URL created");
+
+ try {
+ url.href = "42";
+ ok(false, "url.href = 42 should throw");
+ } catch (e) {
+ ok(true, "url.href = 42 should throw");
+ ok(e instanceof TypeError, "error type typeError");
+ }
+
+ url.href = "http://www.example.org";
+ ok(true, "url.href should not throw");
+
+ try {
+ new URL("42");
+ ok(false, "new URL(42) should throw");
+ } catch (e) {
+ ok(true, "new URL(42) should throw");
+ ok(e instanceof TypeError, "error type typeError");
+ }
+
+ try {
+ new URL("http://www.example.com", "42");
+ ok(false, "new URL(something, 42) should throw");
+ } catch (e) {
+ ok(true, "new URL(something, 42) should throw");
+ ok(e instanceof TypeError, "error type typeError");
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_urlSearchParams.html b/dom/url/tests/test_urlSearchParams.html
new file mode 100644
index 0000000000..d86959ab64
--- /dev/null
+++ b/dom/url/tests/test_urlSearchParams.html
@@ -0,0 +1,59 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887836
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for URLSearchParams</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="urlSearchParams_commons.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887836">Mozilla Bug 887836</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+ /** Test for Bug 887836 **/
+ ok("URLSearchParams" in window, "window.URLSearchParams exists");
+
+ var tests = [
+ testSimpleURLSearchParams,
+ testCopyURLSearchParams,
+ testParserURLSearchParams,
+ testURL,
+ testEncoding,
+ testOrdering,
+ testDelete,
+ testGetNULL,
+ testSet,
+ testIterable,
+ testZeroHandling,
+ testCopyConstructor,
+ testCTORs,
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/url/tests/test_urlSearchParams_sorting.html b/dom/url/tests/test_urlSearchParams_sorting.html
new file mode 100644
index 0000000000..a608e8bc13
--- /dev/null
+++ b/dom/url/tests/test_urlSearchParams_sorting.html
@@ -0,0 +1,63 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for URLSearchParams.sort()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+
+function compareArray(a, b) {
+ is(a.length, b.length, "Length matches");
+ for (let i = 0; i < a.length; ++i) {
+ is(a[i], b[i], "Values " + i + " match");
+ }
+}
+
+[
+ {
+ "input": "z=b&a=b&z=a&a=a",
+ "output": [["a", "b"], ["a", "a"], ["z", "b"], ["z", "a"]],
+ },
+ {
+ "input": "\uFFFD=x&\uFFFC&\uFFFD=a",
+ "output": [["\uFFFC", ""], ["\uFFFD", "x"], ["\uFFFD", "a"]],
+ },
+ {
+ "input": "ffi&🌈", // 🌈 > code point, but < code unit because two code units
+ "output": [["🌈", ""], ["ffi", ""]],
+ },
+ {
+ "input": "é&e\uFFFD&e\u0301",
+ "output": [["e\u0301", ""], ["e\uFFFD", ""], ["é", ""]],
+ },
+ {
+ "input": "z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g",
+ "output": [["a", "a"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["a", "f"], ["a", "g"], ["z", "z"], ["z", "y"], ["z", "x"], ["z", "w"], ["z", "v"], ["z", "u"], ["z", "t"]],
+ },
+].forEach((val) => {
+ info("Run test: " + JSON.stringify(val) + "\n");
+
+ let params = new URLSearchParams(val.input);
+ params.sort();
+
+ let i = 0;
+ for (let param of params) {
+ compareArray(param, val.output[i++]);
+ }
+
+ let url = new URL("?" + val.input, "https://example/");
+ url.searchParams.sort();
+ params = new URLSearchParams(url.search);
+ i = 0;
+ for (let param of params) {
+ compareArray(param, val.output[i++]);
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/url/tests/test_urlSearchParams_utf8.html b/dom/url/tests/test_urlSearchParams_utf8.html
new file mode 100644
index 0000000000..2a37b3b9a1
--- /dev/null
+++ b/dom/url/tests/test_urlSearchParams_utf8.html
@@ -0,0 +1,39 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1032511
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1032511</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1032511">Mozilla Bug 1032511</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+<a href="http://www.example.net?a=b&c=d" id="anchor">foobar</a>
+<area href="http://www.example.net?a=b&c=d" id="area">foobar</area>
+<script type="application/javascript">
+
+ /** Test for Bug 1032511 **/
+ var a = new URLSearchParams("%e2");
+ ok(a, "a exists");
+ is(a.toString(), "%EF%BF%BD=", "The value should be here.");
+
+ a = new URLSearchParams("a%e2");
+ is(a.toString(), "a%EF%BF%BD=", "The value should be here.");
+
+ a = new URLSearchParams("a%e2b");
+ is(a.toString(), "a%EF%BF%BDb=", "The value should be here.");
+
+</script>
+</body>
+</html>
diff --git a/dom/url/tests/test_url_data.html b/dom/url/tests/test_url_data.html
new file mode 100644
index 0000000000..a4d017936a
--- /dev/null
+++ b/dom/url/tests/test_url_data.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test URL API - data:plain</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018682">Mozilla Bug 1018682</a>
+
+<script type="application/javascript">
+
+var base = new URL("data:text/plain,");
+
+let relative;
+try {
+ relative = new URL("a", base);
+ ok(false, "Relative URL from a data:text/plain should not work.");
+} catch (e) {
+ ok(true, "Relative URL from a data:text/plain should not work.");
+}
+
+base.protocol = "chrome:";
+is(base.protocol, "chrome:", "The protocol should be changed from data to chrome.");
+
+try {
+ relative = new URL("a", base);
+ ok(true, "Relative URL from a chrome: should work.");
+} catch (e) {
+ ok(false, "Relative URL from a chrome: should work.");
+}
+
+base.protocol = "http:";
+ok(true, "Protocol: http changed");
+is(base.href, "http://text/plain,", "Base URL is correct");
+
+relative = new URL("a", base);
+ok(relative, "This works.");
+is(relative.href, "http://text/a", "Relative URL is correct");
+
+</script>
+
+</body>
+</html>
diff --git a/dom/url/tests/test_url_empty_port.html b/dom/url/tests/test_url_empty_port.html
new file mode 100644
index 0000000000..5001bdfae5
--- /dev/null
+++ b/dom/url/tests/test_url_empty_port.html
@@ -0,0 +1,53 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=930450
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 930450</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930450">Mozilla Bug 930450</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <a id="link" href="http://www.example.com:8080">foobar</a>
+ <area id="area" href="http://www.example.com:8080" />
+ <script type="application/javascript">
+
+ var url = new URL("http://www.example.com:8080");
+ is(url.port, "8080", "URL.port is 8080");
+ url.port = "";
+ is(url.port, "", "URL.port is ''");
+ url.port = 0;
+ is(url.port, "0", "URL.port is 0");
+
+ var link = document.getElementById("link");
+ is(link.port, "8080", "URL.port is 8080");
+ link.port = "";
+ is(link.href, "http://www.example.com/", "link.href matches");
+ is(link.port, "", "URL.port is ''");
+ link.port = 0;
+ is(link.href, "http://www.example.com:0/", "link.href matches");
+ is(link.port, "0", "URL.port is 0");
+
+ var area = document.getElementById("area");
+ is(area.port, "8080", "URL.port is 8080");
+ area.port = "";
+ is(area.href, "http://www.example.com/", "area.href matches");
+ is(area.port, "", "URL.port is ''");
+ area.port = 0;
+ is(area.href, "http://www.example.com:0/", "area.href matches");
+ is(area.port, "0", "URL.port is 0");
+
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_url_malformedHost.html b/dom/url/tests/test_url_malformedHost.html
new file mode 100644
index 0000000000..7cc23d7e32
--- /dev/null
+++ b/dom/url/tests/test_url_malformedHost.html
@@ -0,0 +1,48 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020041
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020041</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020041">Mozilla Bug 1020041</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <a id="link" href="http://www.example.com:8080">foobar</a>
+ <area id="area" href="http://www.example.com:8080" />
+ <script type="application/javascript">
+
+ var tests = [
+ { host: "?", expected: "www.example.com" },
+ { host: "what?", expected: "what" },
+ { host: "so what", expected: "www.example.com" },
+ { host: "aa#bb", expected: "aa" },
+ { host: "a/b", expected: "a" },
+ { host: "a\\b", expected: "a" },
+ { host: "[2001::1]#bla:10", expected: "[2001::1]"},
+ ];
+
+ for (var i = 0; i < tests.length; ++i) {
+ var url = new URL("http://www.example.com");
+ url.host = tests[i].host;
+ is(url.host, tests[i].expected, "URL.host is: " + url.host);
+
+ url = new URL("http://www.example.com");
+ url.hostname = tests[i].host;
+ is(url.hostname, tests[i].expected, "URL.hostname is: " + url.host);
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_urlutils_stringify.html b/dom/url/tests/test_urlutils_stringify.html
new file mode 100644
index 0000000000..7c68051535
--- /dev/null
+++ b/dom/url/tests/test_urlutils_stringify.html
@@ -0,0 +1,38 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=959190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 959190</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=959190">Mozilla Bug 959190</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <a id="link" href="http://www.example.com:8080">foobar</a>
+ <area id="area" href="http://www.example.com:8080" />
+ <script type="application/javascript">
+
+ var url = new URL("http://www.example.com:8080");
+ is(url + "", "http://www.example.com:8080/", "URL stringify");
+
+ var link = document.getElementById("link");
+ is(link + "", "http://www.example.com:8080/", "Anchor stringify");
+
+ var area = document.getElementById("area");
+ is(area + "", "http://www.example.com:8080/", "Area stringify");
+
+ is((location + "").indexOf(`${location.origin}/tests/dom/url/tests/test_urlutils_stringify.html`), 0, "Location stringify");
+ </script>
+</body>
+</html>
diff --git a/dom/url/tests/test_worker_protocol.html b/dom/url/tests/test_worker_protocol.html
new file mode 100644
index 0000000000..1687159d6a
--- /dev/null
+++ b/dom/url/tests/test_worker_protocol.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for URL protocol setter in workers</title>
+ <script 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">
+
+ let worker = new Worker("protocol_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "Correct worker");
+
+ if (event.data.type == "finish") {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (event.data.type == "status") {
+ ok(event.data.status, event.data.msg);
+ return;
+ }
+
+ ok(false, "Invalid message.");
+ };
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/url/tests/test_worker_url.html b/dom/url/tests/test_worker_url.html
new file mode 100644
index 0000000000..8bcd265d19
--- /dev/null
+++ b/dom/url/tests/test_worker_url.html
@@ -0,0 +1,66 @@
+<!--
+ 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 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, "Correct worker");
+
+ if (event.data.type == "finish") {
+ runTest();
+ } 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, "Correct worker");
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ };
+
+ var tests = [
+ function() { worker.postMessage(0); },
+ function() { worker.postMessage(1); },
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ runTest();
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/url/tests/test_worker_url.xhtml b/dom/url/tests/test_worker_url.xhtml
new file mode 100644
index 0000000000..30d2aa131d
--- /dev/null
+++ b/dom/url/tests/test_worker_url.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function test()
+ {
+ SimpleTest.waitForExplicitFinish();
+
+ const {checkFromJSM} = ChromeUtils.import("chrome://mochitests/content/chrome/dom/url/tests/file_worker_url.jsm");
+ checkFromJSM(ok, is, SimpleTest.finish);
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/url/tests/test_worker_urlApi.html b/dom/url/tests/test_worker_urlApi.html
new file mode 100644
index 0000000000..34de982ef1
--- /dev/null
+++ b/dom/url/tests/test_worker_urlApi.html
@@ -0,0 +1,43 @@
+<!--
+ 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 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>
diff --git a/dom/url/tests/test_worker_urlSearchParams.html b/dom/url/tests/test_worker_urlSearchParams.html
new file mode 100644
index 0000000000..1870d5d7dc
--- /dev/null
+++ b/dom/url/tests/test_worker_urlSearchParams.html
@@ -0,0 +1,43 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for URLSearchParams object in workers</title>
+ <script 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("urlSearchParams_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.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(true);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/url/tests/test_worker_url_exceptions.html b/dom/url/tests/test_worker_url_exceptions.html
new file mode 100644
index 0000000000..2c8b493bbf
--- /dev/null
+++ b/dom/url/tests/test_worker_url_exceptions.html
@@ -0,0 +1,42 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for URL exceptions in workers</title>
+ <script 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_exceptions_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.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(0);
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/url/tests/urlApi_worker.js b/dom/url/tests/urlApi_worker.js
new file mode 100644
index 0000000000..40243c1e66
--- /dev/null
+++ b/dom/url/tests/urlApi_worker.js
@@ -0,0 +1,354 @@
+/* eslint-env worker */
+
+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,
+ });
+}
+
+// eslint-disable-next-line complexity
+onmessage = function () {
+ let status = false;
+ try {
+ if (URL instanceof Object) {
+ status = true;
+ }
+ } catch (e) {}
+
+ ok(status, "URL in workers \\o/");
+
+ 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%3f",
+ },
+ {
+ 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");
+ }
+
+ if ("href" in test) {
+ is(test.href, url + "", "stringify works");
+ }
+ }
+
+ postMessage({ type: "finish" });
+};
diff --git a/dom/url/tests/urlSearchParams_commons.js b/dom/url/tests/urlSearchParams_commons.js
new file mode 100644
index 0000000000..3a1dcb2807
--- /dev/null
+++ b/dom/url/tests/urlSearchParams_commons.js
@@ -0,0 +1,376 @@
+/* import-globals-from urlSearchParams_worker.js */
+
+function testSimpleURLSearchParams() {
+ var u = new URLSearchParams();
+ ok(u, "URLSearchParams created");
+ is(u.has("foo"), false, "URLSearchParams.has(foo)");
+ is(u.get("foo"), null, "URLSearchParams.get(foo)");
+ is(u.getAll("foo").length, 0, "URLSearchParams.getAll(foo)");
+
+ u.append("foo", "bar");
+ is(u.has("foo"), true, "URLSearchParams.has(foo)");
+ is(u.get("foo"), "bar", "URLSearchParams.get(foo)");
+ is(u.getAll("foo").length, 1, "URLSearchParams.getAll(foo)");
+
+ u.set("foo", "bar2");
+ is(u.get("foo"), "bar2", "URLSearchParams.get(foo)");
+ is(u.getAll("foo").length, 1, "URLSearchParams.getAll(foo)");
+
+ is(u + "", "foo=bar2", "stringifier");
+
+ u.delete("foo");
+
+ runTest();
+}
+
+function testCopyURLSearchParams() {
+ var u = new URLSearchParams();
+ ok(u, "URLSearchParams created");
+ u.append("foo", "bar");
+
+ var uu = new URLSearchParams(u);
+ is(uu.get("foo"), "bar", "uu.get()");
+
+ u.append("foo", "bar2");
+ is(u.getAll("foo").length, 2, "u.getAll()");
+ is(uu.getAll("foo").length, 1, "uu.getAll()");
+
+ runTest();
+}
+
+function testURL() {
+ var url = new URL("http://www.example.net?a=b&c=d");
+ ok(url.searchParams, "URL searchParams exists!");
+ ok(url.searchParams.has("a"), "URL.searchParams.has('a')");
+ is(url.searchParams.get("a"), "b", "URL.searchParams.get('a')");
+ ok(url.searchParams.has("c"), "URL.searchParams.has('c')");
+ is(url.searchParams.get("c"), "d", "URL.searchParams.get('c')");
+
+ url.searchParams.set("e", "f");
+ ok(url.href.indexOf("e=f") != 1, "URL right");
+
+ url = new URL("mailto:a@b.com?subject=Hi");
+ ok(url.searchParams, "URL searchParams exists!");
+ ok(url.searchParams.has("subject"), "Hi");
+
+ runTest();
+}
+
+function testParserURLSearchParams() {
+ var checks = [
+ { input: "", data: {} },
+ { input: "a", data: { a: [""] } },
+ { input: "a=b", data: { a: ["b"] } },
+ { input: "a=", data: { a: [""] } },
+ { input: "=b", data: { "": ["b"] } },
+ { input: "&", data: {} },
+ { input: "&a", data: { a: [""] } },
+ { input: "a&", data: { a: [""] } },
+ { input: "a&a", data: { a: ["", ""] } },
+ { input: "a&b&c", data: { a: [""], b: [""], c: [""] } },
+ { input: "a=b&c=d", data: { a: ["b"], c: ["d"] } },
+ { input: "a=b&c=d&", data: { a: ["b"], c: ["d"] } },
+ { input: "&&&a=b&&&&c=d&", data: { a: ["b"], c: ["d"] } },
+ { input: "a=a&a=b&a=c", data: { a: ["a", "b", "c"] } },
+ { input: "a==a", data: { a: ["=a"] } },
+ { input: "a=a+b+c+d", data: { a: ["a b c d"] } },
+ { input: "%=a", data: { "%": ["a"] } },
+ { input: "%a=a", data: { "%a": ["a"] } },
+ { input: "%a_=a", data: { "%a_": ["a"] } },
+ { input: "%61=a", data: { a: ["a"] } },
+ { input: "%=a", data: { "%": ["a"] } },
+ { input: "%a=a", data: { "%a": ["a"] } },
+ { input: "%a_=a", data: { "%a_": ["a"] } },
+ { input: "%61=a", data: { a: ["a"] } },
+ { input: "%61+%4d%4D=", data: { "a MM": [""] } },
+ { input: "?a=1", data: { a: ["1"] } },
+ { input: "?", data: {} },
+ { input: "?=b", data: { "": ["b"] } },
+ ];
+
+ for (var i = 0; i < checks.length; ++i) {
+ var u = new URLSearchParams(checks[i].input);
+
+ var count = 0;
+ for (var key in checks[i].data) {
+ count = count + 1;
+ ok(u.has(key), "key " + key + " found");
+
+ var all = u.getAll(key);
+ is(all.length, checks[i].data[key].length, "same number of elements");
+
+ for (var k = 0; k < all.length; ++k) {
+ is(all[k], checks[i].data[key][k], "value matches");
+ }
+ }
+ }
+
+ runTest();
+}
+
+function testEncoding() {
+ var encoding = [
+ ["1", "1"],
+ ["a b", "a+b"],
+ ["<>", "%3C%3E"],
+ ["\u0541", "%D5%81"],
+ ];
+
+ for (var i = 0; i < encoding.length; ++i) {
+ var url = new URL("http://www.example.net");
+ url.searchParams.set("a", encoding[i][0]);
+ is(url.href, "http://www.example.net/?a=" + encoding[i][1]);
+
+ var url2 = new URL(url.href);
+ is(url2.searchParams.get("a"), encoding[i][0], "a is still there");
+ }
+
+ runTest();
+}
+
+function testCopyConstructor() {
+ var url = new URL("http://example.com/");
+ var p = url.searchParams;
+ var q = new URLSearchParams(p);
+ q.set("a", "b");
+ is(
+ url.href,
+ "http://example.com/",
+ "Messing with copy of URLSearchParams should not affect URL"
+ );
+ p.set("c", "d");
+ is(
+ url.href,
+ "http://example.com/?c=d",
+ "Messing with URLSearchParams should affect URL"
+ );
+
+ runTest();
+}
+
+function testOrdering() {
+ var a = new URLSearchParams("a=1&a=2&b=3&c=4&c=5&a=6");
+ is(a.toString(), "a=1&a=2&b=3&c=4&c=5&a=6", "Order is correct");
+ is(a.getAll("a").length, 3, "Correct length of getAll()");
+
+ var b = new URLSearchParams();
+ b.append("a", "1");
+ b.append("b", "2");
+ b.append("a", "3");
+ is(b.toString(), "a=1&b=2&a=3", "Order is correct");
+ is(b.getAll("a").length, 2, "Correct length of getAll()");
+
+ runTest();
+}
+
+function testDelete() {
+ var a = new URLSearchParams("a=1&a=2&b=3&c=4&c=5&a=6");
+ is(a.toString(), "a=1&a=2&b=3&c=4&c=5&a=6", "Order is correct");
+ is(a.getAll("a").length, 3, "Correct length of getAll()");
+
+ a.delete("a");
+ is(a.getAll("a").length, 0, "Correct length of getAll()");
+ is(a.toString(), "b=3&c=4&c=5", "Order is correct");
+
+ runTest();
+}
+
+function testGetNULL() {
+ var u = new URLSearchParams();
+ is(typeof u.get(""), "object", "typeof URL.searchParams.get('')");
+ is(u.get(""), null, "URL.searchParams.get('') should be null");
+
+ var url = new URL("http://www.example.net?a=b");
+ is(
+ url.searchParams.get("b"),
+ null,
+ "URL.searchParams.get('b') should be null"
+ );
+ is(url.searchParams.get("a"), "b", "URL.searchParams.get('a')");
+
+ runTest();
+}
+
+function testSet() {
+ var u = new URLSearchParams();
+ u.set("a", "b");
+ u.set("e", "c");
+ u.set("i", "d");
+ u.set("o", "f");
+ u.set("u", "g");
+
+ is(u.get("a"), "b", "URL.searchParams.get('a') should return b");
+ is(u.getAll("a").length, 1, "URLSearchParams.getAll('a').length should be 1");
+
+ u.set("a", "h1");
+ u.set("a", "h2");
+ u.set("a", "h3");
+ u.set("a", "h4");
+ is(u.get("a"), "h4", "URL.searchParams.get('a') should return h4");
+ is(u.getAll("a").length, 1, "URLSearchParams.getAll('a').length should be 1");
+
+ is(u.get("e"), "c", "URL.searchParams.get('e') should return c");
+ is(u.get("i"), "d", "URL.searchParams.get('i') should return d");
+ is(u.get("o"), "f", "URL.searchParams.get('o') should return f");
+ is(u.get("u"), "g", "URL.searchParams.get('u') should return g");
+
+ is(u.getAll("e").length, 1, "URLSearchParams.getAll('e').length should be 1");
+ is(u.getAll("i").length, 1, "URLSearchParams.getAll('i').length should be 1");
+ is(u.getAll("o").length, 1, "URLSearchParams.getAll('o').length should be 1");
+ is(u.getAll("u").length, 1, "URLSearchParams.getAll('u').length should be 1");
+
+ u = new URLSearchParams("name1=value1&name1=value2&name1=value3");
+ is(
+ u.get("name1"),
+ "value1",
+ "URL.searchParams.get('name1') should return value1"
+ );
+ is(
+ u.getAll("name1").length,
+ 3,
+ "URLSearchParams.getAll('name1').length should be 3"
+ );
+ u.set("name1", "firstPair");
+ is(
+ u.get("name1"),
+ "firstPair",
+ "URL.searchParams.get('name1') should return firstPair"
+ );
+ is(
+ u.getAll("name1").length,
+ 1,
+ "URLSearchParams.getAll('name1').length should be 1"
+ );
+
+ runTest();
+}
+
+function testIterable() {
+ var u = new URLSearchParams();
+ u.set("1", "2");
+ u.set("2", "4");
+ u.set("3", "6");
+ u.set("4", "8");
+ u.set("5", "10");
+
+ var key_iter = u.keys();
+ var value_iter = u.values();
+ var entries_iter = u.entries();
+ for (var i = 0; i < 5; ++i) {
+ var v = i + 1;
+ var key = key_iter.next();
+ var value = value_iter.next();
+ var entry = entries_iter.next();
+ is(key.value, v.toString(), "Correct Key iterator: " + v.toString());
+ ok(!key.done, "Key.done is false");
+ is(
+ value.value,
+ (v * 2).toString(),
+ "Correct Value iterator: " + (v * 2).toString()
+ );
+ ok(!value.done, "Value.done is false");
+ is(
+ entry.value[0],
+ v.toString(),
+ "Correct Entry 0 iterator: " + v.toString()
+ );
+ is(
+ entry.value[1],
+ (v * 2).toString(),
+ "Correct Entry 1 iterator: " + (v * 2).toString()
+ );
+ ok(!entry.done, "Entry.done is false");
+ }
+
+ var last = key_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last key");
+
+ last = value_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last value");
+
+ last = entries_iter.next();
+ ok(last.done, "Nothing more to read.");
+
+ key_iter = u.keys();
+ key_iter.next();
+ key_iter.next();
+ u.delete("1");
+ u.delete("2");
+ u.delete("3");
+ u.delete("4");
+ u.delete("5");
+
+ last = key_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last key");
+
+ runTest();
+}
+function testZeroHandling() {
+ var u = new URLSearchParams();
+ u.set("a", "b\0c");
+ u.set("d\0e", "f");
+ u.set("g\0h", "i\0j");
+ is(
+ u.toString(),
+ "a=b%00c&d%00e=f&g%00h=i%00j",
+ "Should encode U+0000 as %00"
+ );
+
+ runTest();
+}
+
+function testCTORs() {
+ var a = new URLSearchParams("a=b");
+ is(a.get("a"), "b", "CTOR with string");
+
+ var b = new URLSearchParams([
+ ["a", "b"],
+ ["c", "d"],
+ ]);
+ is(b.get("a"), "b", "CTOR with sequence");
+ is(b.get("c"), "d", "CTOR with sequence");
+
+ ok(new URLSearchParams([]), "CTOR with empty sequence");
+
+ let result;
+ try {
+ result = new URLSearchParams([[1]]);
+ } catch (e) {
+ result = 42;
+ }
+
+ is(
+ result,
+ 42,
+ "CTOR throws if the sequence doesn't contain exactly 2 elements"
+ );
+
+ try {
+ result = new URLSearchParams([[1, 2, 3]]);
+ } catch (e) {
+ result = 43;
+ }
+ is(
+ result,
+ 43,
+ "CTOR throws if the sequence doesn't contain exactly 2 elements"
+ );
+
+ var c = new URLSearchParams({
+ a: "b",
+ c: 42,
+ d: null,
+ e: [1, 2, 3],
+ f: { a: 42 },
+ });
+ is(c.get("a"), "b", "CTOR with record<>");
+ is(c.get("c"), "42", "CTOR with record<>");
+ is(c.get("d"), "null", "CTOR with record<>");
+ is(c.get("e"), [1, 2, 3].toString(), "CTOR with record<>");
+ is(c.get("f"), { a: 42 }.toString(), "CTOR with record<>");
+
+ runTest();
+}
diff --git a/dom/url/tests/urlSearchParams_worker.js b/dom/url/tests/urlSearchParams_worker.js
new file mode 100644
index 0000000000..7959fd7d1c
--- /dev/null
+++ b/dom/url/tests/urlSearchParams_worker.js
@@ -0,0 +1,48 @@
+/* eslint-env worker */
+
+importScripts("urlSearchParams_commons.js");
+
+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,
+ });
+}
+
+var tests = [
+ testSimpleURLSearchParams,
+ testCopyURLSearchParams,
+ testParserURLSearchParams,
+ testURL,
+ testEncoding,
+ testCTORs,
+];
+
+function runTest() {
+ if (!tests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onmessage = function () {
+ let status = false;
+ try {
+ if (URLSearchParams instanceof Object) {
+ status = true;
+ }
+ } catch (e) {}
+ ok(status, "URLSearchParams in workers \\o/");
+
+ runTest();
+};
diff --git a/dom/url/tests/url_exceptions_worker.js b/dom/url/tests/url_exceptions_worker.js
new file mode 100644
index 0000000000..c8d8494ac9
--- /dev/null
+++ b/dom/url/tests/url_exceptions_worker.js
@@ -0,0 +1,38 @@
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+onmessage = function (event) {
+ // URL.href throws
+ var url = new URL("http://www.example.com");
+ ok(url, "URL created");
+
+ var status = false;
+ try {
+ url.href = "42";
+ } catch (e) {
+ status = true;
+ }
+ ok(status, "url.href = 42 should throw");
+
+ url.href = "http://www.example.org";
+ ok(true, "url.href should not throw");
+
+ status = false;
+ try {
+ new URL("42");
+ } catch (e) {
+ status = true;
+ }
+ ok(status, "new URL(42) should throw");
+
+ status = false;
+ try {
+ new URL("http://www.example.com", "42");
+ } catch (e) {
+ status = true;
+ }
+ ok(status, "new URL(something, 42) should throw");
+
+ postMessage({ type: "finish" });
+};
diff --git a/dom/url/tests/url_worker.js b/dom/url/tests/url_worker.js
new file mode 100644
index 0000000000..8a69744603
--- /dev/null
+++ b/dom/url/tests/url_worker.js
@@ -0,0 +1,98 @@
+/* eslint-env worker */
+
+onmessage = function (event) {
+ if (event.data != 0) {
+ var worker = new Worker("url_worker.js");
+ worker.onmessage = function (ev) {
+ postMessage(ev.data);
+ };
+
+ worker.postMessage(event.data - 1);
+ return;
+ }
+
+ let status = false;
+ try {
+ if (URL instanceof Object) {
+ status = true;
+ }
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "URL object:" + URL });
+
+ status = false;
+ var blob = null;
+ try {
+ blob = new Blob([]);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob:" + blob });
+
+ status = false;
+ let url = null;
+ try {
+ url = URL.createObjectURL(blob);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob URL:" + url });
+
+ status = false;
+ try {
+ URL.revokeObjectURL(url);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob Revoke URL" });
+
+ status = false;
+ url = null;
+ try {
+ url = URL.createObjectURL(true);
+ } catch (e) {
+ status = true;
+ }
+
+ postMessage({
+ type: "status",
+ status,
+ msg: "CreateObjectURL should fail if the arg is not a blob",
+ });
+
+ status = false;
+ url = null;
+ try {
+ url = URL.createObjectURL(blob);
+ status = true;
+ } catch (e) {}
+
+ postMessage({ type: "status", status, msg: "Blob URL2:" + url });
+ postMessage({ type: "url", url });
+
+ status = false;
+ try {
+ URL.createObjectURL({});
+ } catch (e) {
+ status = true;
+ }
+
+ postMessage({ type: "status", status, msg: "Exception wanted" });
+
+ blob = new Blob([123]);
+ var uri = URL.createObjectURL(blob);
+ postMessage({
+ type: "status",
+ status: !!uri,
+ msg: "The URI has been generated from the blob",
+ });
+
+ var u = new URL(uri);
+ postMessage({
+ type: "status",
+ status: u.origin == location.origin,
+ msg: "The URL generated from a blob URI has an origin.",
+ });
+
+ postMessage({ type: "finish" });
+};