diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index e4516cff8ce..284eba123a1 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -359,6 +359,8 @@ set(SOURCES Gamepad/Gamepad.cpp Gamepad/NavigatorGamepad.cpp Geolocation/Geolocation.cpp + Serial/Serial.cpp + Serial/SerialPort.cpp Geolocation/GeolocationCoordinates.cpp Geolocation/GeolocationPosition.cpp Geolocation/GeolocationPositionError.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 354f5ef5772..4a021d79d5c 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -944,6 +944,20 @@ class Selection; } +namespace Web::Serial { + +class Serial; +class SerialPort; + +struct SerialPortFilter; +struct SerialPortRequestOptions; +struct SerialOptions; +struct SerialOutputSignals; +struct SerialInputSignals; +struct SerialPortInfo; + +} + namespace Web::ServiceWorker { class ServiceWorker; diff --git a/Libraries/LibWeb/HTML/EventNames.h b/Libraries/LibWeb/HTML/EventNames.h index 44bc5505ea2..b03d852dbd4 100644 --- a/Libraries/LibWeb/HTML/EventNames.h +++ b/Libraries/LibWeb/HTML/EventNames.h @@ -43,6 +43,7 @@ namespace Web::HTML::EventNames { __ENUMERATE_HTML_EVENT(cuechange) \ __ENUMERATE_HTML_EVENT(currententrychange) \ __ENUMERATE_HTML_EVENT(cut) \ + __ENUMERATE_HTML_EVENT(disconnect) \ __ENUMERATE_HTML_EVENT(dispose) \ __ENUMERATE_HTML_EVENT(DOMContentLoaded) \ __ENUMERATE_HTML_EVENT(drag) \ diff --git a/Libraries/LibWeb/HTML/Navigator.cpp b/Libraries/LibWeb/HTML/Navigator.cpp index d8299ed7c39..784a76d2daa 100644 --- a/Libraries/LibWeb/HTML/Navigator.cpp +++ b/Libraries/LibWeb/HTML/Navigator.cpp @@ -69,6 +69,7 @@ void Navigator::visit_edges(Cell::Visitor& visitor) visitor.visit(m_plugin_array); visitor.visit(m_clipboard); visitor.visit(m_geolocation); + visitor.visit(m_serial); visitor.visit(m_user_activation); visitor.visit(m_service_worker_container); visitor.visit(m_media_capabilities); @@ -103,6 +104,13 @@ GC::Ref Navigator::geolocation() return *m_geolocation; } +GC::Ref Navigator::serial() +{ + if (!m_serial) + m_serial = realm().create(realm()); + return *m_serial; +} + GC::Ref Navigator::user_activation() { if (!m_user_activation) diff --git a/Libraries/LibWeb/HTML/Navigator.h b/Libraries/LibWeb/HTML/Navigator.h index 87258658459..6d3b4bb9692 100644 --- a/Libraries/LibWeb/HTML/Navigator.h +++ b/Libraries/LibWeb/HTML/Navigator.h @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -56,6 +57,7 @@ public: [[nodiscard]] GC::Ref plugins(); [[nodiscard]] GC::Ref clipboard(); [[nodiscard]] GC::Ref geolocation(); + [[nodiscard]] GC::Ref serial(); [[nodiscard]] GC::Ref user_activation(); [[nodiscard]] GC::Ref credentials(); @@ -89,6 +91,9 @@ private: // https://w3c.github.io/geolocation/#navigator_interface GC::Ptr m_geolocation; + // https://wicg.github.io/serial/#extensions-to-the-navigator-interface + GC::Ptr m_serial; + // https://html.spec.whatwg.org/multipage/interaction.html#dom-navigator-useractivation GC::Ptr m_user_activation; diff --git a/Libraries/LibWeb/HTML/Navigator.idl b/Libraries/LibWeb/HTML/Navigator.idl index 770e5d1555c..933e80c503f 100644 --- a/Libraries/LibWeb/HTML/Navigator.idl +++ b/Libraries/LibWeb/HTML/Navigator.idl @@ -12,6 +12,7 @@ #import #import #import +#import #import #import @@ -29,6 +30,9 @@ interface Navigator { // https://w3c.github.io/pointerevents/#extensions-to-the-navigator-interface readonly attribute long maxTouchPoints; + // https://wicg.github.io/serial/#extensions-to-the-navigator-interface + [SameObject] readonly attribute Serial serial; + // https://html.spec.whatwg.org/multipage/interaction.html#useractivation [SameObject] readonly attribute UserActivation userActivation; diff --git a/Libraries/LibWeb/HTML/WorkerNavigator.cpp b/Libraries/LibWeb/HTML/WorkerNavigator.cpp index d79a4593b04..5c523c74413 100644 --- a/Libraries/LibWeb/HTML/WorkerNavigator.cpp +++ b/Libraries/LibWeb/HTML/WorkerNavigator.cpp @@ -37,6 +37,7 @@ void WorkerNavigator::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_media_capabilities); + visitor.visit(m_serial); visitor.visit(m_service_worker_container); } @@ -47,6 +48,13 @@ GC::Ref WorkerNavigator::media_capabili return *m_media_capabilities; } +GC::Ref WorkerNavigator::serial() +{ + if (!m_serial) + m_serial = realm().create(realm()); + return *m_serial; +} + GC::Ref WorkerNavigator::service_worker() { if (!m_service_worker_container) diff --git a/Libraries/LibWeb/HTML/WorkerNavigator.h b/Libraries/LibWeb/HTML/WorkerNavigator.h index 94ecdd6f9b5..70f8cc6f616 100644 --- a/Libraries/LibWeb/HTML/WorkerNavigator.h +++ b/Libraries/LibWeb/HTML/WorkerNavigator.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,8 @@ public: GC::Ref media_capabilities(); + [[nodiscard]] GC::Ref serial(); + private: explicit WorkerNavigator(WorkerGlobalScope&); @@ -50,6 +53,9 @@ private: // https://w3c.github.io/media-capabilities/#dom-workernavigator-mediacapabilities GC::Ptr m_media_capabilities; + // https://wicg.github.io/serial/#extensions-to-the-workernavigator-interface + GC::Ptr m_serial; + GC::Ptr m_service_worker_container; }; diff --git a/Libraries/LibWeb/HTML/WorkerNavigator.idl b/Libraries/LibWeb/HTML/WorkerNavigator.idl index bba17e0e9aa..6de2556a1d9 100644 --- a/Libraries/LibWeb/HTML/WorkerNavigator.idl +++ b/Libraries/LibWeb/HTML/WorkerNavigator.idl @@ -4,6 +4,7 @@ #import #import #import +#import #import // https://html.spec.whatwg.org/multipage/workers.html#workernavigator @@ -12,6 +13,9 @@ interface WorkerNavigator { // https://w3c.github.io/media-capabilities/#dom-workernavigator-mediacapabilities [SameObject] readonly attribute MediaCapabilities mediaCapabilities; + // https://wicg.github.io/serial/#extensions-to-the-workernavigator-interface + [SameObject] readonly attribute Serial serial; + // https://w3c.github.io/ServiceWorker/#navigator-serviceworker [SecureContext, SameObject] readonly attribute ServiceWorkerContainer serviceWorker; }; diff --git a/Libraries/LibWeb/Serial/Serial.cpp b/Libraries/LibWeb/Serial/Serial.cpp new file mode 100644 index 00000000000..a92a1519382 --- /dev/null +++ b/Libraries/LibWeb/Serial/Serial.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025, Edwin Hoksberg + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Serial { + +GC_DEFINE_ALLOCATOR(Serial); + +Serial::Serial(JS::Realm& realm) + : DOM::EventTarget(realm) +{ +} + +void Serial::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(Serial); + Base::initialize(realm); +} + +// https://wicg.github.io/serial/#requestport-method +WebIDL::ExceptionOr> Serial::request_port(SerialPortRequestOptions const) +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this's relevant global object's associated Document is not allowed to use the policy-controlled feature named "serial", + // reject promise with a "SecurityError" DOMException and return promise. + + // FIXME: 3. If the relevant global object of this does not have transient activation, reject promise with a "SecurityError" DOMException and return promise. + + // FIXME: 4. If options["filters"] is present, then for each filter in options["filters"] run the following steps: + + // FIXME: 5. Run the following steps in parallel: + { + // FIXME: 1. Let allPorts be an empty list. + + // FIXME: 2. For each Bluetooth device registered with the system: + + // FIXME: 3. For each available non-Bluetooth serial port: + { + // FIXME: 1. Let port be a SerialPort representing the port. + + // FIXME: 2. Append port to allPorts. + } + + // FIXME: 4. Prompt the user to grant the site access to a serial port by presenting them with a list of ports + // in allPorts that match any filter in options["filters"] if present and allPorts otherwise. + + // FIXME: 5. If the user does not choose a port, queue a global task on the relevant global object of this using the + // serial port task source to reject promise with a "NotFoundError" DOMException and abort these steps. + + // FIXME: 6. Let port be a SerialPort representing the port chosen by the user. + + // FIXME: 7. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with port. + } + + // 6. Return promise. + dbgln("FIXME: Unimplemented Serial::request_port()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#getports-method +GC::Ref Serial::get_ports() +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this's relevant global object's associated Document is not allowed to use the policy-controlled feature named "serial", + // reject promise with a "SecurityError" DOMException and return promise. + + // FIXME: 3. Run the following steps in parallel: + { + // FIXME: 1. Let availablePorts be the sequence of available serial ports which the user has allowed the site to + // access as the result of a previous call to requestPort(). + + // FIXME: 2. Let ports be the sequence of the SerialPorts representing the ports in availablePorts. + + // FIXME: 3. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with ports. + } + + // 4. Return promise. + dbgln("FIXME: Unimplemented Serial::get_ports()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#onconnect-attribute +void Serial::set_onconnect(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::connect, event_handler); +} + +WebIDL::CallbackType* Serial::onconnect() +{ + return event_handler_attribute(HTML::EventNames::connect); +} + +// https://wicg.github.io/serial/#ondisconnect-attribute +void Serial::set_ondisconnect(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::disconnect, event_handler); +} + +WebIDL::CallbackType* Serial::ondisconnect() +{ + return event_handler_attribute(HTML::EventNames::disconnect); +} + +} diff --git a/Libraries/LibWeb/Serial/Serial.h b/Libraries/LibWeb/Serial/Serial.h new file mode 100644 index 00000000000..b65020d3350 --- /dev/null +++ b/Libraries/LibWeb/Serial/Serial.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, Edwin Hoksberg + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Serial { + +// https://wicg.github.io/serial/#serialportfilter-dictionary +struct SerialPortFilter { + Optional usb_vendor_id; + Optional usb_product_id; + Optional bluetooth_service_class_id; +}; + +// https://wicg.github.io/serial/#serialportrequestoptions-dictionary +struct SerialPortRequestOptions { + Optional> filters; + Optional> allowed_bluetooth_service_class_ids; +}; + +// https://wicg.github.io/serial/#serial-interface +class Serial : public DOM::EventTarget { + WEB_PLATFORM_OBJECT(Serial, DOM::EventTarget); + GC_DECLARE_ALLOCATOR(Serial); + +public: + // https://wicg.github.io/serial/#requestport-method + WebIDL::ExceptionOr> request_port(SerialPortRequestOptions = {}); + // https://wicg.github.io/serial/#getports-method + GC::Ref get_ports(); + + // https://wicg.github.io/serial/#onconnect-attribute + void set_onconnect(WebIDL::CallbackType*); + WebIDL::CallbackType* onconnect(); + + // https://wicg.github.io/serial/#ondisconnect-attribute + void set_ondisconnect(WebIDL::CallbackType*); + WebIDL::CallbackType* ondisconnect(); + +private: + explicit Serial(JS::Realm&); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Libraries/LibWeb/Serial/Serial.idl b/Libraries/LibWeb/Serial/Serial.idl new file mode 100644 index 00000000000..b5c30092600 --- /dev/null +++ b/Libraries/LibWeb/Serial/Serial.idl @@ -0,0 +1,26 @@ +#import +#import + +// https://wicg.github.io/serial/#serial-interface +[Exposed=(DedicatedWorker, Window), SecureContext] +interface Serial : EventTarget { + attribute EventHandler onconnect; + attribute EventHandler ondisconnect; + Promise> getPorts(); + [Exposed=Window] Promise requestPort(optional SerialPortRequestOptions options = {}); +}; + +// https://wicg.github.io/serial/#serialportfilter-dictionary +dictionary SerialPortFilter { + unsigned short usbVendorId; + unsigned short usbProductId; + // FIXME: Should be a BluetoothServiceUUID + DOMString bluetoothServiceClassId; +}; + +// https://wicg.github.io/serial/#serialportrequestoptions-dictionary +dictionary SerialPortRequestOptions { + sequence filters; + // FIXME: Should be a BluetoothServiceUUID + sequence allowedBluetoothServiceClassIds; +}; diff --git a/Libraries/LibWeb/Serial/SerialPort.cpp b/Libraries/LibWeb/Serial/SerialPort.cpp new file mode 100644 index 00000000000..48f892f9d5a --- /dev/null +++ b/Libraries/LibWeb/Serial/SerialPort.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2025, Edwin Hoksberg + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::Serial { + +SerialPort::SerialPort(JS::Realm& realm) + : DOM::EventTarget(realm) +{ +} + +void SerialPort::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(SerialPort); + Base::initialize(realm); +} + +// https://wicg.github.io/serial/#getinfo-method +SerialPortInfo SerialPort::get_info() const +{ + // 1. Let info be an empty ordered map. + auto info = SerialPortInfo {}; + + // FIXME: 2. If the port is part of a USB device, perform the following steps: + { + // FIXME: 1. Set info["usbVendorId"] to the vendor ID of the device. + + // FIXME: 2. Set info["usbProductId"] to the product ID of the device. + } + + // FIXME: 3. If the port is a service on a Bluetooth device, perform the following steps: + { + // FIXME: 1. Set info["bluetoothServiceClassId"] to the service class UUID of the Bluetooth service. + } + + // 4. Return info. + return info; +} + +// https://wicg.github.io/serial/#open-method +GC::Ref SerialPort::open(SerialOptions) +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this.[[state]] is not "closed", reject promise with an "InvalidStateError" DOMException and return promise. + + // FIXME: 3. If options["dataBits"] is not 7 or 8, reject promise with TypeError and return promise. + + // FIXME: 4. If options["stopBits"] is not 1 or 2, reject promise with TypeError and return promise. + + // FIXME: 5. If options["bufferSize"] is 0, reject promise with TypeError and return promise. + + // FIXME: 6. Optionally, if options["bufferSize"] is larger than the implementation is able to support, reject promise with a TypeError and return promise. + + // FIXME: 7. Set this.[[state]] to "opening". + + // FIXME: 8. Perform the following steps in parallel. + { + // FIXME: 1. Invoke the operating system to open the serial port using the connection parameters (or their defaults) specified in options. + + // FIXME: 2. If this fails for any reason, queue a global task on the relevant global object of this using the serial port task source to reject promise with a "NetworkError" DOMException and abort these steps. + + // FIXME: 3. Set this.[[state]] to "opened". + + // FIXME: 4. Set this.[[bufferSize]] to options["bufferSize"]. + + // FIXME: 5. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined. + } + + // FIXME: 9. Return promise. + dbgln("FIXME: Unimplemented SerialPort::open()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#setsignals-method +GC::Ref SerialPort::set_signals(SerialOutputSignals) +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this.[[state]] is not "opened", reject promise with an "InvalidStateError" DOMException and return promise. + + // FIXME: 3. If all of the specified members of signals are not present reject promise with TypeError and return promise. + + // FIXME: 4. Perform the following steps in parallel: + { + // FIXME: 1. If signals["dataTerminalReady"] is present, invoke the operating system to either assert (if true) or + // deassert (if false) the "data terminal ready" or "DTR" signal on the serial port. + + // FIXME: 2. If signals["requestToSend"] is present, invoke the operating system to either assert (if true) or + // deassert (if false) the "request to send" or "RTS" signal on the serial port. + + // FIXME: 3. If signals["break"] is present, invoke the operating system to either assert (if true) or + // deassert (if false) the "break" signal on the serial port. + + // FIXME: 4. If the operating system fails to change the state of any of these signals for any reason, queue a global task + // on the relevant global object of this using the serial port task source to reject promise with a "NetworkError" DOMException. + + // FIXME: 5. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined. + } + + // 5. Return promise. + dbgln("FIXME: Unimplemented SerialPort::set_signals()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#getsignals-method +GC::Ref SerialPort::get_signals() const +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this.[[state]] is not "opened", reject promise with an "InvalidStateError" DOMException and return promise. + + // FIXME: 3. Perform the following steps in parallel: + { + // FIXME: 1. Query the operating system for the status of the control signals that may be asserted by the device connected to the serial port. + + // FIXME: 2. If the operating system fails to determine the status of these signals for any reason, queue a global task on the relevant global object of + // this using the serial port task source to reject promise with a "NetworkError" DOMException and abort these steps. + + // FIXME: 3. Let dataCarrierDetect be true if the "data carrier detect" or "DCD" signal has been asserted by the device, and false otherwise. + + // FIXME: 4. Let clearToSend be true if the "clear to send" or "CTS" signal has been asserted by the device, and false otherwise. + + // FIXME: 5. Let ringIndicator be true if the "ring indicator" or "RI" signal has been asserted by the device, and false otherwise. + + // FIXME: 6. Let dataSetReady be true if the "data set ready" or "DSR" signal has been asserted by the device, and false otherwise. + + // FIXME: 7. Let signals be the ordered map «[ "dataCarrierDetect" → dataCarrierDetect, "clearToSend" → clearToSend, "ringIndicator" → ringIndicator, "dataSetReady" → dataSetReady ]». + + // FIXME: 8. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with signals. + } + + // 4. Return promise. + dbgln("FIXME: Unimplemented SerialPort::get_signals()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#close-method +GC::Ref SerialPort::close() +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 2. If this.[[state]] is not "opened", reject promise with an "InvalidStateError" DOMException and return promise. + + // FIXME: 3. Let cancelPromise be the result of invoking cancel on this.[[readable]] or a promise resolved with undefined if this.[[readable]] is null. + + // FIXME: 4. Let abortPromise be the result of invoking abort on this.[[writable]] or a promise resolved with undefined if this.[[writable]] is null. + + // FIXME: 5. Let pendingClosePromise be a new promise. + + // FIXME: 6. If this.[[readable]] and this.[[writable]] are null, resolve pendingClosePromise with undefined. + + // FIXME: 7. Set this.[[pendingClosePromise]] to pendingClosePromise. + + // FIXME: 8. Let combinedPromise be the result of getting a promise to wait for all with «cancelPromise, abortPromise, pendingClosePromise». + + // FIXME: 9. Set this.[[state]] to "closing". + + // FIXME: 10. React to combinedPromise. + { + // If combinedPromise was fulfilled, then: + // FIXME: 1. Run the following steps in parallel: + { + // FIXME: 1. Invoke the operating system to close the serial port and release any associated resources. + + // FIXME: 2. Set this.[[state]] to "closed". + + // FIXME: 3. Set this.[[readFatal]] and this.[[writeFatal]] to false. + + // FIXME: 4. Set this.[[pendingClosePromise]] to null. + + // FIXME: 5. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined. + } + + // If combinedPromise was rejected with reason r, then: + { + // FIXME: 1. Set this.[[pendingClosePromise]] to null. + + // FIXME: 2. Queue a global task on the relevant global object of this using the serial port task source to reject promise with r. + } + } + + // 11. Return promise. + dbgln("FIXME: Unimplemented SerialPort::close()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +// https://wicg.github.io/serial/#forget-method +GC::Ref SerialPort::forget() +{ + auto& realm = this->realm(); + + // FIXME: 1. Let promise be a new promise. + + // FIXME: 1. If the user agent can't perform this action (e.g. permission was granted by administrator policy), return a promise resolved with undefined. + + // FIXME: 2. Run the following steps in parallel: + { + // FIXME: 1. Set this.[[state]] to "forgetting". + + // FIXME: 2. Remove this from the sequence of serial ports on the system which the user has allowed the site to access as the result of a previous call to requestPort(). + + // FIXME: 3. Set this.[[state]] to "forgotten". + + // FIXME: 4. Queue a global task on the relevant global object of this using the serial port task source to resolve promise with undefined. + } + + // 7. Return promise. + dbgln("FIXME: Unimplemented SerialPort::forget()"); + return WebIDL::create_rejected_promise(realm, WebIDL::UnknownError::create(realm, ""_string)); +} + +void SerialPort::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_readable); + visitor.visit(m_writable); + visitor.visit(m_pending_close_promise); +} + +// https://wicg.github.io/serial/#onconnect-attribute-0 +void SerialPort::set_onconnect(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::connect, event_handler); +} + +WebIDL::CallbackType* SerialPort::onconnect() +{ + return event_handler_attribute(HTML::EventNames::connect); +} + +// https://wicg.github.io/serial/#ondisconnect-attribute-0 +void SerialPort::set_ondisconnect(WebIDL::CallbackType* event_handler) +{ + set_event_handler_attribute(HTML::EventNames::disconnect, event_handler); +} + +WebIDL::CallbackType* SerialPort::ondisconnect() +{ + return event_handler_attribute(HTML::EventNames::disconnect); +} + +} diff --git a/Libraries/LibWeb/Serial/SerialPort.h b/Libraries/LibWeb/Serial/SerialPort.h new file mode 100644 index 00000000000..61191994864 --- /dev/null +++ b/Libraries/LibWeb/Serial/SerialPort.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2025, Edwin Hoksberg + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::Serial { + +// https://wicg.github.io/serial/#serialoptions-dictionary +struct SerialOptions { + Optional baud_rate = {}; + Optional data_bits = 8; + Optional stop_bits = 1; + Optional parity = Bindings::ParityType::None; + Optional buffer_size = 255; + Optional flow_control = Bindings::FlowControlType::None; +}; + +// https://wicg.github.io/serial/#serialoutputsignals-dictionary +struct SerialOutputSignals { + Optional data_terminal_ready; + Optional request_to_send; + Optional break_; +}; + +// https://wicg.github.io/serial/#serialinputsignals-dictionary +struct SerialInputSignals { + WebIDL::Boolean data_carrier_detect; + WebIDL::Boolean clear_to_send; + WebIDL::Boolean ring_indicator; + WebIDL::Boolean data_set_ready; +}; + +// https://wicg.github.io/serial/#serialportinfo-dictionary +struct SerialPortInfo { + Optional usb_vendor_id; + Optional usb_product_id; + Optional bluetooth_service_class_id; +}; + +enum SerialPortState : u8 { + Closed, + Opening, + Opened, + Closing, + Forgetting, + Forgotten, +}; + +// https://wicg.github.io/serial/#serialport-interface +class SerialPort : public DOM::EventTarget { + WEB_PLATFORM_OBJECT(SerialPort, DOM::EventTarget); + GC_DECLARE_ALLOCATOR(SerialPort); + + // https://wicg.github.io/serial/#getinfo-method + SerialPortInfo get_info() const; + // https://wicg.github.io/serial/#open-method + GC::Ref open(SerialOptions); + // https://wicg.github.io/serial/#setsignals-method + GC::Ref set_signals(SerialOutputSignals = {}); + // https://wicg.github.io/serial/#getsignals-method + GC::Ref get_signals() const; + // https://wicg.github.io/serial/#close-method + GC::Ref close(); + // https://wicg.github.io/serial/#forget-method + GC::Ref forget(); + + // https://wicg.github.io/serial/#connected-attribute + bool connected() const { return m_connected; } + // https://wicg.github.io/serial/#readable-attribute + GC::Ref readable() { return *m_readable; } + // https://wicg.github.io/serial/#writable-attribute + GC::Ref writable() { return *m_writable; } + + // https://wicg.github.io/serial/#onconnect-attribute-0 + void set_onconnect(WebIDL::CallbackType*); + WebIDL::CallbackType* onconnect(); + + // https://wicg.github.io/serial/#ondisconnect-attribute-0 + void set_ondisconnect(WebIDL::CallbackType*); + WebIDL::CallbackType* ondisconnect(); + +protected: + virtual void visit_edges(Cell::Visitor&) override; + +private: + explicit SerialPort(JS::Realm&); + + virtual void initialize(JS::Realm&) override; + + // https://wicg.github.io/serial/#dfn-state + // Tracks the active state of the SerialPort + SerialPortState m_state { SerialPortState::Closed }; + + // https://wicg.github.io/serial/#dfn-buffersize + // The amount of data to buffer for transmit and receive + unsigned long m_buffer_size = {}; + + // https://wicg.github.io/serial/#dfn-connected + // A flag indicating the logical connection state of serial port + bool m_connected { false }; + + // https://wicg.github.io/serial/#dfn-readable + // A ReadableStream that receives data from the port + GC::Ptr m_readable = {}; + + // https://wicg.github.io/serial/#dfn-readfatal + // A flag indicating that the port has encountered a fatal read error + bool m_read_fatal { false }; + + // https://wicg.github.io/serial/#dfn-writable + // A WritableStream that transmits data to the port + GC::Ptr m_writable = {}; + + // https://wicg.github.io/serial/#dfn-writefatal + // A flag indicating that the port has encountered a fatal write error + bool m_write_fatal { false }; + + // https://wicg.github.io/serial/#dfn-pendingclosepromise + // A Promise used to wait for readable and writable to close + GC::Ptr m_pending_close_promise = {}; +}; + +} diff --git a/Libraries/LibWeb/Serial/SerialPort.idl b/Libraries/LibWeb/Serial/SerialPort.idl new file mode 100644 index 00000000000..25193db431c --- /dev/null +++ b/Libraries/LibWeb/Serial/SerialPort.idl @@ -0,0 +1,65 @@ +#import + +// https://wicg.github.io/serial/#serialport-interface +[Exposed=(DedicatedWorker,Window), SecureContext] +interface SerialPort : EventTarget { + attribute EventHandler onconnect; + attribute EventHandler ondisconnect; + readonly attribute boolean connected; + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; + + SerialPortInfo getInfo(); + + Promise open(SerialOptions options); + Promise setSignals(optional SerialOutputSignals signals = {}); + Promise getSignals(); + Promise close(); + Promise forget(); +}; + +// https://wicg.github.io/serial/#serialoptions-dictionary +dictionary SerialOptions { + [EnforceRange] unsigned long baudRate; + [EnforceRange] octet dataBits = 8; + [EnforceRange] octet stopBits = 1; + ParityType parity = "none"; + [EnforceRange] unsigned long bufferSize = 255; + FlowControlType flowControl = "none"; +}; + +// https://wicg.github.io/serial/#serialoutputsignals-dictionary +dictionary SerialOutputSignals { + boolean dataTerminalReady; + boolean requestToSend; + boolean break; +}; + +// https://wicg.github.io/serial/#serialinputsignals-dictionary +dictionary SerialInputSignals { + required boolean dataCarrierDetect; + required boolean clearToSend; + required boolean ringIndicator; + required boolean dataSetReady; +}; + +// https://wicg.github.io/serial/#serialportinfo-dictionary +dictionary SerialPortInfo { + unsigned short usbVendorId; + unsigned short usbProductId; + // FIXME: Should be a BluetoothServiceUUID + DOMString bluetoothServiceClassId; +}; + +// https://wicg.github.io/serial/#paritytype-enum +enum ParityType { + "none", + "even", + "odd" +}; + +// https://wicg.github.io/serial/#flowcontroltype-enum +enum FlowControlType { + "none", + "hardware" +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 2d8d8216bc8..7c415dd9eab 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -306,6 +306,8 @@ libweb_js_bindings(ResizeObserver/ResizeObserver) libweb_js_bindings(ResizeObserver/ResizeObserverEntry) libweb_js_bindings(ResizeObserver/ResizeObserverSize) libweb_js_bindings(ResourceTiming/PerformanceResourceTiming) +libweb_js_bindings(Serial/Serial) +libweb_js_bindings(Serial/SerialPort) libweb_js_bindings(ServiceWorker/CacheStorage) libweb_js_bindings(ServiceWorker/ServiceWorker) libweb_js_bindings(ServiceWorker/ServiceWorkerContainer) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index d5f45871a62..32444f71bc3 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -330,6 +330,7 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface) static ByteString make_input_acceptable_cpp(ByteString const& input) { if (input.is_one_of( + "break", "char", "class", "continue", @@ -4841,6 +4842,7 @@ using namespace Web::RequestIdleCallback; using namespace Web::ResizeObserver; using namespace Web::ResourceTiming; using namespace Web::Selection; +using namespace Web::Serial; using namespace Web::ServiceWorker; using namespace Web::StorageAPI; using namespace Web::Streams; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h index 99beb0e11ed..1bcb7c4616e 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h @@ -37,6 +37,7 @@ static constexpr Array libweb_interface_namespaces = { "ResizeObserver"sv, "SVG"sv, "Selection"sv, + "Serial"sv, "ServiceWorker"sv, "Streams"sv, "UIEvents"sv, diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 675cfefea3b..2559787d9d8 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -388,6 +388,8 @@ ScreenOrientation ScriptProcessorNode SecurityPolicyViolationEvent Selection +Serial +SerialPort ServiceWorker ServiceWorkerContainer ServiceWorkerRegistration