blob: e31e91369d4c2230ad383d0ac9722e4f4892e403 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_UI_TESTING_UTIL_WEB_VIEW_CLIENT_H_
#define SRC_UI_TESTING_UTIL_WEB_VIEW_CLIENT_H_
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <fuchsia/web/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fit/function.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/zx/eventpair.h>
#include <string>
#include <string_view>
namespace ui_testing {
// In-process RealmBuilder component that hosts a Chromium WebEngine Frame and
// serves `fuchsia.ui.app.ViewProvider` to the test scene.
class WebViewClient final : public fuchsia::ui::app::ViewProvider,
public fuchsia::web::NavigationEventListener,
public component_testing::LocalComponentImpl {
public:
static constexpr char kChildName[] = "web_view";
static constexpr component_testing::ChildRef kChildRef{kChildName};
static constexpr char kNetstackChildName[] = "netstack";
static constexpr component_testing::ChildRef kNetstackRef{kNetstackChildName};
// Adds the hermetic WebEngine support components (`context_provider`,
// `fonts_provider`, `intl`, `memory_pressure_signaler`, `netstack`,
// `build_info_provider`, and `cobalt`) to `realm` and routes their
// capabilities to `target`.
static void AddWebEngineSupportToRealm(component_testing::Realm& realm,
component_testing::ChildRef target);
// Adds a local `WebViewClient` child (`kChildName`) and hermetic WebEngine
// support components to `realm`, routing `ViewProvider` to `ParentRef`.
//
// When Component Manager starts the local child, `*out_client` is set to the
// running instance and cleared when the instance is destroyed.
//
// Callers must still route UI and accessibility protocols (such as
// `Flatland`, `Allocator`, `ImeService`, and `SemanticsManager`) from their
// respective realm sources to `kChildRef`.
static void AddToRealm(
component_testing::Realm& realm, async_dispatcher_t* dispatcher, WebViewClient** out_client,
fuchsia::web::ContextFeatureFlags features = fuchsia::web::ContextFeatureFlags::NETWORK);
explicit WebViewClient(
async_dispatcher_t* dispatcher,
fuchsia::web::ContextFeatureFlags features = fuchsia::web::ContextFeatureFlags::NETWORK,
WebViewClient** out_client = nullptr);
~WebViewClient() override;
bool is_started() const { return is_started_; }
bool is_main_document_loaded() const { return is_main_document_loaded_; }
bool is_window_resized() const { return is_window_resized_; }
const std::string& title() const { return title_; }
// Starts navigating the frame to `url` and resets navigation state.
//
// Precondition: `is_started()` must be true.
void LoadUrl(std::string url);
// Executes `script` in the frame across all origins and invokes `on_complete`
// when execution finishes.
//
// Precondition: `is_started()` must be true.
void ExecuteJavaScript(std::string_view script, fit::closure on_complete = nullptr);
// Injects message-port and resize-tracking helpers (`port` and
// `waitForVisible(elementId)`) into the current page, registers a
// `MessagePort` with the page, and invokes `on_registered` once the port
// handshake completes. Also updates `is_window_resized()` once the page
// reports non-zero window dimensions.
//
// Precondition: `is_started()` must be true.
void RegisterMessagePort(fit::closure on_registered);
// Reads the next message sent by the web page on the registered `MessagePort`
// and passes its payload as a string to `callback`.
//
// Precondition: `RegisterMessagePort()` must have been called.
void ReceiveMessage(fit::function<void(std::string)> callback);
// |component_testing::LocalComponentImpl|
void OnStart() override;
// |fuchsia::ui::app::ViewProvider|
void CreateView2(fuchsia::ui::app::CreateView2Args args) override;
void CreateViewWithViewRef(zx::eventpair token,
fuchsia::ui::views::ViewRefControl view_ref_control,
fuchsia::ui::views::ViewRef view_ref) override;
// |fuchsia::web::NavigationEventListener|
void OnNavigationStateChanged(fuchsia::web::NavigationState nav_state,
OnNavigationStateChangedCallback send_ack) override;
private:
async_dispatcher_t* dispatcher_ = nullptr;
fuchsia::web::ContextFeatureFlags features_;
WebViewClient** out_client_ = nullptr;
fidl::BindingSet<fuchsia::ui::app::ViewProvider> view_provider_bindings_;
fidl::Binding<fuchsia::web::NavigationEventListener> navigation_event_listener_binding_;
fuchsia::web::ContextProviderPtr web_context_provider_;
fuchsia::web::ContextPtr web_context_;
fuchsia::web::FramePtr web_frame_;
fuchsia::web::NavigationControllerPtr navigation_controller_;
fuchsia::web::MessagePortPtr message_port_;
bool is_started_ = false;
bool is_main_document_loaded_ = false;
bool is_window_resized_ = false;
std::string title_;
};
} // namespace ui_testing
#endif // SRC_UI_TESTING_UTIL_WEB_VIEW_CLIENT_H_