blitz-pdf 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
use crate::Document;
|
|
2
|
+
use blitz_traits::events::{
|
|
3
|
+
BlitzPointerEvent, BlitzPointerId, DomEvent, DomEventData, EventState, Point, PointerCoords,
|
|
4
|
+
UiEvent,
|
|
5
|
+
};
|
|
6
|
+
use blitz_traits::node_id::NodeId;
|
|
7
|
+
use std::collections::VecDeque;
|
|
8
|
+
|
|
9
|
+
pub trait EventHandler {
|
|
10
|
+
fn handle_event(
|
|
11
|
+
&mut self,
|
|
12
|
+
chain: &[NodeId],
|
|
13
|
+
event: &mut DomEvent,
|
|
14
|
+
doc: &mut dyn Document,
|
|
15
|
+
event_state: &mut EventState,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub struct NoopEventHandler;
|
|
20
|
+
impl EventHandler for NoopEventHandler {
|
|
21
|
+
fn handle_event(
|
|
22
|
+
&mut self,
|
|
23
|
+
_chain: &[NodeId],
|
|
24
|
+
_event: &mut DomEvent,
|
|
25
|
+
_doc: &mut dyn Document,
|
|
26
|
+
_event_state: &mut EventState,
|
|
27
|
+
) {
|
|
28
|
+
// Do nothing
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub struct EventDriver<'doc, Handler: EventHandler> {
|
|
33
|
+
doc: &'doc mut dyn Document,
|
|
34
|
+
handler: Handler,
|
|
35
|
+
queue: VecDeque<DomEvent>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> {
|
|
39
|
+
pub fn new(doc: &'doc mut dyn Document, handler: Handler) -> Self {
|
|
40
|
+
EventDriver {
|
|
41
|
+
doc,
|
|
42
|
+
handler,
|
|
43
|
+
queue: VecDeque::with_capacity(4),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn handle_pointer_move(&mut self, event: &BlitzPointerEvent) -> Option<NodeId> {
|
|
48
|
+
let mut doc = self.doc.inner_mut();
|
|
49
|
+
|
|
50
|
+
let prev_hover_node_id = doc.hover_node_id;
|
|
51
|
+
let changed = doc.set_hover_to(event.page_x(), event.page_y());
|
|
52
|
+
let hover_node_id = doc.hover_node_id;
|
|
53
|
+
|
|
54
|
+
drop(doc);
|
|
55
|
+
|
|
56
|
+
if !changed {
|
|
57
|
+
return prev_hover_node_id;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
self.dispatch_hover_change(prev_hover_node_id, hover_node_id, event);
|
|
61
|
+
|
|
62
|
+
hover_node_id
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// End hover when the pointer leaves the document, dispatching out/leave
|
|
66
|
+
/// events to the previously hovered chain
|
|
67
|
+
pub fn handle_pointer_leave(&mut self, event: &BlitzPointerEvent) {
|
|
68
|
+
let mut doc = self.doc.inner_mut();
|
|
69
|
+
let prev_hover_node_id = doc.hover_node_id;
|
|
70
|
+
doc.clear_hover();
|
|
71
|
+
drop(doc);
|
|
72
|
+
|
|
73
|
+
self.dispatch_hover_change(prev_hover_node_id, None, event);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn dispatch_hover_change(
|
|
77
|
+
&mut self,
|
|
78
|
+
prev_hover_node_id: Option<NodeId>,
|
|
79
|
+
hover_node_id: Option<NodeId>,
|
|
80
|
+
event: &BlitzPointerEvent,
|
|
81
|
+
) {
|
|
82
|
+
let doc = self.doc.inner();
|
|
83
|
+
let mut old_chain = prev_hover_node_id
|
|
84
|
+
.map(|id| doc.node_chain(id))
|
|
85
|
+
.unwrap_or_default();
|
|
86
|
+
let mut new_chain = hover_node_id
|
|
87
|
+
.map(|id| doc.node_chain(id))
|
|
88
|
+
.unwrap_or_default();
|
|
89
|
+
old_chain.reverse();
|
|
90
|
+
new_chain.reverse();
|
|
91
|
+
|
|
92
|
+
// Find the difference in the node chain of the last hovered objected and the newest
|
|
93
|
+
let old_len = old_chain.len();
|
|
94
|
+
let new_len = new_chain.len();
|
|
95
|
+
|
|
96
|
+
let first_difference_index = old_chain
|
|
97
|
+
.iter()
|
|
98
|
+
.zip(&new_chain)
|
|
99
|
+
.position(|(old, new)| old != new)
|
|
100
|
+
.unwrap_or_else(|| old_len.min(new_len));
|
|
101
|
+
|
|
102
|
+
drop(doc);
|
|
103
|
+
|
|
104
|
+
let is_mouse = event.is_mouse();
|
|
105
|
+
|
|
106
|
+
if let Some(target) = prev_hover_node_id {
|
|
107
|
+
self.handle_dom_event(DomEvent::new(
|
|
108
|
+
target,
|
|
109
|
+
DomEventData::PointerOut(event.clone()),
|
|
110
|
+
));
|
|
111
|
+
if is_mouse {
|
|
112
|
+
self.handle_dom_event(DomEvent::new(target, DomEventData::MouseOut(event.clone())));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Send an mouseleave event to all old elements on the chain
|
|
116
|
+
for node_id in old_chain
|
|
117
|
+
.get(first_difference_index..)
|
|
118
|
+
.unwrap_or(&[])
|
|
119
|
+
.iter()
|
|
120
|
+
{
|
|
121
|
+
self.handle_dom_event(DomEvent::new(
|
|
122
|
+
*node_id,
|
|
123
|
+
DomEventData::PointerLeave(event.clone()),
|
|
124
|
+
));
|
|
125
|
+
if is_mouse {
|
|
126
|
+
self.handle_dom_event(DomEvent::new(
|
|
127
|
+
*node_id,
|
|
128
|
+
DomEventData::MouseLeave(event.clone()),
|
|
129
|
+
));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let Some(target) = hover_node_id {
|
|
135
|
+
self.handle_dom_event(DomEvent::new(
|
|
136
|
+
target,
|
|
137
|
+
DomEventData::PointerOver(event.clone()),
|
|
138
|
+
));
|
|
139
|
+
|
|
140
|
+
if is_mouse {
|
|
141
|
+
self.handle_dom_event(DomEvent::new(
|
|
142
|
+
target,
|
|
143
|
+
DomEventData::MouseOver(event.clone()),
|
|
144
|
+
));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Send an mouseenter event to all new elements on the chain
|
|
148
|
+
for node_id in new_chain
|
|
149
|
+
.get(first_difference_index..)
|
|
150
|
+
.unwrap_or(&[])
|
|
151
|
+
.iter()
|
|
152
|
+
{
|
|
153
|
+
self.handle_dom_event(DomEvent::new(
|
|
154
|
+
*node_id,
|
|
155
|
+
DomEventData::PointerEnter(event.clone()),
|
|
156
|
+
));
|
|
157
|
+
|
|
158
|
+
if is_mouse {
|
|
159
|
+
self.handle_dom_event(DomEvent::new(
|
|
160
|
+
*node_id,
|
|
161
|
+
DomEventData::MouseEnter(event.clone()),
|
|
162
|
+
));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub fn handle_ui_event(&mut self, event: UiEvent) {
|
|
169
|
+
let doc = self.doc.inner();
|
|
170
|
+
|
|
171
|
+
// A non-hovering pointer (finger) ends hover once it is lifted or cancelled
|
|
172
|
+
let mut hover_end_event = None;
|
|
173
|
+
let mut hover_node_id = doc.hover_node_id;
|
|
174
|
+
let focussed_node_id = doc.focus_node_id;
|
|
175
|
+
let prev_hover = doc.hover_state();
|
|
176
|
+
drop(doc);
|
|
177
|
+
|
|
178
|
+
// A document the pointer has left has no say over the cursor
|
|
179
|
+
let is_leave = matches!(event, UiEvent::PointerLeave(_));
|
|
180
|
+
|
|
181
|
+
// Update document input state (hover, focus, active, etc)
|
|
182
|
+
match &event {
|
|
183
|
+
UiEvent::PointerMove(event) => {
|
|
184
|
+
hover_node_id = self.handle_pointer_move(event);
|
|
185
|
+
}
|
|
186
|
+
UiEvent::PointerDown(event) => {
|
|
187
|
+
hover_node_id = self.handle_pointer_move(event);
|
|
188
|
+
let mut doc = self.doc.inner_mut();
|
|
189
|
+
doc.active_node();
|
|
190
|
+
doc.set_mousedown_node_id(hover_node_id);
|
|
191
|
+
}
|
|
192
|
+
UiEvent::PointerUp(event) => {
|
|
193
|
+
hover_node_id = self.handle_pointer_move(event);
|
|
194
|
+
let mut doc = self.doc.inner_mut();
|
|
195
|
+
doc.unactive_node();
|
|
196
|
+
|
|
197
|
+
if event.is_primary && matches!(event.id, BlitzPointerId::Finger(_)) {
|
|
198
|
+
hover_end_event = Some(event.clone());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
UiEvent::PointerCancel(event) => {
|
|
202
|
+
hover_node_id = self.handle_pointer_move(event);
|
|
203
|
+
let mut doc = self.doc.inner_mut();
|
|
204
|
+
doc.unactive_node();
|
|
205
|
+
|
|
206
|
+
if event.is_primary && matches!(event.id, BlitzPointerId::Finger(_)) {
|
|
207
|
+
hover_end_event = Some(event.clone());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
UiEvent::PointerLeave(event) => {
|
|
211
|
+
self.handle_pointer_leave(event);
|
|
212
|
+
hover_node_id = None;
|
|
213
|
+
}
|
|
214
|
+
_ => {}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
let target = match event {
|
|
218
|
+
UiEvent::PointerMove(_) => hover_node_id,
|
|
219
|
+
UiEvent::PointerUp(_) => hover_node_id,
|
|
220
|
+
UiEvent::PointerDown(_) => hover_node_id,
|
|
221
|
+
UiEvent::PointerCancel(_) => hover_node_id,
|
|
222
|
+
UiEvent::PointerLeave(_) => hover_node_id,
|
|
223
|
+
UiEvent::Wheel(_) => hover_node_id,
|
|
224
|
+
UiEvent::KeyUp(_) => focussed_node_id,
|
|
225
|
+
UiEvent::KeyDown(_) => focussed_node_id,
|
|
226
|
+
UiEvent::Ime(_) => focussed_node_id,
|
|
227
|
+
UiEvent::AppleStandardKeybinding(_) => focussed_node_id,
|
|
228
|
+
};
|
|
229
|
+
// Fall back to the root element. A document without a root element (e.g. an
|
|
230
|
+
// empty iframe sub-document) has no event target, so there is nothing to do.
|
|
231
|
+
let Some(target) = target.or_else(|| self.doc.inner().try_root_element().map(|el| el.id))
|
|
232
|
+
else {
|
|
233
|
+
return;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
match event {
|
|
237
|
+
UiEvent::PointerMove(data) => {
|
|
238
|
+
self.handle_pointer_event(
|
|
239
|
+
target,
|
|
240
|
+
data,
|
|
241
|
+
DomEventData::PointerMove,
|
|
242
|
+
Some(DomEventData::MouseMove),
|
|
243
|
+
DomEventData::TouchMove,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
UiEvent::PointerUp(data) => {
|
|
247
|
+
self.handle_pointer_event(
|
|
248
|
+
target,
|
|
249
|
+
data,
|
|
250
|
+
DomEventData::PointerUp,
|
|
251
|
+
Some(DomEventData::MouseUp),
|
|
252
|
+
DomEventData::TouchEnd,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
UiEvent::PointerDown(data) => {
|
|
256
|
+
self.handle_pointer_event(
|
|
257
|
+
target,
|
|
258
|
+
data,
|
|
259
|
+
DomEventData::PointerDown,
|
|
260
|
+
Some(DomEventData::MouseDown),
|
|
261
|
+
DomEventData::TouchStart,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
UiEvent::PointerCancel(data) => {
|
|
265
|
+
// `pointercancel` has no mouse-compatibility event, but does
|
|
266
|
+
// generate a `touchcancel` for touch-like inputs.
|
|
267
|
+
self.handle_pointer_event(
|
|
268
|
+
target,
|
|
269
|
+
data,
|
|
270
|
+
DomEventData::PointerCancel,
|
|
271
|
+
None::<fn(BlitzPointerEvent) -> DomEventData>,
|
|
272
|
+
DomEventData::TouchCancel,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
// Out/leave events were dispatched by `handle_pointer_leave`
|
|
276
|
+
UiEvent::PointerLeave(_) => {}
|
|
277
|
+
UiEvent::Wheel(data) => {
|
|
278
|
+
self.handle_dom_event(DomEvent::new(target, DomEventData::Wheel(data)))
|
|
279
|
+
}
|
|
280
|
+
UiEvent::KeyUp(data) => {
|
|
281
|
+
self.handle_dom_event(DomEvent::new(target, DomEventData::KeyUp(data)))
|
|
282
|
+
}
|
|
283
|
+
UiEvent::KeyDown(data) => {
|
|
284
|
+
self.handle_dom_event(DomEvent::new(target, DomEventData::KeyDown(data)))
|
|
285
|
+
}
|
|
286
|
+
UiEvent::Ime(data) => {
|
|
287
|
+
self.handle_dom_event(DomEvent::new(target, DomEventData::Ime(data)))
|
|
288
|
+
}
|
|
289
|
+
UiEvent::AppleStandardKeybinding(data) => {
|
|
290
|
+
let mut dom_event =
|
|
291
|
+
DomEvent::new(target, DomEventData::AppleStandardKeybinding(data));
|
|
292
|
+
self.run_default_action(&mut dom_event);
|
|
293
|
+
self.process_queue();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Update document input state (hover, focus, active, etc)
|
|
298
|
+
if let Some(event) = hover_end_event {
|
|
299
|
+
self.handle_pointer_leave(&event);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Update the cursor once the event (including any forwarding to a
|
|
303
|
+
// sub-document) has been fully handled
|
|
304
|
+
let doc = self.doc.inner();
|
|
305
|
+
if !is_leave && doc.hover_state() != prev_hover {
|
|
306
|
+
doc.update_cursor();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
pub fn handle_dom_event(&mut self, event: DomEvent) {
|
|
311
|
+
self.queue.push_back(event);
|
|
312
|
+
self.process_queue();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fn handle_pointer_event(
|
|
316
|
+
&mut self,
|
|
317
|
+
target: NodeId,
|
|
318
|
+
data: BlitzPointerEvent,
|
|
319
|
+
make_ptr_data: impl FnOnce(BlitzPointerEvent) -> DomEventData,
|
|
320
|
+
make_mouse_data: Option<impl FnOnce(BlitzPointerEvent) -> DomEventData>,
|
|
321
|
+
make_touch_data: impl FnOnce(BlitzPointerEvent) -> DomEventData,
|
|
322
|
+
) {
|
|
323
|
+
let mut ptr_event = DomEvent::new(target, make_ptr_data(data.clone()));
|
|
324
|
+
let mut event_state = EventState::default();
|
|
325
|
+
event_state = self.run_handler_event(&mut ptr_event, event_state);
|
|
326
|
+
|
|
327
|
+
// Generate the corresponding compatibility event (mouse events for the
|
|
328
|
+
// mouse, touch events for fingers and pen/stylus input) and expose it to
|
|
329
|
+
// script. The default action is always run on the pointer event so that
|
|
330
|
+
// the shell layer and default actions remain pointer-based.
|
|
331
|
+
//
|
|
332
|
+
// `pointercancel` has no mouse equivalent, so `make_mouse_data` is `None`
|
|
333
|
+
// in that case and no mouse event is generated.
|
|
334
|
+
if !event_state.is_cancelled() {
|
|
335
|
+
if data.is_mouse() {
|
|
336
|
+
if let Some(make_mouse_data) = make_mouse_data {
|
|
337
|
+
let mut mouse_event = DomEvent::new(target, make_mouse_data(data));
|
|
338
|
+
event_state = self.run_handler_event(&mut mouse_event, event_state);
|
|
339
|
+
}
|
|
340
|
+
} else if data.is_finger() || data.is_pen() {
|
|
341
|
+
let mut touch_event = DomEvent::new(target, make_touch_data(data));
|
|
342
|
+
event_state = self.run_handler_event(&mut touch_event, event_state);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if !event_state.is_cancelled() {
|
|
347
|
+
self.run_default_action(&mut ptr_event);
|
|
348
|
+
}
|
|
349
|
+
self.process_queue();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fn process_queue(&mut self) {
|
|
353
|
+
while let Some(mut event) = self.queue.pop_front() {
|
|
354
|
+
let event_state = self.run_handler_event(&mut event, EventState::default());
|
|
355
|
+
if !event_state.is_cancelled() {
|
|
356
|
+
self.run_default_action(&mut event);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fn adjust_element_coords(
|
|
362
|
+
&self,
|
|
363
|
+
target: NodeId,
|
|
364
|
+
coords: &PointerCoords,
|
|
365
|
+
element: &mut Point<f32>,
|
|
366
|
+
) {
|
|
367
|
+
if let Some(rect) = self.doc.inner().get_client_bounding_rect(target) {
|
|
368
|
+
element.x = coords.client_x - rect.x as f32;
|
|
369
|
+
element.y = coords.client_y - rect.y as f32;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fn run_handler_event(
|
|
374
|
+
&mut self,
|
|
375
|
+
event: &mut DomEvent,
|
|
376
|
+
initial_event_state: EventState,
|
|
377
|
+
) -> EventState {
|
|
378
|
+
let chain = if event.bubbles {
|
|
379
|
+
let doc = self.doc.inner();
|
|
380
|
+
doc.node_chain(event.target)
|
|
381
|
+
} else {
|
|
382
|
+
vec![event.target]
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
match &mut event.data {
|
|
386
|
+
DomEventData::PointerMove(data)
|
|
387
|
+
| DomEventData::PointerDown(data)
|
|
388
|
+
| DomEventData::PointerUp(data)
|
|
389
|
+
| DomEventData::PointerCancel(data)
|
|
390
|
+
| DomEventData::PointerEnter(data)
|
|
391
|
+
| DomEventData::PointerLeave(data)
|
|
392
|
+
| DomEventData::PointerOver(data)
|
|
393
|
+
| DomEventData::PointerOut(data)
|
|
394
|
+
| DomEventData::MouseMove(data)
|
|
395
|
+
| DomEventData::MouseDown(data)
|
|
396
|
+
| DomEventData::MouseUp(data)
|
|
397
|
+
| DomEventData::MouseEnter(data)
|
|
398
|
+
| DomEventData::MouseLeave(data)
|
|
399
|
+
| DomEventData::MouseOver(data)
|
|
400
|
+
| DomEventData::MouseOut(data)
|
|
401
|
+
| DomEventData::TouchStart(data)
|
|
402
|
+
| DomEventData::TouchEnd(data)
|
|
403
|
+
| DomEventData::TouchMove(data)
|
|
404
|
+
| DomEventData::TouchCancel(data)
|
|
405
|
+
| DomEventData::Click(data)
|
|
406
|
+
| DomEventData::ContextMenu(data)
|
|
407
|
+
| DomEventData::DoubleClick(data) => {
|
|
408
|
+
self.adjust_element_coords(event.target, &data.coords, &mut data.element)
|
|
409
|
+
}
|
|
410
|
+
DomEventData::Wheel(data) => {
|
|
411
|
+
self.adjust_element_coords(event.target, &data.coords, &mut data.element)
|
|
412
|
+
}
|
|
413
|
+
_ => {}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let mut event_state = initial_event_state;
|
|
417
|
+
self.handler
|
|
418
|
+
.handle_event(&chain, event, self.doc, &mut event_state);
|
|
419
|
+
|
|
420
|
+
event_state
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fn run_default_action(&mut self, event: &mut DomEvent) {
|
|
424
|
+
let mut doc = self.doc.inner_mut();
|
|
425
|
+
doc.handle_dom_event(event, |new_evt| self.queue.push_back(new_evt));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
use blitz_traits::events::{BlitzFocusEvent, DomEvent, DomEventData};
|
|
2
|
+
|
|
3
|
+
use crate::BaseDocument;
|
|
4
|
+
|
|
5
|
+
pub(crate) fn generate_focus_events(
|
|
6
|
+
doc: &mut BaseDocument,
|
|
7
|
+
update_focus: &mut dyn FnMut(&mut BaseDocument),
|
|
8
|
+
dispatch_event: &mut dyn FnMut(DomEvent),
|
|
9
|
+
) {
|
|
10
|
+
// Update focus, tracking which node was focussed before and after
|
|
11
|
+
let old_focus = doc.get_focussed_node_id();
|
|
12
|
+
update_focus(doc);
|
|
13
|
+
let new_focus = doc.get_focussed_node_id();
|
|
14
|
+
|
|
15
|
+
if old_focus == new_focus {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if let Some(old_focus) = old_focus {
|
|
20
|
+
dispatch_event(DomEvent::new(
|
|
21
|
+
old_focus,
|
|
22
|
+
DomEventData::Blur(BlitzFocusEvent),
|
|
23
|
+
));
|
|
24
|
+
dispatch_event(DomEvent::new(
|
|
25
|
+
old_focus,
|
|
26
|
+
DomEventData::FocusOut(BlitzFocusEvent),
|
|
27
|
+
));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if let Some(new_focus) = new_focus {
|
|
31
|
+
dispatch_event(DomEvent::new(
|
|
32
|
+
new_focus,
|
|
33
|
+
DomEventData::Focus(BlitzFocusEvent),
|
|
34
|
+
));
|
|
35
|
+
dispatch_event(DomEvent::new(
|
|
36
|
+
new_focus,
|
|
37
|
+
DomEventData::FocusIn(BlitzFocusEvent),
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
use blitz_traits::events::{BlitzImeEvent, DomEvent};
|
|
2
|
+
|
|
3
|
+
use crate::BaseDocument;
|
|
4
|
+
|
|
5
|
+
pub(crate) fn handle_ime_event<F: FnMut(DomEvent)>(
|
|
6
|
+
doc: &mut BaseDocument,
|
|
7
|
+
event: BlitzImeEvent,
|
|
8
|
+
dispatch_event: F,
|
|
9
|
+
) {
|
|
10
|
+
if let Some(node_id) = doc.focus_node_id {
|
|
11
|
+
let node = &mut doc.nodes[node_id];
|
|
12
|
+
let text_input_data = node
|
|
13
|
+
.data
|
|
14
|
+
.downcast_element_mut()
|
|
15
|
+
.and_then(|el| el.text_input_data_mut());
|
|
16
|
+
if let Some(input_data) = text_input_data {
|
|
17
|
+
let generated_event = input_data.apply_ime_event(
|
|
18
|
+
&mut doc.font_ctx.lock().unwrap(),
|
|
19
|
+
&mut doc.layout_ctx,
|
|
20
|
+
event,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if let Some(generated_event) = generated_event {
|
|
24
|
+
doc.apply_generated_text_input_event(node_id, generated_event, dispatch_event);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[cfg(feature = "tracing")]
|
|
28
|
+
tracing::debug!(node_id = ?node_id, "Sent ime event");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
use crate::events::focus::generate_focus_events;
|
|
2
|
+
use crate::{BaseDocument, node::GeneratedTextInputEvent, util::ACTION_MOD};
|
|
3
|
+
use blitz_traits::node_id::NodeId;
|
|
4
|
+
use blitz_traits::{
|
|
5
|
+
SmolStr,
|
|
6
|
+
events::{BlitzInputEvent, BlitzKeyEvent, DomEvent, DomEventData},
|
|
7
|
+
};
|
|
8
|
+
use keyboard_types::{Key, Modifiers};
|
|
9
|
+
use markup5ever::local_name;
|
|
10
|
+
|
|
11
|
+
pub(super) enum KeyboardOrTextInputEvent {
|
|
12
|
+
KeyPress(BlitzKeyEvent),
|
|
13
|
+
AppleStandardKeyBinding(SmolStr),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub(crate) fn handle_key_or_input_event<F: FnMut(DomEvent)>(
|
|
17
|
+
doc: &mut BaseDocument,
|
|
18
|
+
target: NodeId,
|
|
19
|
+
event: KeyboardOrTextInputEvent,
|
|
20
|
+
mut dispatch_event: F,
|
|
21
|
+
) {
|
|
22
|
+
if let KeyboardOrTextInputEvent::KeyPress(event) = &event {
|
|
23
|
+
if event.key == Key::Tab {
|
|
24
|
+
let backwards = event.modifiers.contains(Modifiers::SHIFT);
|
|
25
|
+
let old_focus = doc.focus_node_id;
|
|
26
|
+
generate_focus_events(
|
|
27
|
+
doc,
|
|
28
|
+
&mut |doc| {
|
|
29
|
+
if backwards {
|
|
30
|
+
doc.focus_prev_node();
|
|
31
|
+
} else {
|
|
32
|
+
doc.focus_next_node();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
&mut dispatch_event,
|
|
36
|
+
);
|
|
37
|
+
if doc.focus_node_id != old_focus {
|
|
38
|
+
doc.shell_provider.request_redraw();
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle copy (Ctrl+C/Cmd+C) for text selection when no text input is focused
|
|
44
|
+
if event.state.is_pressed() {
|
|
45
|
+
let action_mod = event.modifiers.contains(ACTION_MOD);
|
|
46
|
+
if action_mod {
|
|
47
|
+
if let Key::Character(c) = &event.key {
|
|
48
|
+
if c.to_lowercase() == "c" {
|
|
49
|
+
// Check if we have a text selection (and no focused text input)
|
|
50
|
+
let has_focused_text_input = doc.focus_node_id.is_some_and(|id| {
|
|
51
|
+
doc.get_node(id)
|
|
52
|
+
.and_then(|n| n.element_data())
|
|
53
|
+
.is_some_and(|e| e.text_input_data().is_some())
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if !has_focused_text_input {
|
|
57
|
+
if let Some(text) = doc.get_selected_text() {
|
|
58
|
+
let _ = doc.shell_provider.set_clipboard_text(text);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if let Some(node_id) = doc.focus_node_id {
|
|
69
|
+
if target != node_id {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let node = &mut doc.nodes[node_id];
|
|
74
|
+
let Some(element_data) = node.element_data_mut() else {
|
|
75
|
+
return;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if let Some(input_data) = element_data.text_input_data_mut() {
|
|
79
|
+
let generated_event = match event {
|
|
80
|
+
KeyboardOrTextInputEvent::KeyPress(blitz_key_event) => input_data
|
|
81
|
+
.apply_keypress_event(
|
|
82
|
+
&mut doc.font_ctx.lock().unwrap(),
|
|
83
|
+
&mut doc.layout_ctx,
|
|
84
|
+
&*doc.shell_provider,
|
|
85
|
+
blitz_key_event,
|
|
86
|
+
),
|
|
87
|
+
KeyboardOrTextInputEvent::AppleStandardKeyBinding(command) => input_data
|
|
88
|
+
.apply_apple_standard_keybinding(
|
|
89
|
+
&mut doc.font_ctx.lock().unwrap(),
|
|
90
|
+
&mut doc.layout_ctx,
|
|
91
|
+
&*doc.shell_provider,
|
|
92
|
+
&command,
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if let Some(generated_event) = generated_event {
|
|
97
|
+
doc.apply_generated_text_input_event(node_id, generated_event, dispatch_event);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl BaseDocument {
|
|
104
|
+
pub(crate) fn apply_generated_text_input_event<F: FnMut(DomEvent)>(
|
|
105
|
+
&mut self,
|
|
106
|
+
node_id: NodeId,
|
|
107
|
+
event: GeneratedTextInputEvent,
|
|
108
|
+
mut dispatch_event: F,
|
|
109
|
+
) {
|
|
110
|
+
let node = &mut self.nodes[node_id];
|
|
111
|
+
let element_data = node
|
|
112
|
+
.element_data_mut()
|
|
113
|
+
.expect("apply_generated_text_input_event called on a node that is not an element");
|
|
114
|
+
let input_data = element_data
|
|
115
|
+
.text_input_data_mut()
|
|
116
|
+
.expect("apply_generated_text_input_event called on a node that is not a text input");
|
|
117
|
+
|
|
118
|
+
match event {
|
|
119
|
+
GeneratedTextInputEvent::Input => {
|
|
120
|
+
let value = input_data.editor.raw_text().to_string();
|
|
121
|
+
dispatch_event(DomEvent::new(
|
|
122
|
+
node_id,
|
|
123
|
+
DomEventData::Input(BlitzInputEvent { value }),
|
|
124
|
+
));
|
|
125
|
+
self.shell_provider.request_redraw();
|
|
126
|
+
}
|
|
127
|
+
GeneratedTextInputEvent::Select | GeneratedTextInputEvent::PreEditChange => {
|
|
128
|
+
self.shell_provider.request_redraw();
|
|
129
|
+
}
|
|
130
|
+
GeneratedTextInputEvent::Submit => {
|
|
131
|
+
// TODO: Generate submit event that can be handled by script
|
|
132
|
+
implicit_form_submission(self, node_id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#field-that-blocks-implicit-submission
|
|
139
|
+
fn implicit_form_submission(doc: &BaseDocument, text_target: NodeId) {
|
|
140
|
+
let Some(form_owner_id) = doc.controls_to_form.get(&text_target) else {
|
|
141
|
+
return;
|
|
142
|
+
};
|
|
143
|
+
if doc
|
|
144
|
+
.controls_to_form
|
|
145
|
+
.iter()
|
|
146
|
+
.filter(|(_control_id, form_id)| *form_id == form_owner_id)
|
|
147
|
+
.filter_map(|(control_id, _)| doc.nodes[*control_id].element_data())
|
|
148
|
+
.filter(|element_data| {
|
|
149
|
+
element_data.attr(local_name!("type")).is_some_and(|t| {
|
|
150
|
+
matches!(
|
|
151
|
+
t,
|
|
152
|
+
"text"
|
|
153
|
+
| "search"
|
|
154
|
+
| "email"
|
|
155
|
+
| "url"
|
|
156
|
+
| "tel"
|
|
157
|
+
| "password"
|
|
158
|
+
| "date"
|
|
159
|
+
| "month"
|
|
160
|
+
| "week"
|
|
161
|
+
| "time"
|
|
162
|
+
| "datetime-local"
|
|
163
|
+
| "number"
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
.count()
|
|
168
|
+
> 1
|
|
169
|
+
{
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
doc.submit_form(*form_owner_id, *form_owner_id);
|
|
174
|
+
}
|