Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

ui: use the mediacenter video player for top level video documents

Signed-off-by: webbeef <me@webbeef.org>

webbeef e034eca6 cfbead28

+295 -110
+9
crates/beaver_shell/src/prefs.rs
··· 32 32 /// Enable content blocking (ad blocker) 33 33 #[serde(default = "default_true")] 34 34 pub content_blocking: bool, 35 + /// URL of the custom video player page (empty string disables) 36 + #[serde(default = "default_video_player_url")] 37 + pub video_player_url: String, 35 38 } 36 39 37 40 fn default_start_url() -> String { ··· 50 53 "duckduckgo".to_string() 51 54 } 52 55 56 + fn default_video_player_url() -> String { 57 + "beaver://system/mediacenter/video_player.html".to_string() 58 + } 59 + 53 60 fn default_false() -> bool { 54 61 false 55 62 } ··· 69 76 mobile_simulation: false, 70 77 media_center_mode: false, 71 78 content_blocking: true, 79 + video_player_url: String::new(), 72 80 }; 73 81 } 74 82 ··· 83 91 mobile_simulation: false, 84 92 media_center_mode: false, 85 93 content_blocking: true, 94 + video_player_url: default_video_player_url(), 86 95 } 87 96 } 88 97 }
+43 -19
patches/components/constellation/constellation.rs.patch
··· 471 471 ); 472 472 }, 473 473 #[cfg(feature = "webgpu")] 474 - @@ -2146,6 +2356,962 @@ 474 + @@ -2146,9 +2356,986 @@ 475 475 } 476 476 }, 477 477 }, ··· 584 584 + .send(PaintMessage::ForwardInputEventToEmbeddedWebView( 585 585 + embedded_webview_id, 586 586 + event, 587 + + )); 588 + + } 589 + + }, 590 + + ScriptToConstellationMessage::ForwardInputEventToIframe( 591 + + webview_id, 592 + + pipeline_id, 593 + + event, 594 + + ) => { 595 + + if let Some(pipeline) = self.pipelines.get(&pipeline_id) { 596 + + let constellation_event = ConstellationInputEvent { 597 + + event, 598 + + hit_test_result: None, 599 + + pressed_mouse_buttons: 0, 600 + + active_keyboard_modifiers: Modifiers::empty(), 601 + + }; 602 + + let _ = pipeline 603 + + .event_loop 604 + + .send(ScriptThreadMessage::SendInputEvent( 605 + + webview_id, 606 + + pipeline_id, 607 + + constellation_event, 587 608 + )); 588 609 + } 589 610 + }, ··· 1112 1133 + let _ = callback.send(None); 1113 1134 + } 1114 1135 + }, 1115 - + } 1116 - + } 1117 - + 1136 + } 1137 + } 1138 + 1118 1139 + fn handle_pairing_event(&mut self, event: PairingEvent) { 1119 1140 + if let PairingEvent::MessageReceived { ref from, ref data } = event { 1120 1141 + debug!("P2P message received from {from}, {} bytes", data.len()); ··· 1431 1452 + if self.embedder_error_listeners.contains(&event_loop.id()) { 1432 1453 + let _ = event_loop.send(ScriptThreadMessage::DispatchPairingEvent(event.clone())); 1433 1454 + } 1434 - } 1435 - } 1436 - 1437 - @@ -2465,6 +3631,55 @@ 1455 + + } 1456 + + } 1457 + + 1458 + /// Check the origin of a message against that of the pipeline it came from. 1459 + /// Note: this is still limited as a security check, 1460 + /// see <https://github.com/servo/servo/issues/11722> 1461 + @@ -2465,6 +3652,55 @@ 1438 1462 TransferState::TransferInProgress(queue) => queue.push_back(task), 1439 1463 TransferState::CompletionFailed(queue) => queue.push_back(task), 1440 1464 TransferState::CompletionRequested(_, queue) => queue.push_back(task), ··· 1490 1514 } 1491 1515 } 1492 1516 1493 - @@ -3364,6 +4579,40 @@ 1517 + @@ -3364,6 +4600,40 @@ 1494 1518 /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable> 1495 1519 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) { 1496 1520 debug!("{webview_id}: Closing"); ··· 1531 1555 let browsing_context_id = BrowsingContextId::from(webview_id); 1532 1556 // Step 5. Remove traversable from the user agent's top-level traversable set. 1533 1557 let browsing_context = 1534 - @@ -3640,8 +4889,27 @@ 1558 + @@ -3640,8 +4910,27 @@ 1535 1559 opener_webview_id, 1536 1560 opener_pipeline_id, 1537 1561 response_sender, ··· 1559 1583 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else { 1560 1584 warn!("Failed to create channel"); 1561 1585 let _ = response_sender.send(None); 1562 - @@ -3740,6 +5008,395 @@ 1586 + @@ -3740,6 +5029,395 @@ 1563 1587 }); 1564 1588 } 1565 1589 ··· 1955 1979 #[servo_tracing::instrument(skip_all)] 1956 1980 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) { 1957 1981 let Some(pipeline) = self.pipelines.get(&pipeline_id) else { 1958 - @@ -4289,7 +5946,7 @@ 1982 + @@ -4289,7 +5967,7 @@ 1959 1983 }, 1960 1984 }; 1961 1985 ··· 1964 1988 match self.browsing_contexts.get_mut(&browsing_context_id) { 1965 1989 Some(browsing_context) => { 1966 1990 let old_pipeline_id = browsing_context.pipeline_id; 1967 - @@ -4298,6 +5955,7 @@ 1991 + @@ -4298,6 +5976,7 @@ 1968 1992 old_pipeline_id, 1969 1993 browsing_context.parent_pipeline_id, 1970 1994 browsing_context.webview_id, ··· 1972 1996 ) 1973 1997 }, 1974 1998 None => { 1975 - @@ -4307,6 +5965,15 @@ 1999 + @@ -4307,6 +5986,15 @@ 1976 2000 1977 2001 self.unload_document(old_pipeline_id); 1978 2002 ··· 1988 2012 if let Some(new_pipeline) = self.pipelines.get(&new_pipeline_id) { 1989 2013 if let Some(ref chan) = self.devtools_sender { 1990 2014 let state = NavigationState::Start(new_pipeline.url.clone()); 1991 - @@ -4872,7 +6539,7 @@ 2015 + @@ -4872,7 +6560,7 @@ 1992 2016 } 1993 2017 1994 2018 #[servo_tracing::instrument(skip_all)] ··· 1997 2021 // Send a flat projection of the history to embedder. 1998 2022 // The final vector is a concatenation of the URLs of the past 1999 2023 // entries, the current entry and the future entries. 2000 - @@ -4976,9 +6643,22 @@ 2024 + @@ -4976,9 +6664,22 @@ 2001 2025 self.constellation_to_embedder_proxy 2002 2026 .send(ConstellationToEmbedderMsg::HistoryChanged( 2003 2027 webview_id, ··· 2021 2045 } 2022 2046 2023 2047 #[servo_tracing::instrument(skip_all)] 2024 - @@ -4997,7 +6677,7 @@ 2048 + @@ -4997,7 +6698,7 @@ 2025 2049 } 2026 2050 } 2027 2051 ··· 2030 2054 match self.browsing_contexts.get_mut(&change.browsing_context_id) { 2031 2055 Some(browsing_context) => { 2032 2056 debug!("Adding pipeline to existing browsing context."); 2033 - @@ -5004,11 +6684,15 @@ 2057 + @@ -5004,11 +6705,15 @@ 2034 2058 let old_pipeline_id = browsing_context.pipeline_id; 2035 2059 browsing_context.pipelines.insert(change.new_pipeline_id); 2036 2060 browsing_context.update_current_entry(change.new_pipeline_id); ··· 2048 2072 }, 2049 2073 }; 2050 2074 2051 - @@ -5016,6 +6700,18 @@ 2075 + @@ -5016,6 +6721,18 @@ 2052 2076 self.unload_document(old_pipeline_id); 2053 2077 } 2054 2078
+4 -1
patches/components/constellation/tracing.rs.patch
··· 36 36 Self::ActivateDocument => target!("ActivateDocument"), 37 37 Self::SetDocumentState(..) => target!("SetDocumentState"), 38 38 Self::SetFinalUrl(..) => target!("SetFinalUrl"), 39 - @@ -193,6 +201,74 @@ 39 + @@ -193,6 +201,77 @@ 40 40 Self::TriggerGarbageCollection => target!("TriggerGarbageCollection"), 41 41 Self::AcquireWakeLock(..) => target!("AcquireWakeLock"), 42 42 Self::ReleaseWakeLock(..) => target!("ReleaseWakeLock"), ··· 62 62 + }, 63 63 + Self::ForwardEventToEmbeddedWebView(..) => { 64 64 + target!("ForwardEventToEmbeddedWebView") 65 + + }, 66 + + Self::ForwardInputEventToIframe(..) => { 67 + + target!("ForwardInputEventToIframe") 65 68 + }, 66 69 + Self::EmbeddedWebViewControlResponse(..) => { 67 70 + target!("EmbeddedWebViewControlResponse")
+72 -56
patches/components/script/dom/document/document_event_handler.rs.patch
··· 146 146 } 147 147 } 148 148 149 - @@ -571,6 +643,198 @@ 149 + @@ -571,6 +643,214 @@ 150 150 } 151 151 } 152 152 ··· 158 158 + hit_test_result: &HitTestResult, 159 159 + input_event: &ConstellationInputEvent, 160 160 + ) -> bool { 161 - + // Walk up from the hit node to find if any ancestor is an embedded iframe 162 - + // We use ShadowIncluding::Yes because embedded iframes may be inside shadow DOM 161 + + // Walk up from the hit node to find if any ancestor is an iframe 162 + + // We use ShadowIncluding::Yes because iframes may be inside shadow DOM 163 163 + // (e.g., inside a custom element like <web-view>) 164 - + let Some(embedded_iframe) = hit_test_result 164 + + let Some(iframe) = hit_test_result 165 165 + .node 166 166 + .inclusive_ancestors(ShadowIncluding::Yes) 167 - + .find_map(|ancestor| { 168 - + let iframe = DomRoot::downcast::<HTMLIFrameElement>(ancestor)?; 169 - + if iframe.is_embedded_webview() { 170 - + Some(iframe) 171 - + } else { 172 - + None 173 - + } 174 - + }) 167 + + .find_map(|ancestor| DomRoot::downcast::<HTMLIFrameElement>(ancestor)) 175 168 + else { 176 169 + return false; 177 170 + }; 178 171 + 179 - + // Get the embedded webview ID 180 - + let Some(embedded_webview_id) = embedded_iframe.embedded_webview_id() else { 181 - + return false; 172 + + // Determine how to forward: embedded webview or regular cross-origin iframe 173 + + let is_embedded = iframe.is_embedded_webview(); 174 + + let embedded_webview_id = if is_embedded { 175 + + iframe.embedded_webview_id() 176 + + } else { 177 + + None 182 178 + }; 179 + + let iframe_pipeline_id = iframe.pipeline_id(); 180 + + 181 + + // Must have either an embedded webview ID or a pipeline ID to forward to 182 + + if embedded_webview_id.is_none() && iframe_pipeline_id.is_none() { 183 + + return false; 184 + + } 183 185 + 184 186 + // Get the iframe's border box to transform coordinates from parent to embedded viewport. 185 - + // The border box origin is in document coordinates (relative to initial containing block). 186 - + let Some(iframe_border_box) = embedded_iframe.upcast::<Node>().border_box() else { 187 + + let Some(iframe_border_box) = iframe.upcast::<Node>().border_box() else { 187 188 + return false; 188 189 + }; 189 190 + ··· 238 239 + other => other, 239 240 + }; 240 241 + 241 - + // Create the event with ID to forward to the embedded webview 242 + + // Create the event with ID to forward 242 243 + let event_with_id = InputEventAndId::from(transformed_event); 243 244 + 244 - + // Forward the event to the embedded webview via the Constellation 245 - + self.window.send_to_constellation( 246 - + ScriptToConstellationMessage::ForwardEventToEmbeddedWebView( 247 - + embedded_webview_id, 248 - + event_with_id, 249 - + ), 250 - + ); 245 + + // Forward the event: embedded webview via constellation, regular iframe via pipeline 246 + + if let Some(webview_id) = embedded_webview_id { 247 + + self.window.send_to_constellation( 248 + + ScriptToConstellationMessage::ForwardEventToEmbeddedWebView( 249 + + webview_id, 250 + + event_with_id, 251 + + ), 252 + + ); 253 + + } else if let Some(pipeline_id) = iframe_pipeline_id { 254 + + let webview_id = self.window.webview_id(); 255 + + self.window.send_to_constellation( 256 + + ScriptToConstellationMessage::ForwardInputEventToIframe( 257 + + webview_id, 258 + + pipeline_id, 259 + + event_with_id, 260 + + ), 261 + + ); 262 + + } 251 263 + 252 264 + // Track forwarded touches so subsequent events for the same touch go to the same webview. 253 - + // This is important because the hit test might return a different result for touchmove/touchend. 254 265 + if let InputEvent::Touch(touch) = &input_event.event.event { 255 266 + if touch.event_type == TouchEventType::Down { 256 - + self.forwarded_touches 257 - + .borrow_mut() 258 - + .push((touch.touch_id, embedded_webview_id)); 267 + + if let Some(webview_id) = embedded_webview_id { 268 + + self.forwarded_touches 269 + + .borrow_mut() 270 + + .push((touch.touch_id, webview_id)); 271 + + } 259 272 + } 260 273 + } 261 274 + 262 - + // Notify the parent iframe element that input was received by the embedded webview, 263 - + // but only for "activation" events (mousedown/touchstart), not for moves or other events. 264 - + // This allows the parent document to track which embedded webview is "active". 265 - + let is_activation_event = match &input_event.event.event { 266 - + InputEvent::MouseButton(mouse_button) => mouse_button.action == MouseButtonAction::Down, 267 - + InputEvent::Touch(touch) => touch.event_type == TouchEventType::Down, 268 - + _ => false, 269 - + }; 270 - + if is_activation_event { 271 - + embedded_iframe.dispatch_embedded_webview_event( 272 - + EmbeddedWebViewEventType::InputReceived, 273 - + CanGc::deprecated_note(), 274 - + ); 275 + + // Notify the parent iframe element that input was received, 276 + + // but only for embedded webviews and activation events. 277 + + if is_embedded { 278 + + let is_activation_event = match &input_event.event.event { 279 + + InputEvent::MouseButton(mouse_button) => { 280 + + mouse_button.action == MouseButtonAction::Down 281 + + }, 282 + + InputEvent::Touch(touch) => touch.event_type == TouchEventType::Down, 283 + + _ => false, 284 + + }; 285 + + if is_activation_event { 286 + + iframe.dispatch_embedded_webview_event( 287 + + EmbeddedWebViewEventType::InputReceived, 288 + + CanGc::deprecated_note(), 289 + + ); 290 + + } 275 291 + } 276 292 + 277 293 + true ··· 345 361 /// <https://w3c.github.io/uievents/#handle-native-mouse-move> 346 362 fn handle_native_mouse_move_event( 347 363 &self, 348 - @@ -589,6 +853,57 @@ 364 + @@ -589,6 +869,57 @@ 349 365 return; 350 366 } 351 367 ··· 403 419 // Update the cursor when the mouse moves, if it has changed. 404 420 self.set_cursor(Some(hit_test_result.cursor)); 405 421 406 - @@ -824,6 +1139,12 @@ 422 + @@ -824,6 +1155,12 @@ 407 423 return; 408 424 }; 409 425 ··· 416 432 debug!( 417 433 "{:?}: at {:?}", 418 434 event.action, hit_test_result.point_in_frame 419 - @@ -941,7 +1262,12 @@ 435 + @@ -941,7 +1278,12 @@ 420 436 // Step 9. If mbutton is the secondary mouse button, then 421 437 // Maybe show context menu with native, target. 422 438 if let MouseButton::Right = event.button { ··· 430 446 } 431 447 }, 432 448 // https://w3c.github.io/pointerevents/#dfn-handle-native-mouse-up 433 - @@ -1075,8 +1401,29 @@ 449 + @@ -1075,8 +1417,29 @@ 434 450 cx: &mut js::context::JSContext, 435 451 target: &EventTarget, 436 452 hit_test_result: &HitTestResult, ··· 461 477 // <https://w3c.github.io/pointerevents/#contextmenu> 462 478 let menu_event = PointerEvent::new( 463 479 &self.window, // window 464 - @@ -1090,25 +1437,25 @@ 480 + @@ -1090,25 +1453,25 @@ 465 481 hit_test_result 466 482 .point_relative_to_initial_containing_block 467 483 .to_i32(), ··· 506 522 CanGc::from_cx(cx), 507 523 ); 508 524 menu_event.upcast::<Event>().set_composed(true); 509 - @@ -1127,6 +1474,89 @@ 525 + @@ -1127,6 +1490,89 @@ 510 526 }; 511 527 } 512 528 ··· 596 612 fn handle_touch_event( 597 613 &self, 598 614 cx: &mut JSContext, 599 - @@ -1133,6 +1563,29 @@ 615 + @@ -1133,6 +1579,29 @@ 600 616 event: EmbedderTouchEvent, 601 617 input_event: &ConstellationInputEvent, 602 618 ) -> InputEventResult { ··· 626 642 // Ignore all incoming events without a hit test. 627 643 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else { 628 644 self.update_active_touch_points_when_early_return(event); 629 - @@ -1139,6 +1592,16 @@ 645 + @@ -1139,6 +1608,16 @@ 630 646 return Default::default(); 631 647 }; 632 648 ··· 643 659 let TouchId(identifier) = event.touch_id; 644 660 645 661 let Some(element) = hit_test_result 646 - @@ -1274,6 +1737,10 @@ 662 + @@ -1274,6 +1753,10 @@ 647 663 // <https://html.spec.whatwg.org/multipage/#selector-active> 648 664 // If the element is being actively pointed at the element is being activated. 649 665 self.element_for_activation(element).set_active_state(true); ··· 654 670 (current_target, pointer_touch) 655 671 }, 656 672 _ => { 657 - @@ -1306,10 +1773,31 @@ 673 + @@ -1306,10 +1789,31 @@ 658 674 CanGc::from_cx(cx), 659 675 ); 660 676 ··· 687 703 }, 688 704 TouchEventType::Up | TouchEventType::Cancel => { 689 705 active_touch_points.swap_remove(index); 690 - @@ -1317,6 +1805,17 @@ 706 + @@ -1317,6 +1821,17 @@ 691 707 // <https://html.spec.whatwg.org/multipage/#selector-active> 692 708 // If the element is being actively pointed at the element is being activated. 693 709 self.element_for_activation(element).set_active_state(false); ··· 705 721 }, 706 722 TouchEventType::Down => unreachable!("Should have been handled above"), 707 723 } 708 - @@ -1364,6 +1863,19 @@ 724 + @@ -1364,6 +1879,19 @@ 709 725 ); 710 726 let event = touch_event.upcast::<Event>(); 711 727 event.fire(&touch_dispatch_target, CanGc::from_cx(cx)); ··· 725 741 event.flags().into() 726 742 } 727 743 728 - @@ -1498,6 +2010,16 @@ 744 + @@ -1498,6 +2026,16 @@ 729 745 return Default::default(); 730 746 }; 731 747 ··· 742 758 let Some(el) = hit_test_result 743 759 .node 744 760 .inclusive_ancestors(ShadowIncluding::Yes) 745 - @@ -1959,6 +2481,28 @@ 761 + @@ -1959,6 +2497,28 @@ 746 762 return; 747 763 } 748 764
+60
patches/components/script/dom/servoparser/mod.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -36,6 +36,8 @@ 4 + use servo_base::cross_process_instant::CrossProcessInstant; 5 + use servo_base::id::{PipelineId, WebViewId}; 6 + use servo_config::pref; 7 + +use servo_config::pref_util::PrefValue; 8 + +use servo_config::prefs::get_embedder_pref; 9 + use servo_constellation_traits::{LoadOrigin, TargetSnapshotParams}; 10 + use servo_url::{MutableOrigin, ServoUrl}; 11 + use style::context::QuirksMode as ServoQuirksMode; 12 + @@ -1151,7 +1153,7 @@ 13 + self.is_synthesized_document = true; 14 + parser.last_chunk_received.set(true); 15 + // Step 3. Populate with html/head/body given document. 16 + - let page = "<html><body></body></html>".into(); 17 + + let page = "<html><body style='padding:0;margin:0;'></body></html>".into(); 18 + parser.push_string_input_chunk(page); 19 + parser.parse_sync(cx); 20 + 21 + @@ -1185,6 +1187,39 @@ 22 + audio.SetControls(cx, true); 23 + audio.SetSrc(cx, USVString(self.url.to_string())); 24 + DomRoot::upcast::<Node>(audio) 25 + + } else if let Some(PrefValue::Str(player_url)) = 26 + + get_embedder_pref("beaver.video_player_url") 27 + + .filter(|v| matches!(v, PrefValue::Str(s) if !s.is_empty())) 28 + + { 29 + + let title = self 30 + + .url 31 + + .path_segments() 32 + + .and_then(|s| s.last()) 33 + + .unwrap_or("Video"); 34 + + let query = url::form_urlencoded::Serializer::new(String::new()) 35 + + .append_pair("src", self.url.as_str()) 36 + + .append_pair("title", title) 37 + + .finish(); 38 + + let iframe = Element::create( 39 + + cx, 40 + + QualName::new(None, ns!(html), local_name!("iframe")), 41 + + None, 42 + + doc, 43 + + ElementCreator::ParserCreated(1), 44 + + CustomElementCreationMode::Asynchronous, 45 + + None, 46 + + ); 47 + + iframe.set_string_attribute( 48 + + cx, 49 + + &local_name!("src"), 50 + + format!("{player_url}?{query}").into(), 51 + + ); 52 + + iframe.set_string_attribute( 53 + + cx, 54 + + &local_name!("style"), 55 + + "width:100vw;height:100vh;border:none".into(), 56 + + ); 57 + + DomRoot::upcast::<Node>(iframe) 58 + } else { 59 + let video = Element::create( 60 + cx,
+40 -20
patches/components/script/dom/window.rs.patch
··· 22 22 use js::realm::{AutoRealm, CurrentRealm}; 23 23 use js::rust::wrappers::JS_DefineProperty; 24 24 use js::rust::{ 25 - @@ -190,7 +192,7 @@ 25 + @@ -165,7 +167,9 @@ 26 + use crate::dom::mediaquerylistevent::MediaQueryListEvent; 27 + use crate::dom::messageevent::MessageEvent; 28 + use crate::dom::navigator::Navigator; 29 + -use crate::dom::node::{Node, NodeDamage, NodeTraits, from_untrusted_node_address}; 30 + +use crate::dom::node::{ 31 + + Node, NodeDamage, NodeTraits, ShadowIncluding, from_untrusted_node_address, 32 + +}; 33 + use crate::dom::performance::performance::Performance; 34 + use crate::dom::promise::Promise; 35 + use crate::dom::reporting::reportingendpoint::{ReportingEndpoint, SendReportsToEndpoints}; 36 + @@ -190,7 +194,7 @@ 26 37 use crate::messaging::{MainThreadScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; 27 38 use crate::microtask::{Microtask, UserMicrotask}; 28 39 use crate::network_listener::{ResourceTimingListener, submit_timing}; ··· 31 42 use crate::script_runtime::{CanGc, JSContext as SafeJSContext, Runtime}; 32 43 use crate::script_thread::ScriptThread; 33 44 use crate::script_window_proxies::ScriptWindowProxies; 34 - @@ -1150,12 +1152,22 @@ 45 + @@ -1150,12 +1154,22 @@ 35 46 36 47 let (sender, receiver) = 37 48 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap(); ··· 55 66 receiver.recv().unwrap_or_else(|_| { 56 67 // If the receiver is closed, we assume the dialog was cancelled. 57 68 debug!("Alert dialog was cancelled or failed to show."); 58 - @@ -1183,13 +1195,22 @@ 69 + @@ -1183,13 +1197,22 @@ 59 70 // the user to respond with a positive or negative response. 60 71 let (sender, receiver) = 61 72 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap(); ··· 79 90 // Step 5: Let userPromptHandler be WebDriver BiDi user prompt opened with this, 80 91 // "confirm", and message. 81 92 // 82 - @@ -1234,6 +1255,7 @@ 93 + @@ -1234,6 +1257,7 @@ 83 94 // defaulted to the value given by default. 84 95 let (sender, receiver) = 85 96 ProfiledGenericChannel::channel(self.global().time_profiler_chan().clone()).unwrap(); ··· 87 98 let dialog = SimpleDialogRequest::Prompt { 88 99 id: self.Document().embedder_controls().next_control_id(), 89 100 message: message.to_string(), 90 - @@ -1240,8 +1262,16 @@ 101 + @@ -1240,8 +1264,16 @@ 91 102 default: default.to_string(), 92 103 response_sender: sender, 93 104 }; ··· 105 116 // Step 6: Let userPromptHandler be WebDriver BiDi user prompt opened with this, 106 117 // "prompt", and message. 107 118 // TODO: Add support for WebDriver BiDi. 108 - @@ -1726,6 +1756,26 @@ 119 + @@ -1726,6 +1758,26 @@ 109 120 // https://html.spec.whatwg.org/multipage/#windoweventhandlers 110 121 window_event_handlers!(); 111 122 ··· 132 143 /// <https://developer.mozilla.org/en-US/docs/Web/API/Window/screen> 133 144 fn Screen(&self, can_gc: CanGc) -> DomRoot<Screen> { 134 145 self.screen.or_init(|| Screen::new(self, can_gc)) 135 - @@ -3109,9 +3159,33 @@ 146 + @@ -3109,9 +3161,33 @@ 136 147 &self, 137 148 input_event: &ConstellationInputEvent, 138 149 ) -> Option<HitTestResult> { ··· 169 180 } 170 181 171 182 #[expect(unsafe_code)] 172 - @@ -3130,8 +3204,25 @@ 183 + @@ -3130,8 +3206,34 @@ 173 184 // SAFETY: This is safe because `Window::query_elements_from_point` has ensured that 174 185 // layout has run and any OpaqueNodes that no longer refer to real nodes are gone. 175 186 let address = UntrustedNodeAddress(result.node.0 as *const c_void); ··· 179 190 + let node_doc = node.owner_doc(); 180 191 + let our_doc = self.Document(); 181 192 + if *node_doc != *our_doc { 182 - + warn!( 183 - + "hit_test_from_point_in_viewport: node {:?} belongs to different document! \ 184 - + Node doc URL: {:?}, Our doc URL: {:?}", 185 - + node.debug_str(), 186 - + node_doc.url(), 187 - + our_doc.url() 188 - + ); 189 - + // Return None to indicate hit test failed for this document 190 - + return None; 193 + + // The hit test landed inside a nested iframe's document. 194 + + // Find the iframe element in our document that contains this child document, 195 + + // so the event handler can forward the event to it. 196 + + let child_pipeline = node_doc.window().pipeline_id(); 197 + + let iframe_node = our_doc 198 + + .upcast::<Node>() 199 + + .traverse_preorder(ShadowIncluding::Yes) 200 + + .filter_map(|n| DomRoot::downcast::<HTMLIFrameElement>(n)) 201 + + .find(|iframe| iframe.pipeline_id() == Some(child_pipeline)) 202 + + .map(|iframe| DomRoot::upcast::<Node>(iframe)); 203 + + 204 + + return iframe_node.map(|node| HitTestResult { 205 + + node, 206 + + cursor: result.cursor, 207 + + point_in_node: result.point_in_target, 208 + + point_in_frame, 209 + + point_relative_to_initial_containing_block, 210 + + }); 191 211 + } 192 212 + 193 213 Some(HitTestResult { ··· 196 216 cursor: result.cursor, 197 217 point_in_node: result.point_in_target, 198 218 point_in_frame, 199 - @@ -3680,6 +3771,8 @@ 219 + @@ -3680,6 +3782,8 @@ 200 220 player_context: WindowGLContext, 201 221 #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>, 202 222 inherited_secure_context: Option<bool>, ··· 205 225 theme: Theme, 206 226 weak_script_thread: Weak<ScriptThread>, 207 227 initial_https_state: HttpsState, 208 - @@ -3707,6 +3800,8 @@ 228 + @@ -3707,6 +3811,8 @@ 209 229 gpu_id_hub, 210 230 inherited_secure_context, 211 231 unminify_js, ··· 214 234 Some(font_context), 215 235 initial_https_state, 216 236 ), 217 - @@ -3993,3 +4088,115 @@ 237 + @@ -3993,3 +4099,115 @@ 218 238 Self::create_named_properties_object(cx, proto, object) 219 239 } 220 240 }
+7 -5
patches/components/shared/constellation/from_script_message.rs.patch
··· 107 107 + RevokeOrigin(String), 108 108 + /// Get the list of authorized origins. 109 109 + GetAuthorizedOrigins, 110 - +} 111 - + 110 + } 111 + 112 112 +/// Data returned by com.atproto.server.createSession xrpc calls. 113 113 +#[derive(Clone, Debug, Deserialize, Serialize)] 114 114 +#[serde(rename_all = "camelCase")] ··· 152 152 + pub email_auth_factor: bool, 153 153 + pub active: bool, 154 154 + pub status: Option<String>, 155 - } 156 - 155 + +} 156 + + 157 157 +/// Data returned by com.atproto.server.refreshSession xrpc calls. 158 158 +#[derive(Clone, Debug, Deserialize, Serialize)] 159 159 +#[serde(rename_all = "camelCase")] ··· 214 214 /// Mark a new document as active 215 215 ActivateDocument, 216 216 /// Set the document state for a pipeline (used by screenshot / reftests) 217 - @@ -758,6 +892,175 @@ 217 + @@ -758,6 +892,177 @@ 218 218 /// aggregate lock count and notify the provider only when the count transitions from N to 0. 219 219 /// <https://w3c.github.io/screen-wake-lock/#dfn-release-wake-lock> 220 220 ReleaseWakeLock(WakeLockType), ··· 248 248 + /// Forward an input event to an embedded webview after the parent's DOM hit testing 249 249 + /// determined that the event target is an embedded iframe element. 250 250 + ForwardEventToEmbeddedWebView(WebViewId, InputEventAndId), 251 + + /// Forward an input event to a regular cross-origin iframe's pipeline. 252 + + ForwardInputEventToIframe(WebViewId, PipelineId, InputEventAndId), 251 253 + /// Inject an input event to the webview that currently has an active IME input. 252 254 + /// Used by the virtual keyboard to send keystrokes to the focused input field. 253 255 + InjectInputToActiveIme(InputEventAndId),
+1 -1
ui/system/mediacenter/index.js
··· 672 672 673 673 function openReceivedUrl(url, asVideo) { 674 674 if (asVideo) { 675 - const playerUrl = `beaver://system/mediacenter/player.html?title=${encodeURIComponent(url)}&src=${encodeURIComponent(url)}`; 675 + const playerUrl = `beaver://system/mediacenter/video_player.html?title=${encodeURIComponent(url)}&src=${encodeURIComponent(url)}`; 676 676 launchContent({ category: "received", url: playerUrl, title: url }); 677 677 } else { 678 678 launchContent({ category: "received", url, title: url });
+51 -6
ui/system/mediacenter/player.html ui/system/mediacenter/video_player.html
··· 74 74 display: flex; 75 75 align-items: center; 76 76 gap: 1em; 77 + padding: 10px 0; 77 78 } 78 79 79 80 .time { ··· 92 93 border-radius: 3px; 93 94 position: relative; 94 95 overflow: hidden; 96 + cursor: pointer; 95 97 } 96 98 97 99 .progress-buffered { ··· 189 191 .center-indicator lucide-icon { font-size: 2.5em; } 190 192 </style> 191 193 </head> 192 - <body> 194 + <body class="controls-visible"> 193 195 <video width="400" height="300" id="video"></video> 194 196 195 197 <div class="center-indicator" id="indicator"> ··· 378 380 379 381 video.addEventListener("playing", showOverlay); 380 382 381 - // --- Mouse --- 383 + // --- Pointer: video tap to play/pause --- 384 + video.addEventListener("click", function() { 385 + doAction("play"); 386 + showOverlay(); 387 + }); 388 + 389 + // --- Pointer: overlay background tap to play/pause --- 382 390 overlay.addEventListener("click", function(e) { 383 - var btn = e.target.closest(".btn"); 384 - if (btn) { doAction(btn.dataset.action); } 385 - else { doAction("play"); } 386 - showOverlay(); 391 + if (!e.target.closest(".btn") && !e.target.closest(".progress-track")) { 392 + doAction("play"); 393 + showOverlay(); 394 + } 395 + }); 396 + 397 + // --- Pointer: buttons --- 398 + buttons.forEach(function(btn) { 399 + btn.addEventListener("click", function(e) { 400 + e.stopPropagation(); 401 + doAction(btn.dataset.action); 402 + showOverlay(); 403 + }); 404 + }); 405 + 406 + // --- Pointer: progress bar seek --- 407 + var progressTrack = document.querySelector(".progress-track"); 408 + 409 + function seekToPointer(e) { 410 + if (!video.duration) return; 411 + var rect = progressTrack.getBoundingClientRect(); 412 + var ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); 413 + video.currentTime = ratio * video.duration; 414 + } 415 + 416 + var dragging = false; 417 + progressTrack.addEventListener("pointerdown", function(e) { 418 + e.stopPropagation(); 419 + dragging = true; 420 + progressTrack.setPointerCapture(e.pointerId); 421 + seekToPointer(e); 422 + }); 423 + progressTrack.addEventListener("pointermove", function(e) { 424 + if (dragging) { seekToPointer(e); } 425 + }); 426 + progressTrack.addEventListener("pointerup", function(e) { 427 + if (dragging) { 428 + dragging = false; 429 + progressTrack.releasePointerCapture(e.pointerId); 430 + } 387 431 }); 388 432 433 + // --- Mouse move shows overlay --- 389 434 document.addEventListener("mousemove", showOverlay); 390 435 </script> 391 436 </body>
+8 -2
ui/system/mediacenter/sources.json
··· 20 20 { 21 21 "category": "videos", 22 22 "title": "Big Buck Bunny", 23 - "url": "beaver://system/mediacenter/player.html?title=Bunny&src=http%3A%2F%2Flocalhost%3A8888%2Fbig_buck_bunny_720p_stereo.ogg", 24 - "icon": "film" 23 + "url": "https://test-videos.co.uk/bigbuckbunny/webm-vp8", 24 + "icon": "play-circle" 25 + }, 26 + { 27 + "category": "videos", 28 + "title": "Bunny Direct", 29 + "url": "beaver://system/mediacenter/video_player.html?src=https%3a%2f%2ftest-videos.co.uk%2fvids%2fbigbuckbunny%2fwebm%2fvp8%2f1080%2fbig_buck_bunny_1080_10s_2mb.webm&title=big_buck_bunny_1080_10s_2mb.webm", 30 + "icon": "play-circle" 25 31 }, 26 32 { 27 33 "category": "videos",