diff options
Diffstat (limited to 'dom/url')
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" }); +}; |