diff options
Diffstat (limited to 'components')
31 files changed, 635 insertions, 103 deletions
diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs index 53acbea8b8d..7e348fbc9b9 100644 --- a/components/canvas/backend.rs +++ b/components/canvas/backend.rs @@ -134,7 +134,18 @@ pub(crate) trait GenericPathBuilder<B: Backend> { start_angle: f32, end_angle: f32, anticlockwise: bool, - ); + ) { + Self::ellipse( + self, + origin, + radius, + radius, + 0., + start_angle, + end_angle, + anticlockwise, + ); + } fn bezier_curve_to( &mut self, control_point1: &Point2D<f32>, @@ -212,7 +223,23 @@ pub(crate) trait GenericPathBuilder<B: Backend> { large_arc: bool, sweep: bool, end_point: Point2D<f32>, - ); + ) { + let Some(start) = self.get_current_point() else { + return; + }; + + let arc = lyon_geom::SvgArc { + from: start, + to: end_point, + radii: lyon_geom::vector(radius_x, radius_y), + x_rotation: lyon_geom::Angle::degrees(rotation_angle), + flags: lyon_geom::ArcFlags { large_arc, sweep }, + }; + + arc.for_each_quadratic_bezier(&mut |q| { + self.quadratic_curve_to(&q.ctrl, &q.to); + }); + } fn finish(&mut self) -> B::Path; } diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index efe0ffd05b8..ecf780c36d5 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -680,26 +680,6 @@ impl PathBuilder { } impl GenericPathBuilder<RaqoteBackend> for PathBuilder { - fn arc( - &mut self, - origin: Point2D<f32>, - radius: f32, - start_angle: f32, - end_angle: f32, - anticlockwise: bool, - ) { - <PathBuilder as GenericPathBuilder<RaqoteBackend>>::ellipse( - self, - origin, - radius, - radius, - 0., - start_angle, - end_angle, - anticlockwise, - ); - } - fn bezier_curve_to( &mut self, control_point1: &Point2D<f32>, @@ -720,32 +700,6 @@ impl GenericPathBuilder<RaqoteBackend> for PathBuilder { self.0.as_mut().unwrap().close(); } - fn svg_arc( - &mut self, - radius_x: f32, - radius_y: f32, - rotation_angle: f32, - large_arc: bool, - sweep: bool, - end_point: Point2D<f32>, - ) { - let Some(start) = self.get_current_point() else { - return; - }; - - let arc = lyon_geom::SvgArc { - from: start, - to: end_point, - radii: lyon_geom::vector(radius_x, radius_y), - x_rotation: lyon_geom::Angle::degrees(rotation_angle), - flags: lyon_geom::ArcFlags { large_arc, sweep }, - }; - - arc.for_each_quadratic_bezier(&mut |q| { - self.quadratic_curve_to(&q.ctrl, &q.to); - }); - } - fn get_current_point(&mut self) -> Option<Point2D<f32>> { let path = self.finish(); self.0 = Some(path.clone().into()); diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index f76dc68013d..a51dd5f8cda 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -689,11 +689,6 @@ impl WebViewRenderer { action: MouseButtonAction::Up, point, })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - button, - action: MouseButtonAction::Click, - point, - })); } pub(crate) fn notify_scroll_event( diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index e493a97d184..5db37800c42 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -129,9 +129,10 @@ use embedder_traits::resources::{self, Resource}; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, - FocusSequenceNumber, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent, - MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme, - ViewportDetails, WebDriverCommandMsg, WebDriverLoadStatus, + FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, + MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, + WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -1477,6 +1478,52 @@ where EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => { self.handle_paint_metric(pipeline_id, paint_metric_event); }, + EmbedderToConstellationMessage::EvaluateJavaScript( + webview_id, + evaluation_id, + script, + ) => { + self.handle_evaluate_javascript(webview_id, evaluation_id, script); + }, + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] + fn handle_evaluate_javascript( + &mut self, + webview_id: WebViewId, + evaluation_id: JavaScriptEvaluationId, + script: String, + ) { + let browsing_context_id = BrowsingContextId::from(webview_id); + let Some(pipeline) = self + .browsing_contexts + .get(&browsing_context_id) + .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id)) + else { + self.handle_finish_javascript_evaluation( + evaluation_id, + Err(JavaScriptEvaluationError::InternalError), + ); + return; + }; + + if pipeline + .event_loop + .send(ScriptThreadMessage::EvaluateJavaScript( + pipeline.id, + evaluation_id, + script, + )) + .is_err() + { + self.handle_finish_javascript_evaluation( + evaluation_id, + Err(JavaScriptEvaluationError::InternalError), + ); } } @@ -1817,6 +1864,9 @@ where self.mem_profiler_chan .send(mem::ProfilerMsg::Report(sender)); }, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.handle_finish_javascript_evaluation(evaluation_id, result) + }, } } @@ -3182,6 +3232,22 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] + fn handle_finish_javascript_evaluation( + &mut self, + evaluation_id: JavaScriptEvaluationId, + result: Result<JSValue, JavaScriptEvaluationError>, + ) { + self.embedder_proxy + .send(EmbedderMsg::FinishJavaScriptEvaluation( + evaluation_id, + result, + )); + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") + )] fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) { let browsing_context_id = match self.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline.browsing_context_id, @@ -4691,6 +4757,7 @@ where NavigationHistoryBehavior::Replace, ); }, + // TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { Some(browsing_context) => browsing_context.pipeline_id, diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index eff7f755c6b..fd7fe7dd251 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -77,6 +77,7 @@ mod from_compositor { Self::SetWebViewThrottled(_, _) => target!("SetWebViewThrottled"), Self::SetScrollStates(..) => target!("SetScrollStates"), Self::PaintMetric(..) => target!("PaintMetric"), + Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"), } } } @@ -176,6 +177,7 @@ mod from_script { Self::TitleChanged(..) => target!("TitleChanged"), Self::IFrameSizes(..) => target!("IFrameSizes"), Self::ReportMemory(..) => target!("ReportMemory"), + Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"), } } } @@ -238,6 +240,9 @@ mod from_script { Self::ShutdownComplete => target_variant!("ShutdownComplete"), Self::ShowNotification(..) => target_variant!("ShowNotification"), Self::ShowSelectElementMenu(..) => target_variant!("ShowSelectElementMenu"), + Self::FinishJavaScriptEvaluation(..) => { + target_variant!("FinishJavaScriptEvaluation") + }, } } } diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index e69b792e272..3ddbb71ba89 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -29,7 +29,9 @@ use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox}; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::formatting_contexts::{Baselines, IndependentFormattingContextContents}; -use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags}; +use crate::fragment_tree::{ + BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, +}; use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes}; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{ @@ -142,6 +144,9 @@ struct FlexItemLayoutResult { // Whether or not this layout had a child that dependeded on block constraints. has_child_which_depends_on_block_constraints: bool, + + // The specific layout info that this flex item had. + specific_layout_info: Option<SpecificLayoutInfo>, } impl FlexItemLayoutResult { @@ -295,7 +300,8 @@ impl FlexLineItem<'_> { .sides_to_flow_relative(item_margin) .to_physical(container_writing_mode), None, /* clearance */ - ); + ) + .with_specific_layout_info(self.layout_result.specific_layout_info); // If this flex item establishes a containing block for absolutely-positioned // descendants, then lay out any relevant absolutely-positioned children. This @@ -1910,6 +1916,7 @@ impl FlexItem<'_> { // size can differ from the hypothetical cross size, we should defer // synthesizing until needed. baseline_relative_to_margin_box: None, + specific_layout_info: None, }) }, IndependentFormattingContextContents::NonReplaced(non_replaced) => { @@ -1944,6 +1951,7 @@ impl FlexItem<'_> { content_block_size, baselines: content_box_baselines, depends_on_block_constraints, + specific_layout_info, .. } = layout; @@ -2012,6 +2020,7 @@ impl FlexItem<'_> { containing_block_block_size: item_as_containing_block.size.block, depends_on_block_constraints, has_child_which_depends_on_block_constraints, + specific_layout_info, }) }, } diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index 52d0ca7e20c..9f1520bd085 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -8,7 +8,7 @@ use std::mem; use devtools_traits::AttrInfo; use dom_struct::dom_struct; -use html5ever::{LocalName, Namespace, Prefix, ns}; +use html5ever::{LocalName, Namespace, Prefix, local_name, ns}; use style::attr::{AttrIdentifier, AttrValue}; use style::values::GenericAtomIdent; use stylo_atoms::Atom; @@ -179,7 +179,7 @@ impl Attr { assert_eq!(Some(owner), self.owner().as_deref()); owner.will_mutate_attr(self); self.swap_value(&mut value); - if *self.namespace() == ns!() { + if is_relevant_attribute(self.namespace(), self.local_name()) { vtable_for(owner.upcast()).attribute_mutated( self, AttributeMutation::Set(Some(&value)), @@ -283,3 +283,9 @@ impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> { &self.unsafe_get().identifier.namespace.0 } } + +/// A helper function to check if attribute is relevant. +pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool { + // <https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute> + namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href")) +} diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index 5722dc4f6ac..2e7c4cf8def 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -85,6 +85,7 @@ use crate::dom::htmlulistelement::HTMLUListElement; use crate::dom::htmlunknownelement::HTMLUnknownElement; use crate::dom::htmlvideoelement::HTMLVideoElement; use crate::dom::svgelement::SVGElement; +use crate::dom::svgimageelement::SVGImageElement; use crate::dom::svgsvgelement::SVGSVGElement; use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::CanGc; @@ -114,6 +115,7 @@ fn create_svg_element( } match name.local { + local_name!("image") => make!(SVGImageElement), local_name!("svg") => make!(SVGSVGElement), _ => make!(SVGElement), } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 4f1b957c12c..cb120f0b174 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -65,7 +65,7 @@ use xml5ever::serialize::TraversalScope::{ use crate::conversions::Convert; use crate::dom::activation::Activatable; -use crate::dom::attr::{Attr, AttrHelpersForLayout}; +use crate::dom::attr::{Attr, AttrHelpersForLayout, is_relevant_attribute}; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut, ref_filter_map}; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; @@ -1705,7 +1705,7 @@ impl Element { assert!(attr.GetOwnerElement().as_deref() == Some(self)); self.will_mutate_attr(attr); self.attrs.borrow_mut().push(Dom::from_ref(attr)); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable_for(self.upcast()).attribute_mutated(attr, AttributeMutation::Set(None), can_gc); } } @@ -1847,7 +1847,7 @@ impl Element { local_name: &LocalName, value: DOMString, ) -> AttrValue { - if *namespace == ns!() { + if is_relevant_attribute(namespace, local_name) { vtable_for(self.upcast()).parse_plain_attribute(local_name, value) } else { AttrValue::String(value.into()) @@ -1902,7 +1902,7 @@ impl Element { self.attrs.borrow_mut().remove(idx); attr.set_owner(None); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable_for(self.upcast()).attribute_mutated( &attr, AttributeMutation::Removed, @@ -1996,6 +1996,15 @@ impl Element { .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value.to_owned()))) } + pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString { + assert_eq!(*local_name, local_name.to_ascii_lowercase()); + let value = match self.get_attribute(&ns!(), local_name) { + Some(attr) => (&**attr.value()).into(), + None => "".into(), + }; + TrustedHTMLOrString::String(value) + } + pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString { match self.get_attribute(&ns!(), local_name) { Some(x) => x.Value(), @@ -2722,7 +2731,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { attr.set_owner(Some(self)); self.attrs.borrow_mut()[position] = Dom::from_ref(attr); old_attr.set_owner(None); - if attr.namespace() == &ns!() { + if is_relevant_attribute(attr.namespace(), attr.local_name()) { vtable.attribute_mutated( attr, AttributeMutation::Set(Some(&old_attr.value())), diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 0fbff86e44a..18116eee8ae 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -27,6 +27,8 @@ use crate::dom::attr::Attr; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; +use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString; +use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; @@ -40,6 +42,7 @@ use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext}; +use crate::dom::trustedhtml::TrustedHTML; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::CanGc; @@ -595,10 +598,29 @@ impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement { make_url_setter!(SetSrc, "src"); // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc - make_getter!(Srcdoc, "srcdoc"); + fn Srcdoc(&self) -> TrustedHTMLOrString { + let element = self.upcast::<Element>(); + element.get_trusted_html_attribute(&local_name!("srcdoc")) + } // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc - make_setter!(SetSrcdoc, "srcdoc"); + fn SetSrcdoc(&self, value: TrustedHTMLOrString, can_gc: CanGc) -> Fallible<()> { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "HTMLIFrameElement srcdoc", and "script". + let element = self.upcast::<Element>(); + let local_name = &local_name!("srcdoc"); + let value = TrustedHTML::get_trusted_script_compliant_string( + &element.owner_global(), + value, + "HTMLIFrameElement", + local_name, + can_gc, + )?; + // Step 2: Set an attribute value given this, srcdoc's local name, and compliantString. + element.set_attribute(local_name, AttrValue::String(value), can_gc); + Ok(()) + } // https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox fn Sandbox(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 1622cf57b79..91a4e1b1359 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -547,6 +547,7 @@ pub(crate) mod submitevent; pub(crate) mod subtlecrypto; pub(crate) mod svgelement; pub(crate) mod svggraphicselement; +pub(crate) mod svgimageelement; pub(crate) mod svgsvgelement; pub(crate) mod testbinding; pub(crate) mod testbindingiterable; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index ca785773b48..5f08abce354 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -4207,6 +4207,9 @@ impl From<ElementTypeIdWrapper> for LayoutElementType { LayoutElementType::HTMLTextAreaElement }, ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( + SVGGraphicsElementTypeId::SVGImageElement, + )) => LayoutElementType::SVGImageElement, + ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( SVGGraphicsElementTypeId::SVGSVGElement, )) => LayoutElementType::SVGSVGElement, _ => LayoutElementType::Element, diff --git a/components/script/dom/svgelement.rs b/components/script/dom/svgelement.rs index 9c8b990826d..0f36d942f3e 100644 --- a/components/script/dom/svgelement.rs +++ b/components/script/dom/svgelement.rs @@ -82,6 +82,9 @@ impl SVGElementMethods<crate::DomTypeHolder> for SVGElement { }) } + // <https://html.spec.whatwg.org/multipage/#globaleventhandlers> + global_event_handlers!(); + // FIXME: The nonce should be stored in an internal slot instead of an // attribute (https://html.spec.whatwg.org/multipage/#cryptographicnonce) // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce diff --git a/components/script/dom/svgimageelement.rs b/components/script/dom/svgimageelement.rs new file mode 100644 index 00000000000..17a5a9149d8 --- /dev/null +++ b/components/script/dom/svgimageelement.rs @@ -0,0 +1,96 @@ +/* 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 dom_struct::dom_struct; +use html5ever::{LocalName, Prefix, local_name, ns}; +use js::rust::HandleObject; +use style::attr::AttrValue; + +use crate::dom::attr::Attr; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::AttributeMutation; +use crate::dom::node::{Node, NodeTraits}; +use crate::dom::svggraphicselement::SVGGraphicsElement; +use crate::dom::virtualmethods::VirtualMethods; +use crate::script_runtime::CanGc; + +/// <https://svgwg.org/svg2-draft/embedded.html#Placement> +const DEFAULT_WIDTH: u32 = 300; +const DEFAULT_HEIGHT: u32 = 150; + +#[dom_struct] +pub(crate) struct SVGImageElement { + svggraphicselement: SVGGraphicsElement, +} + +impl SVGImageElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGImageElement { + SVGImageElement { + svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + proto: Option<HandleObject>, + can_gc: CanGc, + ) -> DomRoot<SVGImageElement> { + Node::reflect_node_with_proto( + Box::new(SVGImageElement::new_inherited(local_name, prefix, document)), + document, + proto, + can_gc, + ) + } + + /// <https://svgwg.org/svg2-draft/linking.html#processingURL> + fn fetch_image_resource(&self) { + // TODO: Process and fetch the image resource (as HTMLImageElement). + // Reject any resource fetching request immediately. + self.owner_global() + .task_manager() + .dom_manipulation_task_source() + .queue_simple_event(self.upcast(), atom!("error")); + } +} + +impl VirtualMethods for SVGImageElement { + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + self.super_type() + .unwrap() + .attribute_mutated(attr, mutation, can_gc); + if attr.local_name() == &local_name!("href") && + matches!(attr.namespace(), &ns!() | &ns!(xlink)) + { + if let AttributeMutation::Set(_) = mutation { + self.fetch_image_resource(); + } + } + } + + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { + match *name { + local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH), + local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), + } + } +} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 57ecba7b172..1d992b1f301 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -61,6 +61,7 @@ use crate::dom::htmlvideoelement::HTMLVideoElement; use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; use crate::dom::shadowroot::ShadowRoot; use crate::dom::svgelement::SVGElement; +use crate::dom::svgimageelement::SVGImageElement; use crate::dom::svgsvgelement::SVGSVGElement; /// Trait to allow DOM nodes to opt-in to overriding (or adding to) common @@ -299,6 +300,9 @@ pub(crate) fn vtable_for(node: &Node) -> &dyn VirtualMethods { node.downcast::<HTMLTitleElement>().unwrap() as &dyn VirtualMethods }, NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( + SVGGraphicsElementTypeId::SVGImageElement, + ))) => node.downcast::<SVGImageElement>().unwrap() as &dyn VirtualMethods, + NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( SVGGraphicsElementTypeId::SVGSVGElement, ))) => node.downcast::<SVGSVGElement>().unwrap() as &dyn VirtualMethods, NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGElement)) => { diff --git a/components/script/messaging.rs b/components/script/messaging.rs index e0ea9e30af2..08d6fc841cf 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -91,6 +91,7 @@ impl MixedMessage { #[cfg(feature = "webgpu")] ScriptThreadMessage::SetWebGPUPort(..) => None, ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id), + ScriptThreadMessage::EvaluateJavaScript(id, _, _) => Some(*id), }, MixedMessage::FromScript(inner_msg) => match inner_msg { MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index d6ab18be49b..4815e6feae1 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -50,9 +50,9 @@ use devtools_traits::{ }; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, MediaSessionActionType, - MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, - WebDriverScriptCommand, + CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, + JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, MouseButton, + MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand, }; use euclid::Point2D; use euclid::default::Rect; @@ -156,6 +156,7 @@ use crate::script_runtime::{ }; use crate::task_queue::TaskQueue; use crate::task_source::{SendableTaskSource, TaskSourceName}; +use crate::webdriver_handlers::jsval_to_webdriver; use crate::{devtools, webdriver_handlers}; thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = const { Cell::new(None) }); @@ -1878,6 +1879,9 @@ impl ScriptThread { ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states) => { self.handle_set_scroll_states(pipeline_id, scroll_states) }, + ScriptThreadMessage::EvaluateJavaScript(pipeline_id, evaluation_id, script) => { + self.handle_evaluate_javascript(pipeline_id, evaluation_id, script, can_gc); + }, } } @@ -3815,6 +3819,53 @@ impl ScriptThread { ) } } + + fn handle_evaluate_javascript( + &self, + pipeline_id: PipelineId, + evaluation_id: JavaScriptEvaluationId, + script: String, + can_gc: CanGc, + ) { + let Some(window) = self.documents.borrow().find_window(pipeline_id) else { + let _ = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::FinishJavaScriptEvaluation( + evaluation_id, + Err(JavaScriptEvaluationError::WebViewNotReady), + ), + )); + return; + }; + + let global_scope = window.as_global_scope(); + let realm = enter_realm(global_scope); + let context = window.get_cx(); + + rooted!(in(*context) let mut return_value = UndefinedValue()); + global_scope.evaluate_js_on_global_with_result( + &script, + return_value.handle_mut(), + ScriptFetchOptions::default_classic_script(global_scope), + global_scope.api_base_url(), + can_gc, + ); + let result = match jsval_to_webdriver( + context, + global_scope, + return_value.handle(), + (&realm).into(), + can_gc, + ) { + Ok(ref value) => Ok(value.into()), + Err(_) => Err(JavaScriptEvaluationError::SerializationError), + }; + + let _ = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result), + )); + } } impl Drop for ScriptThread { diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 875a9498078..92871bc54aa 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -371,7 +371,7 @@ DOMInterfaces = { }, 'HTMLIFrameElement': { - 'canGc': ['Sandbox'], + 'canGc': ['Sandbox', 'SetSrcdoc'], }, 'HTMLImageElement': { diff --git a/components/script_bindings/webidls/HTMLIFrameElement.webidl b/components/script_bindings/webidls/HTMLIFrameElement.webidl index 8ba58a20f33..b083f51c0f1 100644 --- a/components/script_bindings/webidls/HTMLIFrameElement.webidl +++ b/components/script_bindings/webidls/HTMLIFrameElement.webidl @@ -9,8 +9,8 @@ interface HTMLIFrameElement : HTMLElement { [CEReactions] attribute USVString src; - [CEReactions] - attribute DOMString srcdoc; + [CEReactions, SetterThrows] + attribute (TrustedHTML or DOMString) srcdoc; [CEReactions] attribute DOMString name; diff --git a/components/script_bindings/webidls/SVGElement.webidl b/components/script_bindings/webidls/SVGElement.webidl index e6bc468d5dc..08bcb4a8c99 100644 --- a/components/script_bindings/webidls/SVGElement.webidl +++ b/components/script_bindings/webidls/SVGElement.webidl @@ -18,7 +18,7 @@ interface SVGElement : Element { //void blur(); }; -//SVGElement includes GlobalEventHandlers; +SVGElement includes GlobalEventHandlers; //SVGElement includes SVGElementInstance; SVGElement includes ElementCSSInlineStyle; SVGElement includes HTMLOrSVGElement; diff --git a/components/script_bindings/webidls/SVGImageElement.webidl b/components/script_bindings/webidls/SVGImageElement.webidl new file mode 100644 index 00000000000..bced6277c5e --- /dev/null +++ b/components/script_bindings/webidls/SVGImageElement.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// https://svgwg.org/svg2-draft/embedded.html#InterfaceSVGImageElement +[Exposed=Window, Pref="dom_svg_enabled"] +interface SVGImageElement : SVGGraphicsElement { + //[SameObject] readonly attribute SVGAnimatedLength x; + //[SameObject] readonly attribute SVGAnimatedLength y; + //[SameObject] readonly attribute SVGAnimatedLength width; + //[SameObject] readonly attribute SVGAnimatedLength height; + //[SameObject] readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio; + //attribute DOMString? crossOrigin; +}; + +//SVGImageElement includes SVGURIReference; diff --git a/components/servo/javascript_evaluator.rs b/components/servo/javascript_evaluator.rs new file mode 100644 index 00000000000..41cb5539b05 --- /dev/null +++ b/components/servo/javascript_evaluator.rs @@ -0,0 +1,65 @@ +/* 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::collections::HashMap; + +use base::id::WebViewId; +use constellation_traits::EmbedderToConstellationMessage; +use embedder_traits::{JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId}; + +use crate::ConstellationProxy; + +struct PendingEvaluation { + callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>, +} + +pub(crate) struct JavaScriptEvaluator { + current_id: JavaScriptEvaluationId, + constellation_proxy: ConstellationProxy, + pending_evaluations: HashMap<JavaScriptEvaluationId, PendingEvaluation>, +} + +impl JavaScriptEvaluator { + pub(crate) fn new(constellation_proxy: ConstellationProxy) -> Self { + Self { + current_id: JavaScriptEvaluationId(0), + constellation_proxy, + pending_evaluations: Default::default(), + } + } + + fn generate_id(&mut self) -> JavaScriptEvaluationId { + let next_id = JavaScriptEvaluationId(self.current_id.0 + 1); + std::mem::replace(&mut self.current_id, next_id) + } + + pub(crate) fn evaluate( + &mut self, + webview_id: WebViewId, + script: String, + callback: Box<dyn FnOnce(Result<JSValue, JavaScriptEvaluationError>)>, + ) { + let evaluation_id = self.generate_id(); + self.constellation_proxy + .send(EmbedderToConstellationMessage::EvaluateJavaScript( + webview_id, + evaluation_id, + script, + )); + self.pending_evaluations + .insert(evaluation_id, PendingEvaluation { callback }); + } + + pub(crate) fn finish_evaluation( + &mut self, + evaluation_id: JavaScriptEvaluationId, + result: Result<JSValue, JavaScriptEvaluationError>, + ) { + (self + .pending_evaluations + .remove(&evaluation_id) + .expect("Received request to finish unknown JavaScript evaluation.") + .callback)(result) + } +} diff --git a/components/servo/lib.rs b/components/servo/lib.rs index b8210450cd8..d2c65429ba9 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -18,6 +18,7 @@ //! `WindowMethods` trait. mod clipboard_delegate; +mod javascript_evaluator; mod proxies; mod responders; mod servo_delegate; @@ -82,6 +83,7 @@ pub use gleam::gl; use gleam::gl::RENDERER; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; +use javascript_evaluator::JavaScriptEvaluator; pub use keyboard_types::*; use layout::LayoutFactoryImpl; use log::{Log, Metadata, Record, debug, warn}; @@ -196,6 +198,9 @@ pub struct Servo { compositor: Rc<RefCell<IOCompositor>>, constellation_proxy: ConstellationProxy, embedder_receiver: Receiver<EmbedderMsg>, + /// A struct that tracks ongoing JavaScript evaluations and is responsible for + /// calling the callback when the evaluation is complete. + javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>, /// Tracks whether we are in the process of shutting down, or have shut down. /// This is shared with `WebView`s and the `ServoRenderer`. shutdown_state: Rc<Cell<ShutdownState>>, @@ -487,10 +492,14 @@ impl Servo { opts.debug.convert_mouse_to_touch, ); + let constellation_proxy = ConstellationProxy::new(constellation_chan); Self { delegate: RefCell::new(Rc::new(DefaultServoDelegate)), compositor: Rc::new(RefCell::new(compositor)), - constellation_proxy: ConstellationProxy::new(constellation_chan), + javascript_evaluator: Rc::new(RefCell::new(JavaScriptEvaluator::new( + constellation_proxy.clone(), + ))), + constellation_proxy, embedder_receiver, shutdown_state, webviews: Default::default(), @@ -738,6 +747,11 @@ impl Servo { webview.delegate().request_unload(webview, request); } }, + EmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result) => { + self.javascript_evaluator + .borrow_mut() + .finish_evaluation(evaluation_id, result); + }, EmbedderMsg::Keyboard(webview_id, keyboard_event) => { if let Some(webview) = self.get_webview_handle(webview_id) { webview diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs index 89fbe2025a3..41900015b94 100644 --- a/components/servo/tests/webview.rs +++ b/components/servo/tests/webview.rs @@ -11,12 +11,14 @@ mod common; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use anyhow::ensure; use common::{ServoTest, run_api_tests}; -use servo::{WebViewBuilder, WebViewDelegate}; +use servo::{ + JSValue, JavaScriptEvaluationError, LoadStatus, WebView, WebViewBuilder, WebViewDelegate, +}; #[derive(Default)] struct WebViewDelegateImpl { @@ -44,6 +46,81 @@ fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> { Ok(()) } +fn evaluate_javascript( + servo_test: &ServoTest, + webview: WebView, + script: impl ToString, +) -> Result<JSValue, JavaScriptEvaluationError> { + let load_webview = webview.clone(); + let _ = servo_test.spin(move || Ok(load_webview.load_status() != LoadStatus::Complete)); + + let saved_result = Rc::new(RefCell::new(None)); + let callback_result = saved_result.clone(); + webview.evaluate_javascript(script, move |result| { + *callback_result.borrow_mut() = Some(result) + }); + + let spin_result = saved_result.clone(); + let _ = servo_test.spin(move || Ok(spin_result.borrow().is_none())); + + (*saved_result.borrow()) + .clone() + .expect("Should have waited until value available") +} + +fn test_evaluate_javascript_basic(servo_test: &ServoTest) -> Result<(), anyhow::Error> { + let delegate = Rc::new(WebViewDelegateImpl::default()); + let webview = WebViewBuilder::new(servo_test.servo()) + .delegate(delegate.clone()) + .build(); + + let result = evaluate_javascript(servo_test, webview.clone(), "undefined"); + ensure!(result == Ok(JSValue::Undefined)); + + let result = evaluate_javascript(servo_test, webview.clone(), "null"); + ensure!(result == Ok(JSValue::Null)); + + let result = evaluate_javascript(servo_test, webview.clone(), "42"); + ensure!(result == Ok(JSValue::Number(42.0))); + + let result = evaluate_javascript(servo_test, webview.clone(), "3 + 4"); + ensure!(result == Ok(JSValue::Number(7.0))); + + let result = evaluate_javascript(servo_test, webview.clone(), "'abc' + 'def'"); + ensure!(result == Ok(JSValue::String("abcdef".into()))); + + let result = evaluate_javascript(servo_test, webview.clone(), "let foo = {blah: 123}; foo"); + ensure!(matches!(result, Ok(JSValue::Object(_)))); + if let Ok(JSValue::Object(values)) = result { + ensure!(values.len() == 1); + ensure!(values.get("blah") == Some(&JSValue::Number(123.0))); + } + + let result = evaluate_javascript(servo_test, webview.clone(), "[1, 2, 3, 4]"); + let expected = JSValue::Array(vec![ + JSValue::Number(1.0), + JSValue::Number(2.0), + JSValue::Number(3.0), + JSValue::Number(4.0), + ]); + ensure!(result == Ok(expected)); + + let result = evaluate_javascript(servo_test, webview.clone(), "window"); + ensure!(matches!(result, Ok(JSValue::Window(..)))); + + let result = evaluate_javascript(servo_test, webview.clone(), "document.body"); + ensure!(matches!(result, Ok(JSValue::Element(..)))); + + let result = evaluate_javascript( + servo_test, + webview.clone(), + "document.body.innerHTML += '<iframe>'; frames[0]", + ); + ensure!(matches!(result, Ok(JSValue::Frame(..)))); + + Ok(()) +} + fn test_create_webview_and_immediately_drop_webview_before_shutdown( servo_test: &ServoTest, ) -> Result<(), anyhow::Error> { @@ -54,6 +131,7 @@ fn test_create_webview_and_immediately_drop_webview_before_shutdown( fn main() { run_api_tests!( test_create_webview, + test_evaluate_javascript_basic, // This test needs to be last, as it tests creating and dropping // a WebView right before shutdown. test_create_webview_and_immediately_drop_webview_before_shutdown diff --git a/components/servo/webview.rs b/components/servo/webview.rs index 95eb6dfd154..10786ad8b69 100644 --- a/components/servo/webview.rs +++ b/components/servo/webview.rs @@ -13,8 +13,8 @@ use compositing_traits::WebViewTrait; use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use dpi::PhysicalSize; use embedder_traits::{ - Cursor, InputEvent, LoadStatus, MediaSessionActionType, ScreenGeometry, Theme, TouchEventType, - ViewportDetails, + Cursor, InputEvent, JSValue, JavaScriptEvaluationError, LoadStatus, MediaSessionActionType, + ScreenGeometry, Theme, TouchEventType, ViewportDetails, }; use euclid::{Point2D, Scale, Size2D}; use servo_geometry::DeviceIndependentPixel; @@ -23,6 +23,7 @@ use webrender_api::ScrollLocation; use webrender_api::units::{DeviceIntPoint, DevicePixel, DeviceRect}; use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate}; +use crate::javascript_evaluator::JavaScriptEvaluator; use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate}; use crate::{ConstellationProxy, Servo, WebRenderDebugOption}; @@ -75,6 +76,7 @@ pub(crate) struct WebViewInner { pub(crate) compositor: Rc<RefCell<IOCompositor>>, pub(crate) delegate: Rc<dyn WebViewDelegate>, pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>, + javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>, rect: DeviceRect, hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>, @@ -117,9 +119,10 @@ impl WebView { compositor: servo.compositor.clone(), delegate: builder.delegate, clipboard_delegate: Rc::new(DefaultClipboardDelegate), + javascript_evaluator: servo.javascript_evaluator.clone(), rect: DeviceRect::from_origin_and_size(Point2D::origin(), size), hidpi_scale_factor: builder.hidpi_scale_factor, - load_status: LoadStatus::Complete, + load_status: LoadStatus::Started, url: None, status_text: None, page_title: None, @@ -549,6 +552,20 @@ impl WebView { pub fn paint(&self) -> bool { self.inner().compositor.borrow_mut().render() } + + /// Evaluate the specified string of JavaScript code. Once execution is complete or an error + /// occurs, Servo will call `callback`. + pub fn evaluate_javascript<T: ToString>( + &self, + script: T, + callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static, + ) { + self.inner().javascript_evaluator.borrow_mut().evaluate( + self.id(), + script.to_string(), + Box::new(callback), + ); + } } /// A structure used to expose a view of the [`WebView`] to the Servo diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index 21665c24e57..3856def660e 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -15,8 +15,8 @@ use base::id::{ use canvas_traits::canvas::{CanvasId, CanvasMsg}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{ - AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult, - ViewportDetails, + AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError, + JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails, }; use euclid::default::Size2D as UntypedSize2D; use http::{HeaderMap, Method}; @@ -644,6 +644,11 @@ pub enum ScriptToConstellationMessage { IFrameSizes(Vec<IFrameSizeMsg>), /// Request results from the memory reporter. ReportMemory(IpcSender<MemoryReportResult>), + /// Return the result of the evaluated JavaScript with the given [`JavaScriptEvaluationId`]. + FinishJavaScriptEvaluation( + JavaScriptEvaluationId, + Result<JSValue, JavaScriptEvaluationError>, + ), } impl fmt::Debug for ScriptToConstellationMessage { diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index 134eed6c1d9..d85fbe31bdf 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -19,8 +19,8 @@ use base::Epoch; use base::cross_process_instant::CrossProcessInstant; use base::id::{MessagePortId, PipelineId, WebViewId}; use embedder_traits::{ - CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails, - WebDriverCommandMsg, + CompositorHitTestResult, Cursor, InputEvent, JavaScriptEvaluationId, MediaSessionActionType, + Theme, ViewportDetails, WebDriverCommandMsg, }; use euclid::Vector2D; pub use from_script_message::*; @@ -92,6 +92,9 @@ pub enum EmbedderToConstellationMessage { SetScrollStates(PipelineId, Vec<ScrollState>), /// Notify the constellation that a particular paint metric event has happened for the given pipeline. PaintMetric(PipelineId, PaintMetricEvent), + /// Evaluate a JavaScript string in the context of a `WebView`. When execution is complete or an + /// error is encountered, a correpsonding message will be sent to the embedding layer. + EvaluateJavaScript(WebViewId, JavaScriptEvaluationId, String), } /// A description of a paint metric that is sent from the Servo renderer to the diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index c87fa9019ef..e9427fcc719 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -13,8 +13,10 @@ pub mod resources; pub mod user_content_manager; mod webdriver; +use std::collections::HashMap; use std::ffi::c_void; use std::fmt::{Debug, Display, Error, Formatter}; +use std::hash::Hash; use std::path::PathBuf; use std::sync::Arc; @@ -372,6 +374,12 @@ pub enum EmbedderMsg { DeviceIntRect, IpcSender<Option<usize>>, ), + /// Inform the embedding layer that a JavaScript evaluation has + /// finished with the given result. + FinishJavaScriptEvaluation( + JavaScriptEvaluationId, + Result<JSValue, JavaScriptEvaluationError>, + ), } impl Debug for EmbedderMsg { @@ -857,3 +865,59 @@ impl Display for FocusSequenceNumber { Display::fmt(&self.0, f) } } + +/// An identifier for a particular JavaScript evaluation that is used to track the +/// evaluation from the embedding layer to the script layer and then back. +#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct JavaScriptEvaluationId(pub usize); + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum JSValue { + Undefined, + Null, + Boolean(bool), + Number(f64), + String(String), + Element(String), + Frame(String), + Window(String), + Array(Vec<JSValue>), + Object(HashMap<String, JSValue>), +} + +impl From<&WebDriverJSValue> for JSValue { + fn from(value: &WebDriverJSValue) -> Self { + match value { + WebDriverJSValue::Undefined => Self::Undefined, + WebDriverJSValue::Null => Self::Null, + WebDriverJSValue::Boolean(value) => Self::Boolean(*value), + WebDriverJSValue::Int(value) => Self::Number(*value as f64), + WebDriverJSValue::Number(value) => Self::Number(*value), + WebDriverJSValue::String(value) => Self::String(value.clone()), + WebDriverJSValue::Element(web_element) => Self::Element(web_element.0.clone()), + WebDriverJSValue::Frame(web_frame) => Self::Frame(web_frame.0.clone()), + WebDriverJSValue::Window(web_window) => Self::Window(web_window.0.clone()), + WebDriverJSValue::ArrayLike(vector) => { + Self::Array(vector.iter().map(Into::into).collect()) + }, + WebDriverJSValue::Object(map) => Self::Object( + map.iter() + .map(|(key, value)| (key.clone(), value.into())) + .collect(), + ), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum JavaScriptEvaluationError { + /// An internal Servo error prevented the JavaSript evaluation from completing properly. + /// This indicates a bug in Servo. + InternalError, + /// The `WebView` on which this evaluation request was triggered is not ready. This might + /// happen if the `WebView`'s `Document` is changing due to ongoing load events, for instance. + WebViewNotReady, + /// The script executed successfully, but Servo could not serialize the JavaScript return + /// value into a [`JSValue`]. + SerializationError, +} diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 748c42400a8..29acc51765c 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::ScriptToDevtoolsControlMsg; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme, - ViewportDetails, WebDriverScriptCommand, + CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId, + MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand, }; use euclid::{Rect, Scale, Size2D, UnknownUnit}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; @@ -245,6 +245,9 @@ pub enum ScriptThreadMessage { /// The compositor scrolled and is updating the scroll states of the nodes in the given /// pipeline via the Constellation. SetScrollStates(PipelineId, Vec<ScrollState>), + /// Evaluate the given JavaScript and return a result via a corresponding message + /// to the Constellation. + EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String), } impl fmt::Debug for ScriptThreadMessage { diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 8c5d4edc4e0..1f526b64240 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -114,6 +114,7 @@ pub enum LayoutElementType { HTMLTableRowElement, HTMLTableSectionElement, HTMLTextAreaElement, + SVGImageElement, SVGSVGElement, } diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index 2d49ebbea22..842d9f0dbc2 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -18,7 +18,7 @@ use webdriver::actions::{ }; use webdriver::error::{ErrorStatus, WebDriverError}; -use crate::{Handler, wait_for_script_response}; +use crate::{Handler, WebElement, wait_for_script_response}; // Interval between wheelScroll and pointerMove increments in ms, based on common vsync static POINTERMOVE_INTERVAL: u64 = 17; @@ -393,20 +393,8 @@ impl Handler { let (x, y) = match action.origin { PointerOrigin::Viewport => (x_offset, y_offset), PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset), - PointerOrigin::Element(ref x) => { - let (sender, receiver) = ipc::channel().unwrap(); - self.browsing_context_script_command( - WebDriverScriptCommand::GetElementInViewCenterPoint(x.to_string(), sender), - ) - .unwrap(); - let response = match wait_for_script_response(receiver) { - Ok(response) => response, - Err(WebDriverError { error, .. }) => return Err(error), - }; - let Ok(Some(point)) = response else { - return Err(ErrorStatus::UnknownError); - }; - point + PointerOrigin::Element(ref web_element) => { + self.get_element_origin_relative_coordinates(web_element)? }, }; @@ -526,10 +514,13 @@ impl Handler { }; // Step 3 - 4 - // Get coordinates relative to an origin. Origin must be viewport. + // Get coordinates relative to an origin. let (x, y) = match action.origin { PointerOrigin::Viewport => (x_offset, y_offset), - _ => return Err(ErrorStatus::InvalidArgument), + PointerOrigin::Pointer => return Err(ErrorStatus::InvalidArgument), + PointerOrigin::Element(ref web_element) => { + self.get_element_origin_relative_coordinates(web_element)? + }, }; // Step 5 - 6 @@ -659,4 +650,24 @@ impl Handler { Ok(()) } } + + fn get_element_origin_relative_coordinates( + &self, + web_element: &WebElement, + ) -> Result<(i64, i64), ErrorStatus> { + let (sender, receiver) = ipc::channel().unwrap(); + self.browsing_context_script_command(WebDriverScriptCommand::GetElementInViewCenterPoint( + web_element.to_string(), + sender, + )) + .unwrap(); + let response = match wait_for_script_response(receiver) { + Ok(response) => response, + Err(WebDriverError { error, .. }) => return Err(error), + }; + match response? { + Some(point) => Ok(point), + None => Err(ErrorStatus::UnknownError), + } + } } |