diff --git a/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt b/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt new file mode 100644 index 0000000000..a3890c7d9f --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt @@ -0,0 +1,100 @@ + originError instanceof DOMException: true +originError.name: SyntaxError +originError.message: Invalid URL for targetOrigin: 'aaaa' +originError.constructor === window.DOMException: true +originParsedBeforeSerializeError instanceof DOMException: true +originParsedBeforeSerializeError.name: SyntaxError +originParsedBeforeSerializeError.message: Invalid URL for targetOrigin: 'aaaa' +originParsedBeforeSerializeError.constructor === window.DOMException: true +serializeError instanceof DOMException: true +serializeError.name: DataCloneError +serializeError.message: Unsupported type +serializeError.constructor === window.DOMException: true +originIframeError instanceof DOMException: false +originIframeError instanceof iframe.contentWindow.DOMException: true +originIframeError.name: SyntaxError +originIframeError.message: Invalid URL for targetOrigin: 'aaaa' +originIframeError.constructor === DOMException: false +originIframeError.constructor === iframe.contentWindow.DOMException: true +originParsedBeforeSerializeIframeError instanceof DOMException: false +originParsedBeforeSerializeIframeError instanceof iframe.contentWindow.DOMException: true +originParsedBeforeSerializeIframeError.name: SyntaxError +originParsedBeforeSerializeIframeError.message: Invalid URL for targetOrigin: 'aaaa' +originParsedBeforeSerializeIframeError.constructor === DOMException: false +originParsedBeforeSerializeIframeError.constructor === iframe.contentWindow.DOMException: true +serializeIframeError instanceof DOMException: false +serializeIframeError instanceof iframe.contentWindow.DOMException: true +serializeIframeError.name: DataCloneError +serializeIframeError.message: Unsupported type +serializeIframeError.constructor === DOMException: false +serializeIframeError.constructor === iframe.contentWindow.DOMException: true +Message 1 data: undefined +Message 1 origin: file:// +Message 1 lastEventId: +Message 1 source: [object Window] +Message 1 source === window: true +Message 1 source === iframe.contentWindow: false +Message 1 source === blobIframe.contentWindow: false +Message 2 data: null +Message 2 origin: file:// +Message 2 lastEventId: +Message 2 source: [object Window] +Message 2 source === window: true +Message 2 source === iframe.contentWindow: false +Message 2 source === blobIframe.contentWindow: false +Message 3 data: true +Message 3 origin: file:// +Message 3 lastEventId: +Message 3 source: [object Window] +Message 3 source === window: true +Message 3 source === iframe.contentWindow: false +Message 3 source === blobIframe.contentWindow: false +Message 4 data: false +Message 4 origin: file:// +Message 4 lastEventId: +Message 4 source: [object Window] +Message 4 source === window: true +Message 4 source === iframe.contentWindow: false +Message 4 source === blobIframe.contentWindow: false +Message 5 data: 123 +Message 5 origin: file:// +Message 5 lastEventId: +Message 5 source: [object Window] +Message 5 source === window: true +Message 5 source === iframe.contentWindow: false +Message 5 source === blobIframe.contentWindow: false +Message 6 data: 123.456 +Message 6 origin: file:// +Message 6 lastEventId: +Message 6 source: [object Window] +Message 6 source === window: true +Message 6 source === iframe.contentWindow: false +Message 6 source === blobIframe.contentWindow: false +Message 7 data: 9007199254740991 +Message 7 origin: file:// +Message 7 lastEventId: +Message 7 source: [object Window] +Message 7 source === window: true +Message 7 source === iframe.contentWindow: false +Message 7 source === blobIframe.contentWindow: false +Message 8 data: This is a string +Message 8 origin: file:// +Message 8 lastEventId: +Message 8 source: [object Window] +Message 8 source === window: true +Message 8 source === iframe.contentWindow: false +Message 8 source === blobIframe.contentWindow: false +Message 9 data: I am from another ~planet~ iframe +Message 9 origin: file:// +Message 9 lastEventId: +Message 9 source: [object Window] +Message 9 source === window: false +Message 9 source === iframe.contentWindow: true +Message 9 source === blobIframe.contentWindow: false +Message 10 data: All done :^) +Message 10 origin: file:// +Message 10 lastEventId: +Message 10 source: [object Window] +Message 10 source === window: false +Message 10 source === iframe.contentWindow: false +Message 10 source === blobIframe.contentWindow: true \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/HTML/Window-postMessage.html b/Tests/LibWeb/Text/input/HTML/Window-postMessage.html new file mode 100644 index 0000000000..fa2ee130f7 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/Window-postMessage.html @@ -0,0 +1,137 @@ + + + + + diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index d7ba5e6101..b27ac83dea 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -997,17 +999,115 @@ Optional Window::prompt(Optional const& message, Optional Window::window_post_message_steps(JS::Value message, WindowPostMessageOptions const& options) { - // FIXME: This is an ad-hoc hack implementation instead, since we don't currently - // have serialization and deserialization of messages. - queue_global_task(Task::Source::PostedMessage, *this, [this, message] { - MessageEventInit event_init {}; - event_init.data = message; - event_init.origin = ""_string; - dispatch_event(MessageEvent::create(realm(), EventNames::message, event_init)); + // 1. Let targetRealm be targetWindow's realm. + auto& target_realm = this->realm(); + + // 2. Let incumbentSettings be the incumbent settings object. + auto& incumbent_settings = incumbent_settings_object(); + + // 3. Let targetOrigin be options["targetOrigin"]. + Variant target_origin = options.target_origin; + + // 4. If targetOrigin is a single U+002F SOLIDUS character (/), then set targetOrigin to incumbentSettings's origin. + if (options.target_origin == "/"sv) { + target_origin = incumbent_settings.origin(); + } + // 5. Otherwise, if targetOrigin is not a single U+002A ASTERISK character (*), then: + else if (options.target_origin != "*"sv) { + // 1. Let parsedURL be the result of running the URL parser on targetOrigin. + auto parsed_url = URL::parse(options.target_origin); + + // 2. If parsedURL is failure, then throw a "SyntaxError" DOMException. + if (!parsed_url.is_valid()) + return WebIDL::SyntaxError::create(target_realm, MUST(String::formatted("Invalid URL for targetOrigin: '{}'", options.target_origin))); + + // 3. Set targetOrigin to parsedURL's origin. + target_origin = URL::url_origin(parsed_url); + } + + // 6. Let transfer be options["transfer"]. + // FIXME: This is currently unused. + + // 7. Let serializeWithTransferResult be StructuredSerializeWithTransfer(message, transfer). Rethrow any exceptions. + // FIXME: Use StructuredSerializeWithTransfer instead of StructuredSerialize + auto serialize_with_transfer_result = TRY(structured_serialize(target_realm.vm(), message)); + + // 8. Queue a global task on the posted message task source given targetWindow to run the following steps: + queue_global_task(Task::Source::PostedMessage, *this, [this, serialize_with_transfer_result = move(serialize_with_transfer_result), target_origin = move(target_origin), &incumbent_settings, &target_realm]() { + // 1. If the targetOrigin argument is not a single literal U+002A ASTERISK character (*) and targetWindow's + // associated Document's origin is not same origin with targetOrigin, then return. + // NOTE: Due to step 4 and 5 above, the only time it's not '*' is if target_origin contains an Origin. + if (!target_origin.has()) { + auto const& actual_target_origin = target_origin.get(); + if (!document()->origin().is_same_origin(actual_target_origin)) + return; + } + + // 2. Let origin be the serialization of incumbentSettings's origin. + auto origin = incumbent_settings.origin().serialize(); + + // 3. Let source be the WindowProxy object corresponding to incumbentSettings's global object (a Window object). + auto& source = verify_cast(incumbent_settings.realm().global_environment().global_this_value()); + + // 4. Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm). + // FIXME: Use StructuredDeserializeWithTransfer instead of StructuredDeserialize + // FIXME: Don't use a temporary execution context here. + auto& settings_object = Bindings::host_defined_environment_settings_object(target_realm); + auto temporary_execution_context = TemporaryExecutionContext { settings_object }; + auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional {}); + + // If this throws an exception, catch it, fire an event named messageerror at targetWindow, using MessageEvent, + // with the origin attribute initialized to origin and the source attribute initialized to source, and then return. + if (deserialize_record_or_error.is_exception()) { + MessageEventInit message_event_init {}; + message_event_init.origin = MUST(String::from_deprecated_string(origin)); + message_event_init.source = JS::make_handle(source); + + auto message_error_event = MessageEvent::create(target_realm, EventNames::messageerror, message_event_init); + dispatch_event(message_error_event); + return; + } + + // 5. Let messageClone be deserializeRecord.[[Deserialized]]. + // FIXME: Get this from deserializeRecord.[[Deserialized]] once it uses StructuredDeserializeWithTransfer instead of StructuredDeserialize. + auto message_clone = deserialize_record_or_error.release_value(); + + // FIXME: 6. Let newPorts be a new frozen array consisting of all MessagePort objects in deserializeRecord.[[TransferredValues]], + // if any, maintaining their relative order. + + // 7. Fire an event named message at targetWindow, using MessageEvent, with the origin attribute initialized to origin, + // the source attribute initialized to source, the data attribute initialized to messageClone, and the ports attribute + // initialized to newPorts. + // FIXME: Set the ports attribute to newPorts. + MessageEventInit message_event_init {}; + message_event_init.origin = MUST(String::from_deprecated_string(origin)); + message_event_init.source = JS::make_handle(source); + message_event_init.data = message_clone; + + auto message_event = MessageEvent::create(target_realm, EventNames::message, message_event_init); + dispatch_event(message_event); }); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-window-postmessage-options +WebIDL::ExceptionOr Window::post_message(JS::Value message, WindowPostMessageOptions const& options) +{ + // The Window interface's postMessage(message, options) method steps are to run the window post message steps given + // this, message, and options. + return window_post_message_steps(message, options); +} + +// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-window-postmessage +WebIDL::ExceptionOr Window::post_message(JS::Value message, String const& target_origin, Vector> const& transfer) +{ + // The Window interface's postMessage(message, targetOrigin, transfer) method steps are to run the window post message + // steps given this, message, and «[ "targetOrigin" → targetOrigin, "transfer" → transfer ]». + return window_post_message_steps(message, WindowPostMessageOptions { { .transfer = transfer }, target_origin }); } // https://dom.spec.whatwg.org/#dom-window-event diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index 9eeaa87335..1305165f37 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -38,6 +38,11 @@ struct ScrollToOptions : public ScrollOptions { Optional top; }; +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#windowpostmessageoptions +struct WindowPostMessageOptions : public StructuredSerializeOptions { + String target_origin { "/"_string }; +}; + class Window final : public DOM::EventTarget , public GlobalEventHandlers @@ -146,7 +151,8 @@ public: bool confirm(Optional const& message); Optional prompt(Optional const& message, Optional const& default_); - void post_message(JS::Value message, String const&); + WebIDL::ExceptionOr post_message(JS::Value message, String const&, Vector> const&); + WebIDL::ExceptionOr post_message(JS::Value message, WindowPostMessageOptions const&); Variant, JS::Value> event() const; @@ -215,6 +221,8 @@ private: }; NamedObjects named_objects(StringView name); + WebIDL::ExceptionOr window_post_message_steps(JS::Value, WindowPostMessageOptions const&); + // https://html.spec.whatwg.org/multipage/window-object.html#concept-document-window JS::GCPtr m_associated_document; diff --git a/Userland/Libraries/LibWeb/HTML/Window.idl b/Userland/Libraries/LibWeb/HTML/Window.idl index ca19c0697a..eb5ccdd295 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.idl +++ b/Userland/Libraries/LibWeb/HTML/Window.idl @@ -52,9 +52,8 @@ interface Window : EventTarget { boolean confirm(optional DOMString message = ""); DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); - undefined postMessage(any message, USVString targetOrigin); - // FIXME: undefined postMessage(any message, USVString targetOrigin, optional sequence transfer = []); - // FIXME: undefined postMessage(any message, optional WindowPostMessageOptions options = {}); + undefined postMessage(any message, USVString targetOrigin, optional sequence transfer = []); + undefined postMessage(any message, optional WindowPostMessageOptions options = {}); // https://dom.spec.whatwg.org/#interface-window-extensions [Replaceable] readonly attribute (Event or undefined) event; // legacy @@ -122,3 +121,7 @@ dictionary ScrollToOptions : ScrollOptions { unrestricted double left; unrestricted double top; }; + +dictionary WindowPostMessageOptions : StructuredSerializeOptions { + USVString targetOrigin = "/"; +};