LibWeb/HTML: Implement the Origin interface

See: https://github.com/whatwg/html/commit/68909b2
This commit is contained in:
Shannon Booth 2025-12-07 14:19:54 +01:00 committed by Jelle Raaijmakers
parent 2c11e03582
commit f9a996650b
45 changed files with 770 additions and 1 deletions

View file

@ -8,6 +8,7 @@
#include <AK/Weakable.h>
#include <LibJS/Runtime/Object.h>
#include <LibURL/Origin.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
@ -45,6 +46,10 @@ public:
JS::ThrowCompletionOr<bool> is_named_property_exposed_on_object(JS::PropertyKey const&) const;
// https://html.spec.whatwg.org/multipage/browsers.html#extract-an-origin
// Platform objects have an extract an origin operation, which returns null unless otherwise specified.
virtual Optional<URL::Origin> extract_an_origin() const { return {}; }
protected:
explicit PlatformObject(JS::Realm&, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);
explicit PlatformObject(JS::Object& prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);

View file

@ -350,6 +350,7 @@ set(SOURCES
DOM/Utils.cpp
DOM/XMLDocument.cpp
DOMURL/DOMURL.cpp
DOMURL/Origin.cpp
DOMURL/URLSearchParams.cpp
DOMURL/URLSearchParamsIterator.cpp
Dump.cpp

View file

@ -482,4 +482,11 @@ Optional<URL::URL> parse(StringView input, Optional<URL::URL const&> base_url, O
return url.release_value();
}
// FIXME: At time of writing, still open spec MR: https://github.com/whatwg/url/pull/892
Optional<URL::Origin> DOMURL::extract_an_origin() const
{
// Objects implementing the URL interface's extract an origin steps are to return this's URL's origin. [[HTML]]
return m_url.origin();
}
}

View file

@ -81,6 +81,8 @@ public:
Optional<String> const& query() const { return m_url.query(); }
void set_query(Badge<URLSearchParams>, Optional<String> query) { m_url.set_query(move(query)); }
virtual Optional<URL::Origin> extract_an_origin() const override;
private:
DOMURL(JS::Realm&, URL::URL, GC::Ref<URLSearchParams> query);

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibURL/Parser.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/OriginPrototype.h>
#include <LibWeb/DOMURL/Origin.h>
namespace Web::DOMURL {
GC_DEFINE_ALLOCATOR(Origin);
Origin::Origin(JS::Realm& realm, URL::Origin origin)
: PlatformObject(realm)
, m_origin(move(origin))
{
}
Origin::~Origin() = default;
void Origin::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(Origin);
Base::initialize(realm);
}
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-constructor
GC::Ref<Origin> Origin::construct_impl(JS::Realm& realm)
{
// The new Origin() constructor steps are to set this's origin to a unique opaque origin.
return realm.create<Origin>(realm, URL::Origin::create_opaque());
}
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-from
WebIDL::ExceptionOr<GC::Ref<Origin>> Origin::from(JS::VM& vm, JS::Value value)
{
auto& realm = *vm.current_realm();
// 1. If value is a platform object:
if (auto* object = value.as_if<Bindings::PlatformObject>()) {
// 1. Let origin be the result of executing value's extract an origin operation.
auto origin = object->extract_an_origin();
// 2. If origin is not null, then return a new Origin object whose origin is origin.
if (origin.has_value())
return realm.create<Origin>(realm, origin.release_value());
}
// 2. If value is a string:
else if (value.is_string()) {
auto string = value.as_string().utf8_string_view();
// 1. Let parsedURL be the result of basic URL parsing value.
auto parsed_url = URL::Parser::basic_parse(string);
// 2. If parsedURL is not failure, then return a new Origin object whose origin is set to parsedURL's origin.
if (parsed_url.has_value())
return realm.create<Origin>(realm, parsed_url->origin());
}
// 3. Throw a TypeError.
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Value is not a valid Origin"sv };
}
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-opaque
bool Origin::opaque() const
{
// The opaque getter steps are to return true if this's origin is an opaque origin; otherwise false.
return m_origin.is_opaque();
}
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-issameorigin
bool Origin::is_same_origin(Origin const& other) const
{
// The isSameOrigin(other) method steps are to return true if this's origin is same origin with other's origin;
// otherwise false.
return m_origin.is_same_origin(other.m_origin);
}
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-issamesite
bool Origin::is_same_site(Origin const& other) const
{
// The isSameSite(other) method steps are to return true if this's origin is same site with other's origin;
// otherwise false.
return m_origin.is_same_site(other.m_origin);
}
// https://html.spec.whatwg.org/multipage/browsers.html#extract-an-origin
Optional<URL::Origin> Origin::extract_an_origin() const
{
// Objects implementing the Origin interface's extract an origin steps are to return this's origin.
return m_origin;
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibURL/URL.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::DOMURL {
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-interface
class Origin : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Origin, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Origin);
public:
static GC::Ref<Origin> construct_impl(JS::Realm&);
static WebIDL::ExceptionOr<GC::Ref<Origin>> from(JS::VM&, JS::Value);
bool opaque() const;
bool is_same_origin(Origin const&) const;
bool is_same_site(Origin const&) const;
virtual Optional<URL::Origin> extract_an_origin() const override;
virtual ~Origin() override;
private:
Origin(JS::Realm&, URL::Origin);
virtual void initialize(JS::Realm&) override;
// https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-origin
// Origin objects have an associated origin, which holds an origin.
URL::Origin m_origin;
};
}

View file

@ -0,0 +1,12 @@
// https://html.spec.whatwg.org/multipage/browsers.html#dom-origin-interface
[Exposed=*]
interface Origin {
constructor();
static Origin from(any value);
readonly attribute boolean opaque;
boolean isSameOrigin(Origin other);
boolean isSameSite(Origin other);
};

View file

@ -20,6 +20,8 @@ class HTMLAnchorElement final
public:
virtual ~HTMLAnchorElement() override;
virtual Optional<URL::Origin> extract_an_origin() const override { return hyperlink_element_utils_extract_an_origin(); }
String rel() const { return get_attribute_value(HTML::AttributeNames::rel); }
String target() const { return get_attribute_value(HTML::AttributeNames::target); }
String download() const { return get_attribute_value(HTML::AttributeNames::download); }

View file

@ -30,6 +30,8 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual Optional<URL::Origin> extract_an_origin() const final { return hyperlink_element_utils_extract_an_origin(); }
// ^DOM::Element
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
virtual i32 default_tab_index_value() const override;

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Shannon Booth <shannon@ladybird.org>
* Copyright (c) 2024-2025, Shannon Booth <shannon@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -524,4 +524,15 @@ void HTMLHyperlinkElementUtils::follow_the_hyperlink(Optional<String> hyperlink_
MUST(target_navigable->navigate({ .url = url.release_value(), .source_document = hyperlink_element_utils_document(), .referrer_policy = referrer_policy, .user_involvement = user_involvement }));
}
// https://html.spec.whatwg.org/multipage/links.html#api-for-a-and-area-elements:extract-an-origin
Optional<URL::Origin> HTMLHyperlinkElementUtils::hyperlink_element_utils_extract_an_origin() const
{
// 1. If this's url is null, then return null.
if (!m_url.has_value())
return {};
// 2. Return this's url's origin.
return m_url->origin();
}
}

View file

@ -63,6 +63,8 @@ protected:
virtual void hyperlink_element_utils_queue_an_element_task(HTML::Task::Source source, Function<void()> steps) = 0;
Optional<URL::Origin> hyperlink_element_utils_extract_an_origin() const;
void set_the_url();
void follow_the_hyperlink(Optional<String> hyperlink_suffix, UserNavigationInvolvement = UserNavigationInvolvement::None);

View file

@ -102,4 +102,11 @@ void MessageEvent::init_message_event(String const& type, bool bubbles, bool can
}
}
// https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interface:extract-an-origin
Optional<URL::Origin> MessageEvent::extract_an_origin() const
{
// Objects implementing the MessageEvent interface's extract an origin steps are to return this's relevant settings object's origin.
return relevant_settings_object(*this).origin();
}
}

View file

@ -41,6 +41,8 @@ public:
GC::Ref<JS::Object> ports() const;
Variant<GC::Root<WindowProxy>, GC::Root<MessagePort>, Empty> source() const;
virtual Optional<URL::Origin> extract_an_origin() const override;
void init_message_event(String const& type, bool bubbles, bool cancelable, JS::Value data, String const& origin, String const& last_event_id, Optional<MessageEventSource> source, Vector<GC::Root<MessagePort>> const& ports);
private:

View file

@ -100,6 +100,8 @@ public:
// ^JS::Object
virtual JS::ThrowCompletionOr<bool> internal_set_prototype_of(JS::Object* prototype) override;
virtual Optional<URL::Origin> extract_an_origin() const override { return window_or_worker_global_scope_extract_an_origin(); }
Page& page();
Page const& page() const;

View file

@ -1277,4 +1277,17 @@ GC::Ref<TrustedTypes::TrustedTypePolicyFactory> WindowOrWorkerGlobalScopeMixin::
return *m_trusted_type_policy_factory;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope-mixin:extract-an-origin
Optional<URL::Origin> WindowOrWorkerGlobalScopeMixin::window_or_worker_global_scope_extract_an_origin() const
{
auto relevant_origin = relevant_settings_object(this_impl()).origin();
// 1. If this's relevant settings object's origin is not same origin-domain with the entry settings object's origin, then return null.
if (!relevant_origin.is_same_origin_domain(entry_settings_object().origin()))
return {};
// 2. Return this's relevant settings object's origin.
return relevant_origin;
}
}

View file

@ -106,6 +106,8 @@ public:
[[nodiscard]] GC::Ref<TrustedTypes::TrustedTypePolicyFactory> trusted_types();
Optional<URL::Origin> window_or_worker_global_scope_extract_an_origin() const;
protected:
void initialize(JS::Realm&);
void visit_edges(JS::Cell::Visitor&);

View file

@ -66,6 +66,8 @@ public:
// https://html.spec.whatwg.org/multipage/workers.html#dom-workerglobalscope-self
GC::Ref<WorkerGlobalScope const> self() const { return *this; }
virtual Optional<URL::Origin> extract_an_origin() const override { return window_or_worker_global_scope_extract_an_origin(); }
GC::Ref<WorkerLocation> location() const;
GC::Ref<WorkerNavigator> navigator() const;
WebIDL::ExceptionOr<void> import_scripts(Vector<String> const& urls, PerformTheFetchHook = nullptr);

View file

@ -121,6 +121,7 @@ libweb_js_bindings(DOM/Text)
libweb_js_bindings(DOM/TreeWalker)
libweb_js_bindings(DOM/XMLDocument)
libweb_js_bindings(DOMURL/DOMURL)
libweb_js_bindings(DOMURL/Origin)
libweb_js_bindings(DOMURL/URLSearchParams ITERABLE)
libweb_js_bindings(Encoding/TextDecoder)
libweb_js_bindings(Encoding/TextEncoder)

View file

@ -105,6 +105,7 @@ static bool is_platform_object(Type const& type)
"Node"sv,
"OffscreenCanvas"sv,
"OffscreenCanvasRenderingContext2D"sv,
"Origin"sv,
"PasswordCredential"sv,
"Path2D"sv,
"PerformanceEntry"sv,

View file

@ -323,6 +323,7 @@ OfflineAudioContext
OffscreenCanvas
OffscreenCanvasRenderingContext2D
Option
Origin
OscillatorNode
PageTransitionEvent
PannerNode

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass Comparison of opaque origins.
Pass Comparison of tuple origins.
Pass Comparisons are schemeful.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass Origin.from(globalThis) is a tuple origin.

View file

@ -0,0 +1,59 @@
Harness status: OK
Found 54 tests
54 Pass
Pass Origin.from(<a>) throws.
Pass Origin.from(<area>) throws.
Pass Origin.from(<a href="about:blank">) returns an opaque origin.
Pass Origin.from(<area href="about:blank">) returns an opaque origin.
Pass Origin.from(<a href="data:text/plain,opaque">) returns an opaque origin.
Pass Origin.from(<area href="data:text/plain,opaque">) returns an opaque origin.
Pass Origin.from(<a href="weird-protocol:whatever">) returns an opaque origin.
Pass Origin.from(<area href="weird-protocol:whatever">) returns an opaque origin.
Pass Origin.from(<a href="weird-hierarchical-protocol://host/path?etc">) returns an opaque origin.
Pass Origin.from(<area href="weird-hierarchical-protocol://host/path?etc">) returns an opaque origin.
Pass Origin.from(<a href="blob:weird-protocol:whatever">) returns an opaque origin.
Pass Origin.from(<area href="blob:weird-protocol:whatever">) returns an opaque origin.
Pass Origin.from(<a href="blob:weird-hierarchical-protocol://host/path?etc">) returns an opaque origin.
Pass Origin.from(<area href="blob:weird-hierarchical-protocol://host/path?etc">) returns an opaque origin.
Pass Origin.from(<a href="http://site.example">) returns a tuple origin.
Pass Origin.from(<area href="http://site.example">) returns a tuple origin.
Pass Origin.from(<a href="https://site.example">) returns a tuple origin.
Pass Origin.from(<area href="https://site.example">) returns a tuple origin.
Pass Origin.from(<a href="https://site.example:123">) returns a tuple origin.
Pass Origin.from(<area href="https://site.example:123">) returns a tuple origin.
Pass Origin.from(<a href="http://sub.site.example">) returns a tuple origin.
Pass Origin.from(<area href="http://sub.site.example">) returns a tuple origin.
Pass Origin.from(<a href="https://sub.site.example">) returns a tuple origin.
Pass Origin.from(<area href="https://sub.site.example">) returns a tuple origin.
Pass Origin.from(<a href="https://sub.site.example:123">) returns a tuple origin.
Pass Origin.from(<area href="https://sub.site.example:123">) returns a tuple origin.
Pass Origin.from(<a href="https://xn--mlauted-m2a.example">) returns a tuple origin.
Pass Origin.from(<area href="https://xn--mlauted-m2a.example">) returns a tuple origin.
Pass Origin.from(<a href="ftp://ftp.example">) returns a tuple origin.
Pass Origin.from(<area href="ftp://ftp.example">) returns a tuple origin.
Pass Origin.from(<a href="ws://ws.example">) returns a tuple origin.
Pass Origin.from(<area href="ws://ws.example">) returns a tuple origin.
Pass Origin.from(<a href="wss://wss.example">) returns a tuple origin.
Pass Origin.from(<area href="wss://wss.example">) returns a tuple origin.
Pass Origin.from(<a href="https://trailing.slash/">) returns a tuple origin.
Pass Origin.from(<area href="https://trailing.slash/">) returns a tuple origin.
Pass Origin.from(<a href="https://user:pass@site.example">) returns a tuple origin.
Pass Origin.from(<area href="https://user:pass@site.example">) returns a tuple origin.
Pass Origin.from(<a href="https://has.a.port:1234/and/path">) returns a tuple origin.
Pass Origin.from(<area href="https://has.a.port:1234/and/path">) returns a tuple origin.
Pass Origin.from(<a href="https://ümlauted.example">) returns a tuple origin.
Pass Origin.from(<area href="https://ümlauted.example">) returns a tuple origin.
Pass Origin.from(<a href="file:///path/to/a/file.txt">) returns a tuple origin.
Pass Origin.from(<area href="file:///path/to/a/file.txt">) returns a tuple origin.
Pass Origin.from(<a href="blob:https://example.com/some-guid">) returns a tuple origin.
Pass Origin.from(<area href="blob:https://example.com/some-guid">) returns a tuple origin.
Pass Origin.from(<a href="ftp://example.com/">) returns a tuple origin.
Pass Origin.from(<area href="ftp://example.com/">) returns a tuple origin.
Pass Origin.from(<a href="https://example.com/path?query#fragment">) returns a tuple origin.
Pass Origin.from(<area href="https://example.com/path?query#fragment">) returns a tuple origin.
Pass Origin.from(<a href="https://127.0.0.1/">) returns a tuple origin.
Pass Origin.from(<area href="https://127.0.0.1/">) returns a tuple origin.
Pass Origin.from(<a href="https://[::1]/">) returns a tuple origin.
Pass Origin.from(<area href="https://[::1]/">) returns a tuple origin.

View file

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass Origin.from(window.location) throws.
Pass Origin.from(Location) throws for cross-origin frames.

View file

@ -0,0 +1,31 @@
Harness status: OK
Found 26 tests
26 Pass
Pass Origin.from(Origin.from("about:blank")) is an opaque origin.
Pass Origin.from(Origin.from("data:text/plain,opaque")) is an opaque origin.
Pass Origin.from(Origin.from("weird-protocol:whatever")) is an opaque origin.
Pass Origin.from(Origin.from("weird-hierarchical-protocol://host/path?etc")) is an opaque origin.
Pass Origin.from(Origin.from("blob:weird-protocol:whatever")) is an opaque origin.
Pass Origin.from(Origin.from("blob:weird-hierarchical-protocol://host/path?etc")) is an opaque origin.
Pass Origin.from(Origin.from("http://site.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://site.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://site.example:123")) is an tuple origin.
Pass Origin.from(Origin.from("http://sub.site.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://sub.site.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://sub.site.example:123")) is an tuple origin.
Pass Origin.from(Origin.from("https://xn--mlauted-m2a.example")) is an tuple origin.
Pass Origin.from(Origin.from("ftp://ftp.example")) is an tuple origin.
Pass Origin.from(Origin.from("ws://ws.example")) is an tuple origin.
Pass Origin.from(Origin.from("wss://wss.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://trailing.slash/")) is an tuple origin.
Pass Origin.from(Origin.from("https://user:pass@site.example")) is an tuple origin.
Pass Origin.from(Origin.from("https://has.a.port:1234/and/path")) is an tuple origin.
Pass Origin.from(Origin.from("https://ümlauted.example")) is an tuple origin.
Pass Origin.from(Origin.from("file:///path/to/a/file.txt")) is an tuple origin.
Pass Origin.from(Origin.from("blob:https://example.com/some-guid")) is an tuple origin.
Pass Origin.from(Origin.from("ftp://example.com/")) is an tuple origin.
Pass Origin.from(Origin.from("https://example.com/path?query#fragment")) is an tuple origin.
Pass Origin.from(Origin.from("https://127.0.0.1/")) is an tuple origin.
Pass Origin.from(Origin.from("https://[::1]/")) is an tuple origin.

View file

@ -0,0 +1,33 @@
Harness status: OK
Found 28 tests
28 Pass
Pass Origin.from("") throws a TypeError.
Pass Origin.from("not-valid") throws a TypeError.
Pass Origin.from("about:blank") is an opaque origin.
Pass Origin.from("data:text/plain,opaque") is an opaque origin.
Pass Origin.from("weird-protocol:whatever") is an opaque origin.
Pass Origin.from("weird-hierarchical-protocol://host/path?etc") is an opaque origin.
Pass Origin.from("blob:weird-protocol:whatever") is an opaque origin.
Pass Origin.from("blob:weird-hierarchical-protocol://host/path?etc") is an opaque origin.
Pass Origin.from("http://site.example") is an opaque origin.
Pass Origin.from("https://site.example") is an opaque origin.
Pass Origin.from("https://site.example:123") is an opaque origin.
Pass Origin.from("http://sub.site.example") is an opaque origin.
Pass Origin.from("https://sub.site.example") is an opaque origin.
Pass Origin.from("https://sub.site.example:123") is an opaque origin.
Pass Origin.from("https://xn--mlauted-m2a.example") is an opaque origin.
Pass Origin.from("ftp://ftp.example") is an opaque origin.
Pass Origin.from("ws://ws.example") is an opaque origin.
Pass Origin.from("wss://wss.example") is an opaque origin.
Pass Origin.from("https://trailing.slash/") is an opaque origin.
Pass Origin.from("https://user:pass@site.example") is an opaque origin.
Pass Origin.from("https://has.a.port:1234/and/path") is an opaque origin.
Pass Origin.from("https://ümlauted.example") is an opaque origin.
Pass Origin.from("file:///path/to/a/file.txt") is an opaque origin.
Pass Origin.from("blob:https://example.com/some-guid") is an opaque origin.
Pass Origin.from("ftp://example.com/") is an opaque origin.
Pass Origin.from("https://example.com/path?query#fragment") is an opaque origin.
Pass Origin.from("https://127.0.0.1/") is an opaque origin.
Pass Origin.from("https://[::1]/") is an opaque origin.

View file

@ -0,0 +1,33 @@
Harness status: OK
Found 28 tests
28 Pass
Pass Origin.from("") throws a TypeError.
Pass Origin.from("not-valid") throws a TypeError.
Pass Origin.from("about:blank") is an opaque origin.
Pass Origin.from("data:text/plain,opaque") is an opaque origin.
Pass Origin.from("weird-protocol:whatever") is an opaque origin.
Pass Origin.from("weird-hierarchical-protocol://host/path?etc") is an opaque origin.
Pass Origin.from("blob:weird-protocol:whatever") is an opaque origin.
Pass Origin.from("blob:weird-hierarchical-protocol://host/path?etc") is an opaque origin.
Pass Origin.from("http://site.example") is an opaque origin.
Pass Origin.from("https://site.example") is an opaque origin.
Pass Origin.from("https://site.example:123") is an opaque origin.
Pass Origin.from("http://sub.site.example") is an opaque origin.
Pass Origin.from("https://sub.site.example") is an opaque origin.
Pass Origin.from("https://sub.site.example:123") is an opaque origin.
Pass Origin.from("https://xn--mlauted-m2a.example") is an opaque origin.
Pass Origin.from("ftp://ftp.example") is an opaque origin.
Pass Origin.from("ws://ws.example") is an opaque origin.
Pass Origin.from("wss://wss.example") is an opaque origin.
Pass Origin.from("https://trailing.slash/") is an opaque origin.
Pass Origin.from("https://user:pass@site.example") is an opaque origin.
Pass Origin.from("https://has.a.port:1234/and/path") is an opaque origin.
Pass Origin.from("https://ümlauted.example") is an opaque origin.
Pass Origin.from("file:///path/to/a/file.txt") is an opaque origin.
Pass Origin.from("blob:https://example.com/some-guid") is an opaque origin.
Pass Origin.from("ftp://example.com/") is an opaque origin.
Pass Origin.from("https://example.com/path?query#fragment") is an opaque origin.
Pass Origin.from("https://127.0.0.1/") is an opaque origin.
Pass Origin.from("https://[::1]/") is an opaque origin.

View file

@ -0,0 +1,14 @@
Harness status: OK
Found 9 tests
9 Pass
Pass Origin.from(null) throws a TypeError.
Pass Origin.from(undefined) throws a TypeError.
Pass Origin.from(1) throws a TypeError.
Pass Origin.from(1.1) throws a TypeError.
Pass Origin.from(true) throws a TypeError.
Pass Origin.from([object Object]) throws a TypeError.
Pass Origin.from(function Object() { [native code] }) throws a TypeError.
Pass Origin.from(function Origin() { [native code] }) throws a TypeError.
Pass Origin.from(function from() { [native code] }) throws a TypeError.

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin` comparison</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-comparison.any.js"></script>

View file

@ -0,0 +1,50 @@
// META: title=`Origin` comparison
test(t => {
const opaqueA = new Origin();
const opaqueB = new Origin();
assert_true(opaqueA.opaque);
assert_true(opaqueB.opaque);
assert_true(opaqueA.isSameOrigin(opaqueA), "Opaque origin should be same-origin with itself.");
assert_true(opaqueA.isSameSite(opaqueA), "Opaque origin should be same-site with itself.");
assert_false(opaqueA.isSameOrigin(opaqueB), "Opaque origin should not be same-origin with another opaque origin.");
assert_false(opaqueA.isSameSite(opaqueB), "Opaque origin should not be same-site with another opaque origin.");
}, "Comparison of opaque origins.");
test(t => {
const a = Origin.from("https://a.example");
const a_a = Origin.from("https://a.a.example");
const b_a = Origin.from("https://b.a.example");
const b = Origin.from("https://b.example");
const b_b = Origin.from("https://b.b.example");
assert_true(a.isSameOrigin(a), "Origin should be same-origin with itself.");
assert_false(a.isSameOrigin(a_a), "Origins with different subdomains should not be same-origin.");
assert_false(a.isSameOrigin(b_a), "Origins with different subdomains should not be same-origin.");
assert_false(a.isSameOrigin(b), "Origins with different domains should not be same-origin.");
assert_false(a.isSameOrigin(b_b), "Origins with different domains should not be same-origin.");
assert_true(a.isSameSite(a), "Origin should be same-site with itself.");
assert_true(a.isSameSite(a_a), "Origins with same registrable domain should be same-site.");
assert_true(a.isSameSite(b_a), "Origins with same registrable domain should be same-site.");
assert_false(a.isSameSite(b), "Origins with different registrable domains should not be same-site.");
assert_false(a.isSameSite(b_b), "Origins with different registrable domains should not be same-site.");
assert_true(a_a.isSameSite(a), "Origins with same registrable domain should be same-site.");
assert_true(a_a.isSameSite(a_a), "Origin should be same-site with itself.");
assert_true(a_a.isSameSite(b_a), "Origins with same registrable domain should be same-site.");
assert_false(a_a.isSameSite(b), "Origins with different registrable domains should not be same-site.");
assert_false(a_a.isSameSite(b_b), "Origins with different registrable domains should not be same-site.");
}, "Comparison of tuple origins.");
test(t => {
const http = Origin.from("http://a.example");
const https = Origin.from("https://a.example");
assert_false(http.isSameOrigin(https), "http is not same-site with https");
assert_false(https.isSameOrigin(http), "https is not same-site with http");
assert_false(http.isSameSite(https), "http is not same-site with https");
assert_false(https.isSameSite(http), "https is not same-site with http");
}, "Comparisons are schemeful.");

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(WindowOrWorkerGlobalScope)`</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="../../../../common/get-host-info.sub.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-global.any.js"></script>

View file

@ -0,0 +1,10 @@
// META: title=`Origin.from(WindowOrWorkerGlobalScope)`
// META: global=window,worker
// META: script=/common/get-host-info.sub.js
test(t => {
const origin = Origin.from(globalThis);
assert_true(!!origin);
assert_false(origin.opaque, "Origin should not be opaque.");
assert_true(origin.isSameOrigin(Origin.from(get_host_info().ORIGIN)));
}, `Origin.from(globalThis) is a tuple origin.`);

View file

@ -0,0 +1,8 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(HTMLHyperlinkElementUtils)`</title>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/serializations.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-htmlhyperlinkelementutils.window.js"></script>

View file

@ -0,0 +1,54 @@
// META: title=`Origin.from(HTMLHyperlinkElementUtils)`
// META: script=resources/serializations.js
test(t => {
const invalid = document.createElement("a");
assert_throws_js(TypeError, _ => Origin.from(invalid));
}, `Origin.from(<a>) throws.`);
test(t => {
const invalid = document.createElement("area");
assert_throws_js(TypeError, _ => Origin.from(invalid));
}, `Origin.from(<area>) throws.`);
for (const opaque of urls.opaque) {
// <a>
test(t => {
const a = document.createElement("a");
a.href = opaque;
const origin = Origin.from(a);
assert_true(!!origin);
assert_true(origin.opaque);
}, `Origin.from(<a href="${opaque}">) returns an opaque origin.`);
// <area>
test(t => {
const area = document.createElement("area");
area.href = opaque;
const origin = Origin.from(area);
assert_true(!!origin);
assert_true(origin.opaque);
}, `Origin.from(<area href="${opaque}">) returns an opaque origin.`);
}
for (const tuple of urls.tuple) {
// <a>
test(t => {
const a = document.createElement("a");
a.href = tuple;
const origin = Origin.from(a);
assert_true(!!origin);
assert_false(origin.opaque);
}, `Origin.from(<a href="${tuple}">) returns a tuple origin.`);
// <area>
test(t => {
const area = document.createElement("area");
area.href = tuple;
const origin = Origin.from(area);
assert_true(!!origin);
assert_false(origin.opaque);
}, `Origin.from(<area href="${tuple}">) returns a tuple origin.`);
}

View file

@ -0,0 +1,8 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(Location)`</title>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="../../../../common/get-host-info.sub.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-location.window.js"></script>

View file

@ -0,0 +1,15 @@
// META: title=`Origin.from(Location)`
// META: script=/common/get-host-info.sub.js
test(t => {
assert_throws_js(TypeError, _ => Origin.from(window.location));
}, `Origin.from(window.location) throws.`);
async_test(t => {
const el = document.createElement('iframe');
el.src = get_host_info().REMOTE_ORIGIN + "/common/blank.html";
el.onload = t.step_func_done(_ => {
assert_throws_js(TypeError, _ => Origin.from(el.contentWindow.location));
});
document.body.appendChild(el);
}, `Origin.from(Location) throws for cross-origin frames.`);

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(URL)`</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/serializations.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-origin.any.js"></script>

View file

@ -0,0 +1,22 @@
// META: title=`Origin.from(URL)`
// META: script=resources/serializations.js
for (const opaque of urls.opaque) {
test(t => {
const originFromString = Origin.from(opaque);
const origin = Origin.from(originFromString);
assert_true(!!origin);
assert_true(origin.opaque, "Origin should be opaque.");
assert_true(origin.isSameOrigin(originFromString));
}, `Origin.from(Origin.from(${JSON.stringify(opaque)})) is an opaque origin.`);
}
for (const tuple of urls.tuple) {
test(t => {
const originFromString = Origin.from(tuple);
const origin = Origin.from(originFromString);
assert_true(!!origin);
assert_false(origin.opaque, "Origin should be opaque.");
assert_true(origin.isSameOrigin(originFromString));
}, `Origin.from(Origin.from(${JSON.stringify(tuple)})) is an tuple origin.`);
}

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(String)`</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/serializations.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-string.any.js"></script>

View file

@ -0,0 +1,24 @@
// META: title=`Origin.from(String)`
// META: script=resources/serializations.js
for (const invalid of urls.invalid) {
test(t => {
assert_throws_js(TypeError, _ => Origin.from(invalid));
}, `Origin.from(${JSON.stringify(invalid)}) throws a TypeError.`);
}
for (const opaque of urls.opaque) {
test(t => {
const origin = Origin.from(opaque);
assert_true(!!origin);
assert_true(origin.opaque, "Origin should be opaque.");
}, `Origin.from(${JSON.stringify(opaque)}) is an opaque origin.`);
}
for (const tuple of urls.tuple) {
test(t => {
const origin = Origin.from(tuple);
assert_true(!!origin);
assert_false(origin.opaque, "Origin should not be opaque.");
}, `Origin.from(${JSON.stringify(tuple)}) is an opaque origin.`);
}

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from(URL)`</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/serializations.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from-url.any.js"></script>

View file

@ -0,0 +1,24 @@
// META: title=`Origin.from(URL)`
// META: script=resources/serializations.js
for (const invalid of urls.invalid) {
test(t => {
assert_throws_js(TypeError, _ => Origin.from(new URL(invalid)));
}, `Origin.from(${JSON.stringify(invalid)}) throws a TypeError.`);
}
for (const opaque of urls.opaque) {
test(t => {
const origin = Origin.from(new URL(opaque));
assert_true(!!origin);
assert_true(origin.opaque, "Origin should be opaque.");
}, `Origin.from(${JSON.stringify(opaque)}) is an opaque origin.`);
}
for (const tuple of urls.tuple) {
test(t => {
const origin = Origin.from(new URL(tuple));
assert_true(!!origin);
assert_false(origin.opaque, "Origin should not be opaque.");
}, `Origin.from(${JSON.stringify(tuple)}) is an opaque origin.`);
}

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>`Origin.from()`</title>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/serializations.js"></script>
<div id=log></div>
<script src="../../../../html/browsers/origin/api/origin-from.any.js"></script>

View file

@ -0,0 +1,25 @@
// META: title=`Origin.from()`
// META: script=resources/serializations.js
//
// Invalid Inputs: `null`, `undefined`, invalid URL strings, random objects.
//
const invalidInputs = [
null,
undefined,
1,
1.1,
true,
{},
Object,
Origin,
Origin.from,
];
for (const invalid of invalidInputs) {
test(t => {
assert_throws_js(TypeError, _ => Origin.from(invalid));
}, `Origin.from(${invalid}) throws a TypeError.`);
}
// Specific object types are tested in `origin-from-*.js` in this directory.

View file

@ -0,0 +1,36 @@
const urls = {
invalid: [
"",
"not-valid",
],
opaque: [
"about:blank",
"data:text/plain,opaque",
"weird-protocol:whatever",
"weird-hierarchical-protocol://host/path?etc",
"blob:weird-protocol:whatever",
"blob:weird-hierarchical-protocol://host/path?etc",
],
tuple: [
"http://site.example",
"https://site.example",
"https://site.example:123",
"http://sub.site.example",
"https://sub.site.example",
"https://sub.site.example:123",
"https://xn--mlauted-m2a.example",
"ftp://ftp.example",
"ws://ws.example",
"wss://wss.example",
"https://trailing.slash/",
"https://user:pass@site.example",
"https://has.a.port:1234/and/path",
"https://ümlauted.example",
"file:///path/to/a/file.txt",
"blob:https://example.com/some-guid",
"ftp://example.com/",
"https://example.com/path?query#fragment",
"https://127.0.0.1/",
"https://[::1]/",
],
};