aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/canvas/backend.rs31
-rw-r--r--components/canvas/raqote_backend.rs46
-rw-r--r--components/compositing/webview_renderer.rs5
-rw-r--r--components/constellation/constellation.rs73
-rw-r--r--components/constellation/tracing.rs5
-rw-r--r--components/layout/flexbox/layout.rs13
-rw-r--r--components/script/dom/attr.rs10
-rw-r--r--components/script/dom/create.rs2
-rw-r--r--components/script/dom/element.rs19
-rw-r--r--components/script/dom/htmliframeelement.rs26
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/node.rs3
-rw-r--r--components/script/dom/svgelement.rs3
-rw-r--r--components/script/dom/svgimageelement.rs96
-rw-r--r--components/script/dom/virtualmethods.rs4
-rw-r--r--components/script/messaging.rs1
-rw-r--r--components/script/script_thread.rs57
-rw-r--r--components/script_bindings/codegen/Bindings.conf2
-rw-r--r--components/script_bindings/webidls/HTMLIFrameElement.webidl4
-rw-r--r--components/script_bindings/webidls/SVGElement.webidl2
-rw-r--r--components/script_bindings/webidls/SVGImageElement.webidl16
-rw-r--r--components/servo/javascript_evaluator.rs65
-rw-r--r--components/servo/lib.rs16
-rw-r--r--components/servo/tests/webview.rs82
-rw-r--r--components/servo/webview.rs23
-rw-r--r--components/shared/constellation/from_script_message.rs9
-rw-r--r--components/shared/constellation/lib.rs7
-rw-r--r--components/shared/embedder/lib.rs64
-rw-r--r--components/shared/script/lib.rs7
-rw-r--r--components/shared/script_layout/lib.rs1
-rw-r--r--components/webdriver_server/actions.rs45
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),
+ }
+ }
}