mirror of
https://github.com/lexmount/moli.git
synced 2026-10-19 00:01:26 +00:00
fix(svg): implement deselectAll
This commit is contained in:
@@ -490,6 +490,13 @@ struct SvgSvgElementTemplateMethodsDeclaration {
|
||||
)]
|
||||
create_svg_rect: (),
|
||||
|
||||
#[webapi(
|
||||
method = "deselectAll",
|
||||
length = 0,
|
||||
callback = svg_svg_element_deselect_all_callback
|
||||
)]
|
||||
deselect_all: (),
|
||||
|
||||
#[webapi(
|
||||
method = "createSVGMatrix",
|
||||
length = 0,
|
||||
|
||||
@@ -1548,6 +1548,56 @@ pub(super) fn svg_svg_element_create_matrix_callback<'s>(
|
||||
rv.set(super::super::geometry_runtime::build_dom_matrix_identity_object(scope).into());
|
||||
}
|
||||
|
||||
pub(super) fn svg_svg_element_deselect_all_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Ok((runtime_ptr, handle)) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, args.this())
|
||||
else {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
"SVGSVGElement.deselectAll called on incompatible receiver.",
|
||||
);
|
||||
return;
|
||||
};
|
||||
let (is_svg_root, owner_document) = {
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let is_svg_root = runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(|node| node.as_element())
|
||||
.is_some_and(|element| element.is_svg_element("svg"));
|
||||
let owner_document = runtime.dom_host().owner_document_handle(handle);
|
||||
(is_svg_root, owner_document)
|
||||
};
|
||||
if !is_svg_root {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
"SVGSVGElement.deselectAll called on incompatible receiver.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(owner_document) = owner_document else {
|
||||
return;
|
||||
};
|
||||
let Some(window) = crate::native_bridge::document::document_associated_window_for_handle(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
owner_document,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let Some(selection) = selection_value_for_window(scope, window) else {
|
||||
return;
|
||||
};
|
||||
if selection_has_range(scope, selection) {
|
||||
selection_clear(scope, selection);
|
||||
selection_dispatch_change(scope);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn svg_svg_element_create_transform_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::{build_dom_point_object, build_dom_rect_object, optional_dom_point_init_arg};
|
||||
use super::{
|
||||
build_dom_point_object, build_dom_rect_object, optional_dom_point_init_arg,
|
||||
selection::{selection_clear, selection_dispatch_change, selection_has_range},
|
||||
selection_value_for_window,
|
||||
};
|
||||
use crate::{
|
||||
native_bridge::throw_dom_exception,
|
||||
util::{callback_data_item, get_private_value, set_private_value, v8_string, v8str},
|
||||
|
||||
@@ -1696,6 +1696,30 @@ fn document_associated_window_for_object<'s>(
|
||||
.map(|context| context.global(scope))
|
||||
}
|
||||
|
||||
pub(crate) fn document_associated_window_for_handle<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
handle: DomHandle,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if !node_is_document(unsafe { &*runtime_ptr }, handle) {
|
||||
return None;
|
||||
}
|
||||
let document = unsafe { &mut *runtime_ptr }
|
||||
.native_bridge_mut()
|
||||
.wrap_handle(scope, runtime_ptr, handle)?;
|
||||
if let Some(window) = get_private_value(scope, document, DOCUMENT_ASSOCIATED_WINDOW_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
return Some(window);
|
||||
}
|
||||
if unsafe { &*runtime_ptr }.dom_host().document_handle() != handle {
|
||||
return None;
|
||||
}
|
||||
document
|
||||
.get_creation_context(scope)
|
||||
.map(|context| context.global(scope))
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn set_document_associated_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
document: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -8659,6 +8659,110 @@ fn reflected_dom_token_list_attributes_are_live_same_object_and_owner_scoped() {
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_svg_element_deselect_all_clears_the_owner_document_selection() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://svg-deselect-all.test/",
|
||||
"<!doctype html><html><head></head><body></body></html>",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const svgNamespace = "http://www.w3.org/2000/svg";
|
||||
const outer = document.createElementNS(svgNamespace, "svg");
|
||||
const inner = document.createElementNS(svgNamespace, "svg");
|
||||
const svgText = document.createElementNS(svgNamespace, "text");
|
||||
const htmlText = document.createElement("p");
|
||||
svgText.textContent = "SVG selection";
|
||||
htmlText.textContent = "HTML selection";
|
||||
inner.appendChild(svgText);
|
||||
outer.appendChild(inner);
|
||||
document.body.append(outer, htmlText);
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGSVGElement.prototype,
|
||||
"deselectAll"
|
||||
);
|
||||
assert(!!descriptor, "deselectAll descriptor");
|
||||
assert(typeof descriptor.value === "function", "deselectAll function");
|
||||
assert(descriptor.value.name === "deselectAll", "deselectAll name");
|
||||
assert(descriptor.value.length === 0, "deselectAll length");
|
||||
assert(descriptor.enumerable && descriptor.writable && descriptor.configurable,
|
||||
"deselectAll descriptor flags");
|
||||
|
||||
const selection = window.getSelection();
|
||||
const select = node => {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(node);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
assert(selection.rangeCount === 1, "selection precondition");
|
||||
};
|
||||
|
||||
outer.deselectAll();
|
||||
assert(selection.rangeCount === 0 && selection.isCollapsed,
|
||||
"empty selection stays empty");
|
||||
|
||||
select(svgText);
|
||||
outer.deselectAll();
|
||||
assert(selection.rangeCount === 0 && selection.isCollapsed,
|
||||
"outer svg clears SVG selection");
|
||||
|
||||
select(htmlText);
|
||||
inner.deselectAll();
|
||||
assert(selection.rangeCount === 0 && selection.isCollapsed,
|
||||
"inner svg clears selection outside its subtree");
|
||||
|
||||
select(svgText);
|
||||
const originalDocumentGetSelection = document.getSelection;
|
||||
const originalRemoveAllRanges = Selection.prototype.removeAllRanges;
|
||||
document.getSelection = () => { throw new Error("observable getSelection call"); };
|
||||
Selection.prototype.removeAllRanges = () => {
|
||||
throw new Error("observable removeAllRanges call");
|
||||
};
|
||||
Object.defineProperty(outer, "ownerDocument", {
|
||||
value: null,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(document, "defaultView", {
|
||||
value: null,
|
||||
configurable: true
|
||||
});
|
||||
try {
|
||||
outer.deselectAll();
|
||||
} finally {
|
||||
delete outer.ownerDocument;
|
||||
delete document.defaultView;
|
||||
document.getSelection = originalDocumentGetSelection;
|
||||
Selection.prototype.removeAllRanges = originalRemoveAllRanges;
|
||||
}
|
||||
assert(selection.rangeCount === 0, "deselectAll uses internal selection state");
|
||||
|
||||
let borrowed = "returned";
|
||||
try {
|
||||
descriptor.value.call(document.createElement("div"));
|
||||
} catch (error) {
|
||||
borrowed = error && error.name;
|
||||
}
|
||||
assert(borrowed === "TypeError", "deselectAll receiver brand");
|
||||
|
||||
const detachedDocument = document.implementation.createHTMLDocument("");
|
||||
const detachedSvg = detachedDocument.createElementNS(svgNamespace, "svg");
|
||||
detachedSvg.deselectAll();
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("SVGSVGElement.deselectAll should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_rel_list_supported_tokens_are_ascii_case_insensitive_and_owner_specific() {
|
||||
let mut vm = new_storage_test_vm("https://rel-list-supports.test/");
|
||||
|
||||
Reference in New Issue
Block a user