From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/url/URL.cpp | 442 +++++++++++++++++++++ dom/url/URL.h | 147 +++++++ dom/url/URLMainThread.cpp | 102 +++++ dom/url/URLMainThread.h | 31 ++ dom/url/URLSearchParams.cpp | 246 ++++++++++++ dom/url/URLSearchParams.h | 129 ++++++ dom/url/URLWorker.cpp | 160 ++++++++ dom/url/URLWorker.h | 45 +++ dom/url/moz.build | 35 ++ dom/url/tests/browser.ini | 5 + dom/url/tests/browser_download_after_revoke.js | 94 +++++ dom/url/tests/chrome.ini | 12 + dom/url/tests/empty.html | 2 + dom/url/tests/file_url.jsm | 24 ++ dom/url/tests/file_worker_url.jsm | 23 ++ dom/url/tests/jsm_url_worker.js | 84 ++++ dom/url/tests/mochitest.ini | 25 ++ dom/url/tests/protocol_worker.js | 25 ++ dom/url/tests/test_bloburl_location.html | 35 ++ dom/url/tests/test_bug883784.jsm | 38 ++ dom/url/tests/test_bug883784.xhtml | 34 ++ dom/url/tests/test_unknown_url_origin.html | 17 + dom/url/tests/test_url.html | 507 ++++++++++++++++++++++++ dom/url/tests/test_url.xhtml | 24 ++ dom/url/tests/test_urlExceptions.html | 56 +++ dom/url/tests/test_urlSearchParams.html | 59 +++ dom/url/tests/test_urlSearchParams_sorting.html | 63 +++ dom/url/tests/test_urlSearchParams_utf8.html | 39 ++ dom/url/tests/test_url_data.html | 45 +++ dom/url/tests/test_url_empty_port.html | 53 +++ dom/url/tests/test_url_malformedHost.html | 48 +++ dom/url/tests/test_urlutils_stringify.html | 38 ++ dom/url/tests/test_worker_protocol.html | 41 ++ dom/url/tests/test_worker_url.html | 66 +++ dom/url/tests/test_worker_url.xhtml | 33 ++ dom/url/tests/test_worker_urlApi.html | 43 ++ dom/url/tests/test_worker_urlSearchParams.html | 43 ++ dom/url/tests/test_worker_url_exceptions.html | 42 ++ dom/url/tests/urlApi_worker.js | 354 +++++++++++++++++ dom/url/tests/urlSearchParams_commons.js | 376 ++++++++++++++++++ dom/url/tests/urlSearchParams_worker.js | 48 +++ dom/url/tests/url_exceptions_worker.js | 38 ++ dom/url/tests/url_worker.js | 98 +++++ 43 files changed, 3869 insertions(+) create mode 100644 dom/url/URL.cpp create mode 100644 dom/url/URL.h create mode 100644 dom/url/URLMainThread.cpp create mode 100644 dom/url/URLMainThread.h create mode 100644 dom/url/URLSearchParams.cpp create mode 100644 dom/url/URLSearchParams.h create mode 100644 dom/url/URLWorker.cpp create mode 100644 dom/url/URLWorker.h create mode 100644 dom/url/moz.build create mode 100644 dom/url/tests/browser.ini create mode 100644 dom/url/tests/browser_download_after_revoke.js create mode 100644 dom/url/tests/chrome.ini create mode 100644 dom/url/tests/empty.html create mode 100644 dom/url/tests/file_url.jsm create mode 100644 dom/url/tests/file_worker_url.jsm create mode 100644 dom/url/tests/jsm_url_worker.js create mode 100644 dom/url/tests/mochitest.ini create mode 100644 dom/url/tests/protocol_worker.js create mode 100644 dom/url/tests/test_bloburl_location.html create mode 100644 dom/url/tests/test_bug883784.jsm create mode 100644 dom/url/tests/test_bug883784.xhtml create mode 100644 dom/url/tests/test_unknown_url_origin.html create mode 100644 dom/url/tests/test_url.html create mode 100644 dom/url/tests/test_url.xhtml create mode 100644 dom/url/tests/test_urlExceptions.html create mode 100644 dom/url/tests/test_urlSearchParams.html create mode 100644 dom/url/tests/test_urlSearchParams_sorting.html create mode 100644 dom/url/tests/test_urlSearchParams_utf8.html create mode 100644 dom/url/tests/test_url_data.html create mode 100644 dom/url/tests/test_url_empty_port.html create mode 100644 dom/url/tests/test_url_malformedHost.html create mode 100644 dom/url/tests/test_urlutils_stringify.html create mode 100644 dom/url/tests/test_worker_protocol.html create mode 100644 dom/url/tests/test_worker_url.html create mode 100644 dom/url/tests/test_worker_url.xhtml create mode 100644 dom/url/tests/test_worker_urlApi.html create mode 100644 dom/url/tests/test_worker_urlSearchParams.html create mode 100644 dom/url/tests/test_worker_url_exceptions.html create mode 100644 dom/url/tests/urlApi_worker.js create mode 100644 dom/url/tests/urlSearchParams_commons.js create mode 100644 dom/url/tests/urlSearchParams_worker.js create mode 100644 dom/url/tests/url_exceptions_worker.js create mode 100644 dom/url/tests/url_worker.js (limited to 'dom/url') 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 aGivenProto) { + return URL_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed URL::Constructor(const GlobalObject& aGlobal, + const nsAString& aURL, + const Optional& 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::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 baseUri; + nsresult rv = NS_NewURI(getter_AddRefs(baseUri), base); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError(base); + return nullptr; + } + + return Constructor(aParent, aURL, baseUri, aRv); +} + +/* static */ +already_AddRefed 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 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(urlStr); + return nullptr; + } + + return MakeAndAddRef(aParent, std::move(uri)); +} + +already_AddRefed URL::FromURI(GlobalObject& aGlobal, nsIURI* aURI) { + return MakeAndAddRef(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& aBase) { + nsCOMPtr 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 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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), href); + if (NS_FAILED(rv)) { + aRv.ThrowTypeError(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 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 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 +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 aURI) + : mParent(aParent), mURI(std::move(aURI)) { + MOZ_ASSERT(mURI); + } + + // WebIDL methods + nsISupports* GetParentObject() const { return mParent; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + static already_AddRefed Constructor(const GlobalObject& aGlobal, + const nsAString& aURL, + const Optional& aBase, + ErrorResult& aRv); + + static already_AddRefed Constructor(nsISupports* aParent, + const nsAString& aURL, + const nsAString& aBase, + ErrorResult& aRv); + + static already_AddRefed 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& 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 FromURI(GlobalObject&, nsIURI*); + + private: + ~URL() = default; + + void UpdateURLSearchParams(); + + private: + void SetSearchInternal(const nsAString& aSearch); + + void CreateSearchParamsIfNeeded(); + + nsCOMPtr mParent; + RefPtr mSearchParams; + nsCOMPtr 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 global = do_QueryInterface(aGlobal.GetAsSupports()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsCOMPtr 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 global = do_QueryInterface(aGlobal.GetAsSupports()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsCOMPtr principal = + nsContentUtils::ObjectPrincipal(aGlobal.Get()); + + nsAutoCString url; + aRv = BlobURLProtocolHandler::AddDataEntry(&aSource, principal, + global->GetAgentClusterId(), url); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + nsCOMPtr 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 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 +#include +#include +#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 aGivenProto) { + return URLSearchParams_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed URLSearchParams::Constructor( + const GlobalObject& aGlobal, + const USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString& aInit, + ErrorResult& aRv) { + RefPtr 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>& list = + aInit.GetAsUSVStringSequenceSequence(); + for (uint32_t i = 0; i < list.Length(); ++i) { + const Sequence& 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& 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& 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& aValue) { + if (!aValue.WasPassed()) { + return mParams->Has(aName); + } + return mParams->Has(aName, aValue.Value()); +} + +void URLSearchParams::Delete(const nsAString& aName, + const Optional& 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::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + RefPtr 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 +#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 +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 aGivenProto) override; + + static already_AddRefed 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& aRetval); + + void Set(const nsAString& aName, const nsAString& aValue); + + void Append(const nsAString& aName, const nsAString& aValue); + + bool Has(const nsAString& aName, const Optional& aValue); + + void Delete(const nsAString& aName, const Optional& 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 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 mParams; + nsCOMPtr mParent; + RefPtr 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 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 = aBlob.Impl(); + MOZ_ASSERT(blobImpl); + + RefPtr 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 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 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 Constructor( + const GlobalObject& aGlobal, const nsAString& aURL, + const Optional& aBase, ErrorResult& aRv); + + static already_AddRefed 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 @@ + + 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 @@ + + + + + Test for blobURL in location + + + + + + + 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 @@ + + + + + + + +

+ +

+  
+  
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 @@ + + + + + Test for unknwon URL.origin + + + + + + + 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 @@ + + + + + Test URL API + + + + +Mozilla Bug 887364 +Mozilla Bug 991471 +Mozilla Bug 996055 +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + Test for Bug 926890 + + + + +Mozilla Bug 926890 +

+ +
+
+ + + 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 @@ + + + + + + + Test for URLSearchParams + + + + + +Mozilla Bug 887836 +

+ +
+
+ + + 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 @@ + + + + + + Test for URLSearchParams.sort() + + + + + + + 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 @@ + + + + + + + Test for Bug 1032511 + + + + +Mozilla Bug 1032511 +

+ +
+
+foobar +foobar + + + 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 @@ + + + + + Test URL API - data:plain + + + + +Mozilla Bug 1018682 + + + + + 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 @@ + + + + + + + Test for Bug 930450 + + + + +Mozilla Bug 930450 +

+ +
+
+ foobar + + + + 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 @@ + + + + + + + Test for Bug 1020041 + + + + +Mozilla Bug 1020041 +

+ +
+
+ foobar + + + + 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 @@ + + + + + + + Test for Bug 959190 + + + + +Mozilla Bug 959190 +

+ +
+
+ foobar + + + + 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 @@ + + + + + Test for URL protocol setter in workers + + + + +

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test for URL object in workers
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  
+
+  
+    

+ +

+  
+  
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 @@ + + + + + Test for URL API object in workers + + + + +

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test for URLSearchParams object in workers
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test for URL exceptions in workers
+  
+  
+
+
+

+ +

+
+
+
+
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" });
+};
-- 
cgit v1.2.3