diff --git a/Libraries/LibIPC/Connection.h b/Libraries/LibIPC/Connection.h index 9bbf65b0c30..9d100df40d5 100644 --- a/Libraries/LibIPC/Connection.h +++ b/Libraries/LibIPC/Connection.h @@ -27,6 +27,7 @@ public: [[nodiscard]] bool is_open() const; ErrorOr post_message(Message const&); + ErrorOr post_message(MessageBuffer); void shutdown(); virtual void die() { } @@ -47,7 +48,6 @@ protected: ErrorOr drain_messages_from_peer(); void try_parse_messages(Vector const& bytes, size_t& index); - ErrorOr post_message(MessageBuffer); void handle_messages(); IPC::Stub& m_local_stub; diff --git a/Libraries/LibIPC/Encoder.cpp b/Libraries/LibIPC/Encoder.cpp index f5218724edb..005d8f5e053 100644 --- a/Libraries/LibIPC/Encoder.cpp +++ b/Libraries/LibIPC/Encoder.cpp @@ -47,18 +47,12 @@ ErrorOr encode(Encoder& encoder, double const& value) template<> ErrorOr encode(Encoder& encoder, String const& value) { - auto bytes = value.bytes(); - TRY(encoder.encode_size(bytes.size())); - TRY(encoder.append(bytes.data(), bytes.size())); - return {}; + return encoder.encode(value.bytes_as_string_view()); } template<> ErrorOr encode(Encoder& encoder, StringView const& value) { - if (value.is_null()) - return encoder.encode(NumericLimits::max()); - TRY(encoder.encode_size(value.length())); TRY(encoder.append(reinterpret_cast(value.characters_without_null_termination()), value.length())); return {}; diff --git a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp index fe32d26146d..11f887898eb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -17,9 +18,11 @@ #include namespace { + struct Parameter { Vector attributes; ByteString type; + ByteString type_for_encoding; ByteString name; }; @@ -71,7 +74,9 @@ static bool is_primitive_type(ByteString const& type) static bool is_simple_type(ByteString const& type) { // Small types that it makes sense just to pass by value. - return type.is_one_of("AK::CaseSensitivity", "AK::Duration", "Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Web::DevicePixelRect", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior", "Web::HTML::VisibilityState", "WebView::PageInfoType"); + if (type.starts_with("ReadonlySpan<"sv) && type.ends_with(">"sv)) + return true; + return type.is_one_of("AK::CaseSensitivity", "AK::Duration", "Gfx::Color", "ReadonlyBytes", "StringView", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Web::DevicePixelRect", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior", "Web::HTML::VisibilityState", "WebView::PageInfoType"); } static bool is_primitive_or_simple_type(ByteString const& type) @@ -91,6 +96,19 @@ static ByteString message_name(ByteString const& endpoint, ByteString const& mes return builder.to_byte_string(); } +static ByteString make_argument_type(ByteString const& type) +{ + StringBuilder builder; + + bool const_ref = !is_primitive_or_simple_type(type); + + builder.append(type); + if (const_ref) + builder.append(" const&"sv); + + return builder.to_byte_string(); +} + Vector parse(ByteBuffer const& file_contents) { GenericLexer lexer(file_contents); @@ -165,11 +183,23 @@ Vector parse(ByteBuffer const& file_contents) consume_whitespace(); } } + parameter.type = parse_parameter_type(); if (parameter.type.ends_with(',') || parameter.type.ends_with(')')) { warnln("Parameter {} of method: {} must be named", parameter_index, message_name); VERIFY_NOT_REACHED(); } + + if (parameter.type.starts_with("Vector<"sv) && parameter.type.ends_with(">"sv)) { + parameter.type_for_encoding = parameter.type.replace("Vector"sv, "ReadonlySpan"sv, ReplaceMode::FirstOnly); + } else if (parameter.type.is_one_of("String"sv, "ByteString"sv)) { + parameter.type_for_encoding = "StringView"sv; + } else if (parameter.type == "ByteBuffer"sv) { + parameter.type_for_encoding = "ReadonlyBytes"sv; + } else { + parameter.type_for_encoding = parameter.type; + } + VERIFY(!lexer.is_eof()); consume_whitespace(); parameter.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == ',' || ch == ')'; }); @@ -367,7 +397,9 @@ public:)~~~"); message_generator.append(R"~~~( virtual ~@message.pascal_name@() override = default; - virtual u32 endpoint_magic() const override { return @endpoint.magic@; } + static constexpr u32 ENDPOINT_MAGIC = @endpoint.magic@; + + virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; } virtual i32 message_id() const override { return (int)MessageID::@message.pascal_name@; } static i32 static_message_id() { return (int)MessageID::@message.pascal_name@; } virtual const char* message_name() const override { return "@endpoint.name@::@message.pascal_name@"; } @@ -410,26 +442,53 @@ public:)~~~"); return make<@message.pascal_name@>(@message.constructor_call_parameters@); })~~~"); - message_generator.appendln(R"~~~( - virtual ErrorOr encode() const override + message_generator.append(R"~~~( + static ErrorOr static_encode()~~~"); + + for (auto const& [i, parameter] : enumerate(parameters)) { + auto argument_generator = message_generator.fork(); + argument_generator.set("argument.type", make_argument_type(parameter.type_for_encoding)); + argument_generator.set("argument.name", parameter.name); + argument_generator.append("@argument.type@ @argument.name@"); + if (i != parameters.size() - 1) + argument_generator.append(", "); + } + + message_generator.append(R"~~~() { IPC::MessageBuffer buffer; IPC::Encoder stream(buffer); - TRY(stream.encode(endpoint_magic())); + TRY(stream.encode(ENDPOINT_MAGIC)); TRY(stream.encode((int)MessageID::@message.pascal_name@));)~~~"); for (auto const& parameter : parameters) { auto parameter_generator = message_generator.fork(); parameter_generator.set("parameter.name", parameter.name); - parameter_generator.appendln(R"~~~( - TRY(stream.encode(m_@parameter.name@));)~~~"); + parameter_generator.append(R"~~~( + TRY(stream.encode(@parameter.name@));)~~~"); } message_generator.appendln(R"~~~( return buffer; })~~~"); + message_generator.append(R"~~~( + virtual ErrorOr encode() const override + { + return static_encode()~~~"); + + for (auto const& [i, parameter] : enumerate(parameters)) { + auto parameter_generator = message_generator.fork(); + parameter_generator.set("parameter.name", parameter.name); + parameter_generator.append("m_@parameter.name@"); + if (i != parameters.size() - 1) + parameter_generator.append(", "); + } + + message_generator.appendln(R"~~~(); + })~~~"); + for (auto const& parameter : parameters) { auto parameter_generator = message_generator.fork(); parameter_generator.set("parameter.type", parameter.type); @@ -453,8 +512,17 @@ private:)~~~"); message_generator.appendln("\n};"); } -void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& endpoint, Message const& message, ByteString const& name, Vector const& parameters, bool is_synchronous, bool is_try) +void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& endpoint, Message const& message, ByteString const& name, Vector const& parameters, bool is_synchronous, bool is_try, bool is_utf8_string_overload = false) { + // FIXME: For String parameters, we want to retain the property that all tranferred String objects are strictly UTF-8. + // So instead of generating a single proxy method that accepts StringView parameters, we generate two overloads. + // The first accepts StringView parameters, but validates the view is UTF-8. The second accepts String parameters, + // for callers that already have a UTF-8 String object. + // + // Ideally, we will eventually have separate StringView types for each of String and ByteString, where String's + // view internally provides UTF-8 guarantees. Then we won't need these overloads. + bool generate_utf8_string_overload = false; + ByteString return_type = "void"; if (is_synchronous) { if (message.outputs.size() == 1) @@ -476,10 +544,17 @@ void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& e message_generator.append(R"~~~( @message.complex_return_type@ @try_prefix_maybe@@async_prefix_maybe@@handler_name@()~~~"); - for (size_t i = 0; i < parameters.size(); ++i) { - auto const& parameter = parameters[i]; + for (auto const& [i, parameter] : enumerate(parameters)) { + ByteString type; + if (is_synchronous || is_try) + type = parameter.type; + else if (is_utf8_string_overload) + type = make_argument_type(parameter.type); + else + type = make_argument_type(parameter.type_for_encoding); + auto argument_generator = message_generator.fork(); - argument_generator.set("argument.type", parameter.type); + argument_generator.set("argument.type", type); argument_generator.set("argument.name", parameter.name); argument_generator.append("@argument.type@ @argument.name@"); if (i != parameters.size() - 1) @@ -488,6 +563,21 @@ void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& e message_generator.append(") {"); + if (!is_synchronous && !is_try && !is_utf8_string_overload) { + for (auto const& parameter : parameters) { + auto const& type = is_synchronous || is_try ? parameter.type : parameter.type_for_encoding; + + if (parameter.type == "String"sv && type == "StringView"sv) { + auto argument_generator = message_generator.fork(); + argument_generator.set("argument.name", parameter.name); + argument_generator.append(R"~~~( + VERIFY(Utf8View { @argument.name@ }.validate());)~~~"); + + generate_utf8_string_overload = true; + } + } + } + if (is_synchronous && !is_try) { if (return_type != "void") { message_generator.append(R"~~~( @@ -505,14 +595,15 @@ void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& e auto result = m_connection.template send_sync_but_allow_failure()~~~"); } else { message_generator.append(R"~~~( - MUST(m_connection.post_message(Messages::@endpoint.name@::@message.pascal_name@ { )~~~"); + auto message_buffer = MUST(Messages::@endpoint.name@::@message.pascal_name@::static_encode()~~~"); } - for (size_t i = 0; i < parameters.size(); ++i) { - auto const& parameter = parameters[i]; + for (auto const& [i, parameter] : enumerate(parameters)) { + auto const& type = is_synchronous || is_try ? parameter.type : parameter.type_for_encoding; + auto argument_generator = message_generator.fork(); argument_generator.set("argument.name", parameter.name); - if (is_primitive_or_simple_type(parameters[i].type)) + if (is_primitive_or_simple_type(type)) argument_generator.append("@argument.name@"); else argument_generator.append("move(@argument.name@)"); @@ -547,11 +638,15 @@ void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& e return { };)~~~"); } } else { - message_generator.appendln(" }));"); + message_generator.append(R"~~~()); + MUST(m_connection.post_message(move(message_buffer))); )~~~"); } message_generator.appendln(R"~~~( })~~~"); + + if (generate_utf8_string_overload) + generate_proxy_method(message_generator, endpoint, message, message.name, message.inputs, is_synchronous, is_try, generate_utf8_string_overload); } void do_message_for_proxy(SourceGenerator message_generator, Endpoint const& endpoint, Message const& message) @@ -742,18 +837,6 @@ public: message_generator.append(R"~~~( virtual @message.complex_return_type@ @handler_name@()~~~"); - auto make_argument_type = [](ByteString const& type) { - StringBuilder builder; - - bool const_ref = !is_primitive_or_simple_type(type); - - builder.append(type); - if (const_ref) - builder.append(" const&"sv); - - return builder.to_byte_string(); - }; - for (size_t i = 0; i < parameters.size(); ++i) { auto const& parameter = parameters[i]; auto argument_generator = message_generator.fork(); diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index be2a780433a..8b6242b28b2 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -449,7 +449,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const auto* node = Web::DOM::Node::from_unique_id(node_id); // Note: Nodes without layout (aka non-visible nodes, don't have style computed) if (!node || !node->layout_node()) { - async_did_inspect_dom_node(page_id, false, {}, {}, {}, {}, {}, {}); + async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); return; } @@ -458,7 +458,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const if (node->is_element()) { auto& element = as(*node); if (!element.computed_properties()) { - async_did_inspect_dom_node(page_id, false, {}, {}, {}, {}, {}, {}); + async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); return; } @@ -562,7 +562,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const if (pseudo_element.has_value()) { auto pseudo_element_node = element.get_pseudo_element_node(pseudo_element.value()); if (!pseudo_element_node) { - async_did_inspect_dom_node(page_id, false, {}, {}, {}, {}, {}, {}); + async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); return; } @@ -596,7 +596,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const return; } - async_did_inspect_dom_node(page_id, false, {}, {}, {}, {}, {}, {}); + async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {}); } void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID const& node_id, Optional const& pseudo_element) diff --git a/UI/Headless/HeadlessWebView.cpp b/UI/Headless/HeadlessWebView.cpp index 81ef0ba2f74..e02c9deffc5 100644 --- a/UI/Headless/HeadlessWebView.cpp +++ b/UI/Headless/HeadlessWebView.cpp @@ -156,7 +156,7 @@ void HeadlessWebView::initialize_client(CreateNewClient create_new_client) client().async_update_system_theme(m_client_state.page_index, m_theme); client().async_set_viewport_size(m_client_state.page_index, viewport_size()); client().async_set_window_size(m_client_state.page_index, viewport_size()); - client().async_update_screen_rects(m_client_state.page_index, { screen_rect }, 0); + client().async_update_screen_rects(m_client_state.page_index, { { screen_rect } }, 0); } void HeadlessWebView::clear_content_filters()