Serenity Operating System
at master 278 lines 8.4 kB view raw
1/* 2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/SourceLocation.h> 9#include <LibWeb/DOM/Document.h> 10#include <LibWeb/HTML/BrowsingContext.h> 11#include <LibWeb/HTML/EventLoop/EventLoop.h> 12#include <LibWeb/HTML/Scripting/Environments.h> 13#include <LibWeb/Page/Page.h> 14#include <LibWeb/Platform/EventLoopPlugin.h> 15 16namespace Web { 17 18Page::Page(PageClient& client) 19 : m_client(client) 20{ 21 m_top_level_browsing_context = JS::make_handle(*HTML::BrowsingContext::create_a_new_top_level_browsing_context(*this)); 22} 23 24Page::~Page() = default; 25 26HTML::BrowsingContext& Page::focused_context() 27{ 28 if (m_focused_context) 29 return *m_focused_context; 30 return top_level_browsing_context(); 31} 32 33void Page::set_focused_browsing_context(Badge<EventHandler>, HTML::BrowsingContext& browsing_context) 34{ 35 m_focused_context = browsing_context.make_weak_ptr(); 36} 37 38void Page::load(const AK::URL& url) 39{ 40 top_level_browsing_context().loader().load(url, FrameLoader::Type::Navigation); 41} 42 43void Page::load(LoadRequest& request) 44{ 45 top_level_browsing_context().loader().load(request, FrameLoader::Type::Navigation); 46} 47 48void Page::load_html(StringView html, const AK::URL& url) 49{ 50 top_level_browsing_context().loader().load_html(html, url); 51} 52 53Gfx::Palette Page::palette() const 54{ 55 return m_client.palette(); 56} 57 58// https://w3c.github.io/csswg-drafts/cssom-view-1/#web-exposed-screen-area 59CSSPixelRect Page::web_exposed_screen_area() const 60{ 61 auto device_pixel_rect = m_client.screen_rect(); 62 auto scale = client().device_pixels_per_css_pixel(); 63 return { 64 device_pixel_rect.x().value() / scale, 65 device_pixel_rect.y().value() / scale, 66 device_pixel_rect.width().value() / scale, 67 device_pixel_rect.height().value() / scale 68 }; 69} 70 71CSS::PreferredColorScheme Page::preferred_color_scheme() const 72{ 73 return m_client.preferred_color_scheme(); 74} 75 76CSSPixelPoint Page::device_to_css_point(DevicePixelPoint point) const 77{ 78 return { 79 point.x().value() / client().device_pixels_per_css_pixel(), 80 point.y().value() / client().device_pixels_per_css_pixel(), 81 }; 82} 83 84DevicePixelPoint Page::css_to_device_point(CSSPixelPoint point) const 85{ 86 return { 87 point.x().value() * client().device_pixels_per_css_pixel(), 88 point.y().value() * client().device_pixels_per_css_pixel(), 89 }; 90} 91 92CSSPixelRect Page::device_to_css_rect(DevicePixelRect rect) const 93{ 94 auto scale = client().device_pixels_per_css_pixel(); 95 return { 96 rect.x().value() / scale, 97 rect.y().value() / scale, 98 rect.width().value() / scale, 99 rect.height().value() / scale 100 }; 101} 102 103DevicePixelRect Page::enclosing_device_rect(CSSPixelRect rect) const 104{ 105 auto scale = client().device_pixels_per_css_pixel(); 106 return { 107 floorf(rect.x().value() * scale), 108 floorf(rect.y().value() * scale), 109 ceilf(rect.width().value() * scale), 110 ceilf(rect.height().value() * scale) 111 }; 112} 113 114DevicePixelRect Page::rounded_device_rect(CSSPixelRect rect) const 115{ 116 auto scale = client().device_pixels_per_css_pixel(); 117 return { 118 roundf(rect.x().value() * scale), 119 roundf(rect.y().value() * scale), 120 roundf(rect.width().value() * scale), 121 roundf(rect.height().value() * scale) 122 }; 123} 124 125bool Page::handle_mousewheel(DevicePixelPoint position, unsigned button, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) 126{ 127 return top_level_browsing_context().event_handler().handle_mousewheel(device_to_css_point(position), button, buttons, modifiers, wheel_delta_x, wheel_delta_y); 128} 129 130bool Page::handle_mouseup(DevicePixelPoint position, unsigned button, unsigned buttons, unsigned modifiers) 131{ 132 return top_level_browsing_context().event_handler().handle_mouseup(device_to_css_point(position), button, buttons, modifiers); 133} 134 135bool Page::handle_mousedown(DevicePixelPoint position, unsigned button, unsigned buttons, unsigned modifiers) 136{ 137 return top_level_browsing_context().event_handler().handle_mousedown(device_to_css_point(position), button, buttons, modifiers); 138} 139 140bool Page::handle_mousemove(DevicePixelPoint position, unsigned buttons, unsigned modifiers) 141{ 142 return top_level_browsing_context().event_handler().handle_mousemove(device_to_css_point(position), buttons, modifiers); 143} 144 145bool Page::handle_doubleclick(DevicePixelPoint position, unsigned button, unsigned buttons, unsigned modifiers) 146{ 147 return top_level_browsing_context().event_handler().handle_doubleclick(device_to_css_point(position), button, buttons, modifiers); 148} 149 150bool Page::handle_keydown(KeyCode key, unsigned modifiers, u32 code_point) 151{ 152 return focused_context().event_handler().handle_keydown(key, modifiers, code_point); 153} 154 155bool Page::handle_keyup(KeyCode key, unsigned modifiers, u32 code_point) 156{ 157 return focused_context().event_handler().handle_keyup(key, modifiers, code_point); 158} 159 160HTML::BrowsingContext& Page::top_level_browsing_context() 161{ 162 return *m_top_level_browsing_context; 163} 164 165HTML::BrowsingContext const& Page::top_level_browsing_context() const 166{ 167 return *m_top_level_browsing_context; 168} 169 170template<typename ResponseType> 171static ResponseType spin_event_loop_until_dialog_closed(PageClient& client, Optional<ResponseType>& response, SourceLocation location = SourceLocation::current()) 172{ 173 auto& event_loop = Web::HTML::current_settings_object().responsible_event_loop(); 174 175 ScopeGuard guard { [&] { event_loop.set_execution_paused(false); } }; 176 event_loop.set_execution_paused(true); 177 178 Web::Platform::EventLoopPlugin::the().spin_until([&]() { 179 return response.has_value() || !client.is_connection_open(); 180 }); 181 182 if (!client.is_connection_open()) { 183 dbgln("WebContent client disconnected during {}. Exiting peacefully.", location.function_name()); 184 exit(0); 185 } 186 187 return response.release_value(); 188} 189 190void Page::did_request_alert(DeprecatedString const& message) 191{ 192 m_pending_dialog = PendingDialog::Alert; 193 m_client.page_did_request_alert(message); 194 195 if (!message.is_empty()) 196 m_pending_dialog_text = message; 197 198 spin_event_loop_until_dialog_closed(m_client, m_pending_alert_response); 199} 200 201void Page::alert_closed() 202{ 203 if (m_pending_dialog == PendingDialog::Alert) { 204 m_pending_dialog = PendingDialog::None; 205 m_pending_alert_response = Empty {}; 206 m_pending_dialog_text.clear(); 207 } 208} 209 210bool Page::did_request_confirm(DeprecatedString const& message) 211{ 212 m_pending_dialog = PendingDialog::Confirm; 213 m_client.page_did_request_confirm(message); 214 215 if (!message.is_empty()) 216 m_pending_dialog_text = message; 217 218 return spin_event_loop_until_dialog_closed(m_client, m_pending_confirm_response); 219} 220 221void Page::confirm_closed(bool accepted) 222{ 223 if (m_pending_dialog == PendingDialog::Confirm) { 224 m_pending_dialog = PendingDialog::None; 225 m_pending_confirm_response = accepted; 226 m_pending_dialog_text.clear(); 227 } 228} 229 230DeprecatedString Page::did_request_prompt(DeprecatedString const& message, DeprecatedString const& default_) 231{ 232 m_pending_dialog = PendingDialog::Prompt; 233 m_client.page_did_request_prompt(message, default_); 234 235 if (!message.is_empty()) 236 m_pending_dialog_text = message; 237 238 return spin_event_loop_until_dialog_closed(m_client, m_pending_prompt_response); 239} 240 241void Page::prompt_closed(DeprecatedString response) 242{ 243 if (m_pending_dialog == PendingDialog::Prompt) { 244 m_pending_dialog = PendingDialog::None; 245 m_pending_prompt_response = move(response); 246 m_pending_dialog_text.clear(); 247 } 248} 249 250void Page::dismiss_dialog() 251{ 252 switch (m_pending_dialog) { 253 case PendingDialog::None: 254 break; 255 case PendingDialog::Alert: 256 m_client.page_did_request_accept_dialog(); 257 break; 258 case PendingDialog::Confirm: 259 case PendingDialog::Prompt: 260 m_client.page_did_request_dismiss_dialog(); 261 break; 262 } 263} 264 265void Page::accept_dialog() 266{ 267 switch (m_pending_dialog) { 268 case PendingDialog::None: 269 break; 270 case PendingDialog::Alert: 271 case PendingDialog::Confirm: 272 case PendingDialog::Prompt: 273 m_client.page_did_request_accept_dialog(); 274 break; 275 } 276} 277 278}