mirror of
https://github.com/servo/servo.git
synced 2025-09-27 15:20:09 +01:00
script: Move keyboard scrolling to script (#39371)
Instead of having every single embedder implement keyboard scrolling, handle it in script in the default key event handler. This allows properly targeting the scroll events to their scroll containers as well as appropriately sizing "page up" and "page down" scroll deltas. This change means that when you use the keyboard to scroll, the focused or most recently clicked `<iframe>` or overflow scroll container is scrolled, rather than the main frame. In addition, when a particular scroll frame is larger than its content in the axis of the scroll, the scrolling operation is chained to the parent (as in other browsers). One exception is for `<iframe>`s, which will be implemented in a followup change. Testing: automated tests runnable locally with `mach test-wpt --product servodriver` Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
99fbd36b5d
commit
ac8895c3ae
19 changed files with 540 additions and 185 deletions
|
@ -8,7 +8,7 @@ use compositing_traits::display_list::AxesScrollSensitivity;
|
|||
use euclid::Rect;
|
||||
use euclid::default::Size2D as UntypedSize2D;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{LayoutElementType, LayoutNodeType};
|
||||
use layout_api::{AxesOverflow, LayoutElementType, LayoutNodeType};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use servo_arc::Arc;
|
||||
|
@ -31,7 +31,7 @@ use crate::fragment_tree::{FragmentFlags, FragmentTree};
|
|||
use crate::geom::{LogicalVec2, PhysicalSize};
|
||||
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
|
||||
use crate::replaced::ReplacedContents;
|
||||
use crate::style_ext::{AxesOverflow, Display, DisplayGeneratingBox, DisplayInside};
|
||||
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
|
||||
use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
|
||||
use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ use layout_api::{
|
|||
BoxAreaType, IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory,
|
||||
OffsetParentResponse, PropertyRegistration, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
||||
ReflowRequest, ReflowRequestRestyle, ReflowResult, RegisterPropertyError,
|
||||
ScrollContainerQueryType, ScrollContainerResponse, TrustedNodeAddress,
|
||||
ScrollContainerQueryFlags, ScrollContainerResponse, TrustedNodeAddress,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||
|
@ -334,10 +334,10 @@ impl Layout for LayoutThread {
|
|||
fn query_scroll_container(
|
||||
&self,
|
||||
node: TrustedNodeAddress,
|
||||
query_type: ScrollContainerQueryType,
|
||||
flags: ScrollContainerQueryFlags,
|
||||
) -> Option<ScrollContainerResponse> {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_scroll_container_query(node, query_type)
|
||||
process_scroll_container_query(node, flags)
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
|
|
|
@ -12,8 +12,8 @@ use euclid::{SideOffsets2D, Size2D};
|
|||
use itertools::Itertools;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{
|
||||
BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse, ScrollContainerQueryType,
|
||||
ScrollContainerResponse,
|
||||
BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse,
|
||||
ScrollContainerQueryFlags, ScrollContainerResponse,
|
||||
};
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
|
@ -677,7 +677,7 @@ pub fn process_offset_parent_query(
|
|||
#[inline]
|
||||
pub(crate) fn process_scroll_container_query(
|
||||
node: ServoLayoutNode<'_>,
|
||||
query_type: ScrollContainerQueryType,
|
||||
query_flags: ScrollContainerQueryFlags,
|
||||
) -> Option<ScrollContainerResponse> {
|
||||
let layout_data = node.to_threadsafe().inner_layout_data()?;
|
||||
|
||||
|
@ -686,15 +686,15 @@ pub(crate) fn process_scroll_container_query(
|
|||
let layout_box = layout_data.self_box.borrow();
|
||||
let layout_box = layout_box.as_ref()?;
|
||||
|
||||
let (mut current_position_value, flags) = layout_box
|
||||
.with_first_base(|base| (base.style.clone_position(), base.base_fragment_info.flags))?;
|
||||
let (style, flags) =
|
||||
layout_box.with_first_base(|base| (base.style.clone(), base.base_fragment_info.flags))?;
|
||||
|
||||
// - The element is the root element.
|
||||
// - The element is the body element.
|
||||
//
|
||||
// Note: We only do this for `scrollParent`, which needs to be null. But `scrollIntoView` on the
|
||||
// `<body>` or root element should still bring it into view by scrolling the viewport.
|
||||
if query_type == ScrollContainerQueryType::ForScrollParent &&
|
||||
if query_flags.contains(ScrollContainerQueryFlags::ForScrollParent) &&
|
||||
flags.intersects(
|
||||
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
|
||||
)
|
||||
|
@ -702,6 +702,15 @@ pub(crate) fn process_scroll_container_query(
|
|||
return None;
|
||||
}
|
||||
|
||||
if query_flags.contains(ScrollContainerQueryFlags::Inclusive) &&
|
||||
style.establishes_scroll_container(flags)
|
||||
{
|
||||
return Some(ScrollContainerResponse::Element(
|
||||
node.opaque().into(),
|
||||
style.effective_overflow(flags),
|
||||
));
|
||||
}
|
||||
|
||||
// - The element’s computed value of the position property is fixed and no ancestor
|
||||
// establishes a fixed position containing block.
|
||||
//
|
||||
|
@ -721,6 +730,7 @@ pub(crate) fn process_scroll_container_query(
|
|||
// Notes: We don't follow the specification exactly below, but we follow the spirit.
|
||||
//
|
||||
// TODO: Handle the situation where the ancestor is "closed-shadow-hidden" from the element.
|
||||
let mut current_position_value = style.clone_position();
|
||||
let mut current_ancestor = node.as_element()?;
|
||||
while let Some(ancestor) = current_ancestor.traversal_parent() {
|
||||
current_ancestor = ancestor;
|
||||
|
@ -757,6 +767,7 @@ pub(crate) fn process_scroll_container_query(
|
|||
if ancestor_style.establishes_scroll_container(ancestor_flags) {
|
||||
return Some(ScrollContainerResponse::Element(
|
||||
ancestor.as_node().opaque().into(),
|
||||
ancestor_style.effective_overflow(ancestor_flags),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use layout_api::AxesOverflow;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use style::Zero;
|
||||
use style::color::AbsoluteColor;
|
||||
|
@ -58,30 +59,6 @@ pub(crate) enum DisplayGeneratingBox {
|
|||
/// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
|
||||
LayoutInternal(DisplayLayoutInternal),
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AxesOverflow {
|
||||
pub x: Overflow,
|
||||
pub y: Overflow,
|
||||
}
|
||||
|
||||
impl Default for AxesOverflow {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ComputedValues> for AxesOverflow {
|
||||
fn from(style: &ComputedValues) -> Self {
|
||||
Self {
|
||||
x: style.clone_overflow_x(),
|
||||
y: style.clone_overflow_y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayGeneratingBox {
|
||||
pub(crate) fn display_inside(&self) -> DisplayInside {
|
||||
match *self {
|
||||
|
|
|
@ -174,6 +174,7 @@ use crate::dom::processinginstruction::ProcessingInstruction;
|
|||
use crate::dom::promise::Promise;
|
||||
use crate::dom::range::Range;
|
||||
use crate::dom::resizeobserver::{ResizeObservationDepth, ResizeObserver};
|
||||
use crate::dom::scrolling_box::{ScrollingBox, ScrollingBoxSource};
|
||||
use crate::dom::selection::Selection;
|
||||
use crate::dom::servoparser::ServoParser;
|
||||
use crate::dom::shadowroot::ShadowRoot;
|
||||
|
@ -4447,6 +4448,10 @@ impl Document {
|
|||
pub(crate) fn set_active_sandboxing_flag_set(&self, flags: SandboxingFlagSet) {
|
||||
self.active_sandboxing_flag_set.set(flags)
|
||||
}
|
||||
|
||||
pub(crate) fn viewport_scrolling_box(&self) -> ScrollingBox {
|
||||
ScrollingBox::new(ScrollingBoxSource::Viewport(DomRoot::from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
|
|
@ -18,17 +18,17 @@ use embedder_traits::{
|
|||
MouseLeftViewportEvent, ScrollEvent, TouchEvent as EmbedderTouchEvent, TouchEventType, TouchId,
|
||||
UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
|
||||
};
|
||||
use euclid::Point2D;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use ipc_channel::ipc;
|
||||
use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
|
||||
use layout_api::node_id_from_scroll_id;
|
||||
use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
|
||||
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
|
||||
use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
|
||||
use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
|
||||
use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
|
||||
use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
|
||||
use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
|
||||
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
|
||||
use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
|
||||
use script_bindings::inheritance::Castable;
|
||||
use script_bindings::num::Finite;
|
||||
use script_bindings::root::{Dom, DomRoot, DomSlice};
|
||||
|
@ -50,6 +50,7 @@ use crate::dom::gamepad::gamepadevent::GamepadEventType;
|
|||
use crate::dom::inputevent::HitTestResult;
|
||||
use crate::dom::node::{self, Node, ShadowIncluding};
|
||||
use crate::dom::pointerevent::PointerId;
|
||||
use crate::dom::scrolling_box::ScrollingBoxAxis;
|
||||
use crate::dom::types::{
|
||||
ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
|
||||
HTMLAnchorElement, KeyboardEvent, MouseEvent, PointerEvent, Touch, TouchEvent, TouchList,
|
||||
|
@ -78,6 +79,8 @@ pub(crate) struct DocumentEventHandler {
|
|||
last_click_info: DomRefCell<Option<(Instant, Point2D<f32, CSSPixel>)>>,
|
||||
/// The element that is currently hovered by the cursor.
|
||||
current_hover_target: MutNullableDom<Element>,
|
||||
/// The element that was most recently clicked.
|
||||
most_recently_clicked_element: MutNullableDom<Element>,
|
||||
/// The most recent mouse movement point, used for processing `mouseleave` events.
|
||||
#[no_trace]
|
||||
most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
|
||||
|
@ -100,6 +103,7 @@ impl DocumentEventHandler {
|
|||
mouse_move_event_index: Default::default(),
|
||||
last_click_info: Default::default(),
|
||||
current_hover_target: Default::default(),
|
||||
most_recently_clicked_element: Default::default(),
|
||||
most_recent_mousemove_point: Default::default(),
|
||||
current_cursor: Default::default(),
|
||||
active_touch_points: Default::default(),
|
||||
|
@ -581,6 +585,8 @@ impl DocumentEventHandler {
|
|||
match event.action {
|
||||
// https://w3c.github.io/uievents/#handle-native-mouse-click
|
||||
MouseButtonAction::Click => {
|
||||
self.most_recently_clicked_element.set(Some(&el));
|
||||
|
||||
el.set_click_in_progress(true);
|
||||
dom_event.dispatch(node.upcast(), false, can_gc);
|
||||
el.set_click_in_progress(false);
|
||||
|
@ -1472,4 +1478,70 @@ impl DocumentEventHandler {
|
|||
document.handle_element_scroll_event(&element);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_default_keyboard_event_handler(&self, event: &KeyboardEvent) {
|
||||
if event.upcast::<Event>().type_() != atom!("keydown") {
|
||||
return;
|
||||
}
|
||||
if !event.modifiers().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let scroll_axis = match event.key() {
|
||||
Key::Named(
|
||||
NamedKey::Home |
|
||||
NamedKey::End |
|
||||
NamedKey::PageDown |
|
||||
NamedKey::PageUp |
|
||||
NamedKey::ArrowUp |
|
||||
NamedKey::ArrowDown,
|
||||
) => ScrollingBoxAxis::Y,
|
||||
Key::Named(NamedKey::ArrowLeft | NamedKey::ArrowRight) => ScrollingBoxAxis::X,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let document = self.window.Document();
|
||||
let mut scrolling_box = document
|
||||
.get_focused_element()
|
||||
.or(self.most_recently_clicked_element.get())
|
||||
.and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
|
||||
.unwrap_or_else(|| document.viewport_scrolling_box());
|
||||
|
||||
while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
|
||||
// Always fall back to trying to scroll the entire document.
|
||||
if scrolling_box.is_viewport() {
|
||||
break;
|
||||
}
|
||||
let parent = scrolling_box
|
||||
.parent()
|
||||
.unwrap_or_else(|| document.viewport_scrolling_box());
|
||||
scrolling_box = parent;
|
||||
}
|
||||
|
||||
const LINE_HEIGHT: f32 = 76.0;
|
||||
const LINE_WIDTH: f32 = 76.0;
|
||||
|
||||
let current_scroll_offset = scrolling_box.scroll_position();
|
||||
let delta = match event.key() {
|
||||
Key::Named(NamedKey::Home) => Vector2D::new(0.0, -current_scroll_offset.y),
|
||||
Key::Named(NamedKey::End) => Vector2D::new(
|
||||
0.0,
|
||||
-current_scroll_offset.y + scrolling_box.content_size().height -
|
||||
scrolling_box.size().height,
|
||||
),
|
||||
Key::Named(NamedKey::PageDown) => {
|
||||
Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
|
||||
},
|
||||
Key::Named(NamedKey::PageUp) => {
|
||||
Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
|
||||
},
|
||||
Key::Named(NamedKey::ArrowUp) => Vector2D::new(0.0, -LINE_HEIGHT),
|
||||
Key::Named(NamedKey::ArrowDown) => Vector2D::new(0.0, LINE_HEIGHT),
|
||||
Key::Named(NamedKey::ArrowLeft) => Vector2D::new(-LINE_WIDTH, 0.0),
|
||||
Key::Named(NamedKey::ArrowRight) => Vector2D::new(LINE_WIDTH, 0.0),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_pr
|
|||
use js::jsapi::Heap;
|
||||
use js::jsval::JSVal;
|
||||
use js::rust::HandleObject;
|
||||
use layout_api::{LayoutDamage, ScrollContainerQueryType, ScrollContainerResponse};
|
||||
use layout_api::{LayoutDamage, ScrollContainerQueryFlags, ScrollContainerResponse};
|
||||
use net_traits::ReferrerPolicy;
|
||||
use net_traits::request::CorsSettings;
|
||||
use selectors::Element as SelectorsElement;
|
||||
|
@ -61,7 +61,7 @@ use style::values::{AtomIdent, AtomString, CSSFloat, computed, specified};
|
|||
use style::{ArcSlice, CaseSensitivityExt, dom_apis, thread_state};
|
||||
use stylo_atoms::Atom;
|
||||
use stylo_dom::ElementState;
|
||||
use webrender_api::units::{LayoutSize, LayoutVector2D};
|
||||
use webrender_api::units::LayoutVector2D;
|
||||
use xml5ever::serialize::TraversalScope::{
|
||||
ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode,
|
||||
};
|
||||
|
@ -166,6 +166,7 @@ use crate::dom::node::{
|
|||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::raredata::ElementRareData;
|
||||
use crate::dom::scrolling_box::{ScrollingBox, ScrollingBoxSource};
|
||||
use crate::dom::servoparser::ServoParser;
|
||||
use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot};
|
||||
use crate::dom::text::Text;
|
||||
|
@ -274,60 +275,6 @@ impl FromStr for AdjacentPosition {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents a scrolling box that can be either an element or the viewport
|
||||
/// <https://drafts.csswg.org/cssom-view/#scrolling-box>
|
||||
enum ScrollingBox {
|
||||
Element(DomRoot<Element>),
|
||||
Viewport(DomRoot<Document>),
|
||||
}
|
||||
|
||||
impl ScrollingBox {
|
||||
fn scroll_position(&self) -> LayoutVector2D {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element
|
||||
.owner_window()
|
||||
.scroll_offset_query(element.upcast::<Node>()),
|
||||
ScrollingBox::Viewport(document) => document.window().scroll_offset(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> LayoutSize {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element.client_rect().size.to_f32().cast_unit(),
|
||||
ScrollingBox::Viewport(document) => {
|
||||
document.window().viewport_details().size.cast_unit()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<ScrollingBox> {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element.scrolling_box(),
|
||||
ScrollingBox::Viewport(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn node(&self) -> &Node {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element.upcast(),
|
||||
ScrollingBox::Viewport(document) => document.upcast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_to(&self, position: LayoutVector2D, behavior: ScrollBehavior) {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => {
|
||||
element
|
||||
.owner_window()
|
||||
.scroll_an_element(element, position.x, position.y, behavior);
|
||||
},
|
||||
ScrollingBox::Viewport(document) => {
|
||||
document.window().scroll(position.x, position.y, behavior);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Element methods
|
||||
//
|
||||
|
@ -889,16 +836,19 @@ impl Element {
|
|||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn scrolling_box(&self) -> Option<ScrollingBox> {
|
||||
pub(crate) fn scrolling_box(&self, flags: ScrollContainerQueryFlags) -> Option<ScrollingBox> {
|
||||
self.owner_window()
|
||||
.scroll_container_query(self.upcast(), ScrollContainerQueryType::ForScrollIntoView)
|
||||
.scroll_container_query(self.upcast(), flags)
|
||||
.and_then(|response| match response {
|
||||
ScrollContainerResponse::Viewport => {
|
||||
Some(ScrollingBox::Viewport(self.owner_document()))
|
||||
},
|
||||
ScrollContainerResponse::Element(parent_node_address) => {
|
||||
ScrollContainerResponse::Viewport => Some(ScrollingBox::new(
|
||||
ScrollingBoxSource::Viewport(self.owner_document()),
|
||||
)),
|
||||
ScrollContainerResponse::Element(parent_node_address, axes_overflow) => {
|
||||
let node = unsafe { from_untrusted_node_address(parent_node_address) };
|
||||
Some(ScrollingBox::Element(DomRoot::downcast(node)?))
|
||||
Some(ScrollingBox::new(ScrollingBoxSource::Element(
|
||||
DomRoot::downcast(node)?,
|
||||
axes_overflow,
|
||||
)))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -913,7 +863,7 @@ impl Element {
|
|||
) {
|
||||
// Step 1: For each ancestor element or viewport that establishes a scrolling box `scrolling
|
||||
// box`, in order of innermost to outermost scrolling box, run these substeps:
|
||||
let mut parent_scrolling_box = self.scrolling_box();
|
||||
let mut parent_scrolling_box = self.scrolling_box(ScrollContainerQueryFlags::empty());
|
||||
while let Some(scrolling_box) = parent_scrolling_box {
|
||||
parent_scrolling_box = scrolling_box.parent();
|
||||
|
||||
|
@ -987,21 +937,22 @@ impl Element {
|
|||
// to follow it using our own geometry types.
|
||||
//
|
||||
// TODO: This makes the code below wrong for the purposes of writing modes.
|
||||
let (adjusted_element_top_left, adjusted_element_bottom_right) = match scrolling_box {
|
||||
ScrollingBox::Viewport(_) => (target_top_left, target_bottom_right),
|
||||
ScrollingBox::Element(scrolling_element) => {
|
||||
let scrolling_padding_rect_top_left = scrolling_element
|
||||
.upcast::<Node>()
|
||||
.padding_box()
|
||||
.unwrap_or_default()
|
||||
.origin
|
||||
.map(to_pixel);
|
||||
(
|
||||
target_top_left - scrolling_padding_rect_top_left.to_vector(),
|
||||
target_bottom_right - scrolling_padding_rect_top_left.to_vector(),
|
||||
)
|
||||
},
|
||||
};
|
||||
let (adjusted_element_top_left, adjusted_element_bottom_right) =
|
||||
match scrolling_box.target() {
|
||||
ScrollingBoxSource::Viewport(_) => (target_top_left, target_bottom_right),
|
||||
ScrollingBoxSource::Element(scrolling_element, _) => {
|
||||
let scrolling_padding_rect_top_left = scrolling_element
|
||||
.upcast::<Node>()
|
||||
.padding_box()
|
||||
.unwrap_or_default()
|
||||
.origin
|
||||
.map(to_pixel);
|
||||
(
|
||||
target_top_left - scrolling_padding_rect_top_left.to_vector(),
|
||||
target_bottom_right - scrolling_padding_rect_top_left.to_vector(),
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let scrolling_box_size = scrolling_box.size();
|
||||
let current_scroll_position = scrolling_box.scroll_position();
|
||||
|
@ -5062,7 +5013,7 @@ impl Element {
|
|||
.inspect(|states| states.for_each_state(callback));
|
||||
}
|
||||
|
||||
fn client_rect(&self) -> Rect<i32> {
|
||||
pub(crate) fn client_rect(&self) -> Rect<i32> {
|
||||
let doc = self.node.owner_doc();
|
||||
|
||||
if let Some(rect) = self
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::rc::Rc;
|
|||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix, QualName, local_name, ns};
|
||||
use js::rust::HandleObject;
|
||||
use layout_api::{QueryMsg, ScrollContainerQueryType, ScrollContainerResponse};
|
||||
use layout_api::{QueryMsg, ScrollContainerQueryFlags, ScrollContainerResponse};
|
||||
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
|
||||
use style::attr::AttrValue;
|
||||
use stylo_dom::ElementState;
|
||||
|
@ -450,10 +450,10 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
|
|||
#[allow(unsafe_code)]
|
||||
fn GetScrollParent(&self) -> Option<DomRoot<Element>> {
|
||||
self.owner_window()
|
||||
.scroll_container_query(self.upcast(), ScrollContainerQueryType::ForScrollParent)
|
||||
.scroll_container_query(self.upcast(), ScrollContainerQueryFlags::ForScrollParent)
|
||||
.and_then(|response| match response {
|
||||
ScrollContainerResponse::Viewport => self.owner_document().GetScrollingElement(),
|
||||
ScrollContainerResponse::Element(parent_node_address) => {
|
||||
ScrollContainerResponse::Element(parent_node_address, _) => {
|
||||
let node = unsafe { from_untrusted_node_address(parent_node_address) };
|
||||
DomRoot::downcast(node)
|
||||
},
|
||||
|
|
|
@ -428,6 +428,7 @@ pub(crate) mod resizeobserverentry;
|
|||
pub(crate) mod resizeobserversize;
|
||||
pub(crate) mod response;
|
||||
pub(crate) mod screen;
|
||||
mod scrolling_box;
|
||||
pub(crate) mod securitypolicyviolationevent;
|
||||
pub(crate) mod selection;
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -121,6 +121,7 @@ use crate::dom::shadowroot::{IsUserAgentWidget, LayoutShadowRootHelpers, ShadowR
|
|||
use crate::dom::stylesheetlist::StyleSheetListOwner;
|
||||
use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement};
|
||||
use crate::dom::text::Text;
|
||||
use crate::dom::types::KeyboardEvent;
|
||||
use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
|
||||
use crate::dom::window::Window;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
@ -4084,6 +4085,14 @@ impl VirtualMethods for Node {
|
|||
self.ranges().drain_to_parent(context, self);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: &Event, _: CanGc) {
|
||||
if let Some(event) = event.downcast::<KeyboardEvent>() {
|
||||
self.owner_document()
|
||||
.event_handler()
|
||||
.run_default_keyboard_event_handler(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of the changes that happened to a node.
|
||||
|
|
147
components/script/dom/scrolling_box.rs
Normal file
147
components/script/dom/scrolling_box.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use layout_api::{AxesOverflow, ScrollContainerQueryFlags};
|
||||
use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
|
||||
use script_bindings::inheritance::Castable;
|
||||
use script_bindings::root::DomRoot;
|
||||
use style::values::computed::Overflow;
|
||||
use webrender_api::units::{LayoutSize, LayoutVector2D};
|
||||
|
||||
use crate::dom::node::{Node, NodeTraits};
|
||||
use crate::dom::types::{Document, Element};
|
||||
|
||||
pub(crate) struct ScrollingBox {
|
||||
target: ScrollingBoxSource,
|
||||
cached_content_size: Cell<Option<LayoutSize>>,
|
||||
cached_size: Cell<Option<LayoutSize>>,
|
||||
}
|
||||
|
||||
/// Represents a scrolling box that can be either an element or the viewport
|
||||
/// <https://drafts.csswg.org/cssom-view/#scrolling-box>
|
||||
pub(crate) enum ScrollingBoxSource {
|
||||
Element(DomRoot<Element>, AxesOverflow),
|
||||
Viewport(DomRoot<Document>),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ScrollingBoxAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl ScrollingBox {
|
||||
pub(crate) fn new(target: ScrollingBoxSource) -> Self {
|
||||
Self {
|
||||
target,
|
||||
cached_content_size: Default::default(),
|
||||
cached_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn target(&self) -> &ScrollingBoxSource {
|
||||
&self.target
|
||||
}
|
||||
|
||||
pub(crate) fn is_viewport(&self) -> bool {
|
||||
matches!(self.target, ScrollingBoxSource::Viewport(..))
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_position(&self) -> LayoutVector2D {
|
||||
match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => element
|
||||
.owner_window()
|
||||
.scroll_offset_query(element.upcast::<Node>()),
|
||||
ScrollingBoxSource::Viewport(document) => document.window().scroll_offset(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn content_size(&self) -> LayoutSize {
|
||||
if let Some(content_size) = self.cached_content_size.get() {
|
||||
return content_size;
|
||||
}
|
||||
|
||||
let (document, node_to_query) = match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => {
|
||||
(element.owner_document(), Some(element.upcast()))
|
||||
},
|
||||
ScrollingBoxSource::Viewport(document) => (document.clone(), None),
|
||||
};
|
||||
|
||||
let content_size = document
|
||||
.window()
|
||||
.scrolling_area_query(node_to_query)
|
||||
.size
|
||||
.to_f32()
|
||||
.cast_unit();
|
||||
self.cached_content_size.set(Some(content_size));
|
||||
content_size
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> LayoutSize {
|
||||
if let Some(size) = self.cached_size.get() {
|
||||
return size;
|
||||
}
|
||||
|
||||
let size = match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => {
|
||||
element.client_rect().size.to_f32().cast_unit()
|
||||
},
|
||||
ScrollingBoxSource::Viewport(document) => {
|
||||
document.window().viewport_details().size.cast_unit()
|
||||
},
|
||||
};
|
||||
self.cached_size.set(Some(size));
|
||||
size
|
||||
}
|
||||
|
||||
pub(crate) fn parent(&self) -> Option<ScrollingBox> {
|
||||
match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => {
|
||||
element.scrolling_box(ScrollContainerQueryFlags::empty())
|
||||
},
|
||||
ScrollingBoxSource::Viewport(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn node(&self) -> &Node {
|
||||
match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => element.upcast(),
|
||||
ScrollingBoxSource::Viewport(document) => document.upcast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_to(&self, position: LayoutVector2D, behavior: ScrollBehavior) {
|
||||
match &self.target {
|
||||
ScrollingBoxSource::Element(element, _) => {
|
||||
element
|
||||
.owner_window()
|
||||
.scroll_an_element(element, position.x, position.y, behavior);
|
||||
},
|
||||
ScrollingBoxSource::Viewport(document) => {
|
||||
document.window().scroll(position.x, position.y, behavior);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_keyboard_scroll_in_axis(&self, axis: ScrollingBoxAxis) -> bool {
|
||||
let axes_overflow = match &self.target {
|
||||
ScrollingBoxSource::Element(_, axes_overflow) => axes_overflow,
|
||||
ScrollingBoxSource::Viewport(_) => return true,
|
||||
};
|
||||
let overflow = match axis {
|
||||
ScrollingBoxAxis::X => axes_overflow.x,
|
||||
ScrollingBoxAxis::Y => axes_overflow.x,
|
||||
};
|
||||
if !overflow.is_scrollable() || overflow == Overflow::Hidden {
|
||||
return false;
|
||||
}
|
||||
match axis {
|
||||
ScrollingBoxAxis::X => self.content_size().width > self.size().width,
|
||||
ScrollingBoxAxis::Y => self.content_size().height > self.size().height,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ use layout_api::{
|
|||
BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout,
|
||||
LayoutImageDestination, PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg,
|
||||
ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason,
|
||||
ScrollContainerQueryType, ScrollContainerResponse, TrustedNodeAddress,
|
||||
ScrollContainerQueryFlags, ScrollContainerResponse, TrustedNodeAddress,
|
||||
combine_id_with_fragment_type,
|
||||
};
|
||||
use malloc_size_of::MallocSizeOf;
|
||||
|
@ -2629,12 +2629,12 @@ impl Window {
|
|||
pub(crate) fn scroll_container_query(
|
||||
&self,
|
||||
node: &Node,
|
||||
query_type: ScrollContainerQueryType,
|
||||
flags: ScrollContainerQueryFlags,
|
||||
) -> Option<ScrollContainerResponse> {
|
||||
self.layout_reflow(QueryMsg::ScrollParentQuery);
|
||||
self.layout
|
||||
.borrow()
|
||||
.query_scroll_container(node.to_trusted_node_address(), query_type)
|
||||
.query_scroll_container(node.to_trusted_node_address(), flags)
|
||||
}
|
||||
|
||||
pub(crate) fn text_index_query(
|
||||
|
|
|
@ -50,10 +50,11 @@ use style::data::ElementData;
|
|||
use style::dom::OpaqueNode;
|
||||
use style::invalidation::element::restyle_hints::RestyleHint;
|
||||
use style::media_queries::Device;
|
||||
use style::properties::PropertyId;
|
||||
use style::properties::style_structs::Font;
|
||||
use style::properties::{ComputedValues, PropertyId};
|
||||
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
|
||||
use style::stylesheets::{Stylesheet, UrlExtraData};
|
||||
use style::values::computed::Overflow;
|
||||
use style_traits::CSSPixel;
|
||||
use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
|
||||
use webrender_api::{ExternalScrollId, ImageKey};
|
||||
|
@ -308,7 +309,7 @@ pub trait Layout {
|
|||
fn query_scroll_container(
|
||||
&self,
|
||||
node: TrustedNodeAddress,
|
||||
query_type: ScrollContainerQueryType,
|
||||
flags: ScrollContainerQueryFlags,
|
||||
) -> Option<ScrollContainerResponse>;
|
||||
fn query_resolved_style(
|
||||
&self,
|
||||
|
@ -366,16 +367,44 @@ pub struct OffsetParentResponse {
|
|||
pub rect: Rect<Au>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ScrollContainerQueryType {
|
||||
ForScrollParent,
|
||||
ForScrollIntoView,
|
||||
bitflags! {
|
||||
#[derive(PartialEq)]
|
||||
pub struct ScrollContainerQueryFlags: u8 {
|
||||
/// Whether or not this query is for the purposes of a `scrollParent` layout query.
|
||||
const ForScrollParent = 1 << 0;
|
||||
/// Whether or not to consider the original element's scroll box for the return value.
|
||||
const Inclusive = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AxesOverflow {
|
||||
pub x: Overflow,
|
||||
pub y: Overflow,
|
||||
}
|
||||
|
||||
impl Default for AxesOverflow {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ComputedValues> for AxesOverflow {
|
||||
fn from(style: &ComputedValues) -> Self {
|
||||
Self {
|
||||
x: style.clone_overflow_x(),
|
||||
y: style.clone_overflow_y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ScrollContainerResponse {
|
||||
Viewport,
|
||||
Element(UntrustedNodeAddress),
|
||||
Element(UntrustedNodeAddress, AxesOverflow),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue