mirror of
https://github.com/RGBCube/serenity
synced 2025-05-22 17:05:10 +00:00

With current architecture every window has its own WebContent process
and there is one WebDriver process that is responsible for talking to
all opened windows. It thus make sense to manage open windows from
WebDriver process instead of WebContent process that is not supposed
to know about all other opened WebContent processes.
This mostly reverts 826d5f8f9a
but also
adds `web_content_connection` to window structure and window id
generation (currently out of spec).
With these changes `get_window_handles`, `switch_to_window` and
`close_window` start to actually switch, close and returned handles
of currently opened windows.
166 lines
5.9 KiB
C++
166 lines
5.9 KiB
C++
/*
|
||
* Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
|
||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include "Session.h"
|
||
#include "Client.h"
|
||
#include <AK/JsonObject.h>
|
||
#include <LibCore/LocalServer.h>
|
||
#include <LibCore/StandardPaths.h>
|
||
#include <LibCore/System.h>
|
||
#include <unistd.h>
|
||
|
||
namespace WebDriver {
|
||
|
||
Session::Session(unsigned session_id, NonnullRefPtr<Client> client, Web::WebDriver::LadybirdOptions options)
|
||
: m_client(move(client))
|
||
, m_options(move(options))
|
||
, m_id(session_id)
|
||
{
|
||
}
|
||
|
||
Session::~Session()
|
||
{
|
||
if (auto error = stop(); error.is_error())
|
||
warnln("Failed to stop session {}: {}", m_id, error.error());
|
||
}
|
||
|
||
ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<ServerPromise> promise)
|
||
{
|
||
dbgln("Listening for WebDriver connection on {}", *m_web_content_socket_path);
|
||
|
||
auto server = TRY(Core::LocalServer::try_create());
|
||
server->listen(*m_web_content_socket_path);
|
||
|
||
server->on_accept = [this, promise](auto client_socket) {
|
||
auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(move(client_socket), m_client, session_id()));
|
||
if (maybe_connection.is_error()) {
|
||
promise->resolve(maybe_connection.release_error());
|
||
return;
|
||
}
|
||
|
||
dbgln("WebDriver is connected to WebContent socket");
|
||
auto web_content_connection = maybe_connection.release_value();
|
||
|
||
auto handle_name = String::formatted("window-{}"sv, m_next_handle_id).release_value_but_fixme_should_propagate_errors();
|
||
m_next_handle_id++;
|
||
m_windows.set(handle_name, Session::Window { handle_name, move(web_content_connection) });
|
||
|
||
if (m_current_window_handle.is_empty())
|
||
m_current_window_handle = handle_name;
|
||
|
||
promise->resolve({});
|
||
};
|
||
|
||
server->on_accept_error = [promise](auto error) {
|
||
promise->resolve(move(error));
|
||
};
|
||
|
||
return server;
|
||
}
|
||
|
||
ErrorOr<void> Session::start(LaunchBrowserCallbacks const& callbacks)
|
||
{
|
||
auto promise = TRY(ServerPromise::try_create());
|
||
|
||
m_web_content_socket_path = DeprecatedString::formatted("{}/webdriver/session_{}_{}", TRY(Core::StandardPaths::runtime_directory()), getpid(), m_id);
|
||
auto web_content_server = TRY(create_server(promise));
|
||
|
||
if (m_options.headless)
|
||
m_browser_pid = TRY(callbacks.launch_headless_browser(*m_web_content_socket_path));
|
||
else
|
||
m_browser_pid = TRY(callbacks.launch_browser(*m_web_content_socket_path));
|
||
|
||
// FIXME: Allow this to be more asynchronous. For now, this at least allows us to propagate
|
||
// errors received while accepting the Browser and WebContent sockets.
|
||
TRY(promise->await());
|
||
|
||
m_started = true;
|
||
return {};
|
||
}
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-close-the-session
|
||
Web::WebDriver::Response Session::stop()
|
||
{
|
||
if (!m_started)
|
||
return JsonValue {};
|
||
|
||
// 1. Perform the following substeps based on the remote end’s type:
|
||
// NOTE: We perform the "Remote end is an endpoint node" steps in the WebContent process.
|
||
web_content_connection().close_session();
|
||
|
||
// 2. Remove the current session from active sessions.
|
||
// NOTE: Handled by WebDriver::Client.
|
||
|
||
// 3. Perform any implementation-specific cleanup steps.
|
||
if (m_browser_pid.has_value()) {
|
||
MUST(Core::System::kill(*m_browser_pid, SIGTERM));
|
||
m_browser_pid = {};
|
||
}
|
||
if (m_web_content_socket_path.has_value()) {
|
||
MUST(Core::System::unlink(*m_web_content_socket_path));
|
||
m_web_content_socket_path = {};
|
||
}
|
||
|
||
m_started = false;
|
||
|
||
// 4. If an error has occurred in any of the steps above, return the error, otherwise return success with data null.
|
||
return JsonValue {};
|
||
}
|
||
|
||
// 11.2 Close Window, https://w3c.github.io/webdriver/#dfn-close-window
|
||
Web::WebDriver::Response Session::close_window()
|
||
{
|
||
// 3. Close the current top-level browsing context.
|
||
TRY(web_content_connection().close_window());
|
||
m_windows.remove(m_current_window_handle);
|
||
|
||
// 4. If there are no more open top-level browsing contexts, then close the session.
|
||
if (m_windows.is_empty())
|
||
stop();
|
||
|
||
// 5. Return the result of running the remote end steps for the Get Window Handles command.
|
||
return get_window_handles();
|
||
}
|
||
|
||
// 11.3 Switch to Window, https://w3c.github.io/webdriver/#dfn-switch-to-window
|
||
Web::WebDriver::Response Session::switch_to_window(StringView handle)
|
||
{
|
||
// 4. If handle is equal to the associated window handle for some top-level browsing context in the
|
||
// current session, let context be the that browsing context, and set the current top-level
|
||
// browsing context with context.
|
||
// Otherwise, return error with error code no such window.
|
||
if (auto it = m_windows.find(handle); it != m_windows.end())
|
||
m_current_window_handle = it->key;
|
||
else
|
||
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::NoSuchWindow, "Window not found");
|
||
|
||
// FIXME: 5. Update any implementation-specific state that would result from the user selecting the current
|
||
// browsing context for interaction, without altering OS-level focus.
|
||
|
||
// 6. Return success with data null.
|
||
return JsonValue {};
|
||
}
|
||
|
||
// 11.4 Get Window Handles, https://w3c.github.io/webdriver/#dfn-get-window-handles
|
||
Web::WebDriver::Response Session::get_window_handles() const
|
||
{
|
||
// 1. Let handles be a JSON List.
|
||
JsonArray handles {};
|
||
|
||
// 2. For each top-level browsing context in the remote end, push the associated window handle onto handles.
|
||
for (auto const& window_handle : m_windows.keys()) {
|
||
handles.append(JsonValue(window_handle));
|
||
}
|
||
|
||
// 3. Return success with data handles.
|
||
return JsonValue { move(handles) };
|
||
}
|
||
|
||
}
|