Serenity Operating System
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}