mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 08:01:05 +00:00
fix(selectors): honor document design mode
This commit is contained in:
@@ -55,6 +55,7 @@ pub struct Document {
|
||||
// override execution, but detached/template documents must retain `false`
|
||||
// for fragment parsing and serialization even without a window.
|
||||
scripting_enabled: bool,
|
||||
design_mode_enabled: bool,
|
||||
css_target: Option<NativeNodeId>,
|
||||
default_language: Option<Box<str>>,
|
||||
source_last_modified_ms: Option<f64>,
|
||||
@@ -79,6 +80,7 @@ impl Document {
|
||||
quirks_mode: QuirksMode::NoQuirks,
|
||||
kind: DocumentKind::Html,
|
||||
scripting_enabled,
|
||||
design_mode_enabled: false,
|
||||
css_target: None,
|
||||
default_language: None,
|
||||
source_last_modified_ms: None,
|
||||
@@ -94,6 +96,7 @@ impl Document {
|
||||
quirks_mode: QuirksMode::NoQuirks,
|
||||
kind: DocumentKind::Xml,
|
||||
scripting_enabled: true,
|
||||
design_mode_enabled: false,
|
||||
css_target: None,
|
||||
default_language: None,
|
||||
source_last_modified_ms: None,
|
||||
@@ -144,6 +147,10 @@ impl Document {
|
||||
self.scripting_enabled
|
||||
}
|
||||
|
||||
pub fn design_mode_enabled(&self) -> bool {
|
||||
self.design_mode_enabled
|
||||
}
|
||||
|
||||
pub fn fallback_base_url(&self) -> &Url {
|
||||
self.base_url_state.fallback_base_url()
|
||||
}
|
||||
@@ -181,6 +188,10 @@ impl Document {
|
||||
self.scripting_enabled = scripting_enabled;
|
||||
}
|
||||
|
||||
pub fn set_design_mode_enabled(&mut self, design_mode_enabled: bool) {
|
||||
self.design_mode_enabled = design_mode_enabled;
|
||||
}
|
||||
|
||||
pub fn set_css_target(&mut self, target: Option<NativeNodeId>) -> bool {
|
||||
if self.css_target == target {
|
||||
return false;
|
||||
|
||||
@@ -993,6 +993,15 @@ impl DomHost {
|
||||
.map(Document::scripting_enabled)
|
||||
}
|
||||
|
||||
pub fn document_design_mode_enabled_for_handle(
|
||||
&self,
|
||||
document_handle: DomHandle,
|
||||
) -> Option<bool> {
|
||||
self.node(document_handle)
|
||||
.and_then(Node::as_document)
|
||||
.map(Document::design_mode_enabled)
|
||||
}
|
||||
|
||||
pub fn document_base_url(&self) -> Option<Url> {
|
||||
self.document_base_url_for_handle(self.document_handle())
|
||||
}
|
||||
@@ -1173,6 +1182,25 @@ impl DomHost {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_document_design_mode_enabled_for_handle(
|
||||
&mut self,
|
||||
document_handle: DomHandle,
|
||||
design_mode_enabled: bool,
|
||||
) -> bool {
|
||||
let Some(document) = self
|
||||
.node_mut(document_handle)
|
||||
.and_then(|node| node.data_mut().as_document_mut())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if document.design_mode_enabled() == design_mode_enabled {
|
||||
return false;
|
||||
}
|
||||
document.set_design_mode_enabled(design_mode_enabled);
|
||||
self.record_mutation(MutationScope::QueryState);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_document_quirks_mode_for_handle(
|
||||
&mut self,
|
||||
document_handle: DomHandle,
|
||||
|
||||
@@ -753,7 +753,6 @@ pub(super) struct DocumentRuntime {
|
||||
selector_engine: QueryEngine,
|
||||
selector_debug: SelectorDebugCounters,
|
||||
document: HostDocumentState,
|
||||
design_mode_documents: HashSet<DomHandle>,
|
||||
script_execution_control: crate::script_execution_control::RendererScriptExecutionControl,
|
||||
author_styles_disabled: bool,
|
||||
bypass_content_security_policy: bool,
|
||||
|
||||
@@ -13,18 +13,14 @@ use html5ever::tree_builder::QuirksMode;
|
||||
// selector semantics or orchestrate mutation side effects on their own.
|
||||
impl DocumentRuntime {
|
||||
pub(crate) fn document_design_mode_enabled(&self, document: DomHandle) -> bool {
|
||||
self.design_mode_documents.contains(&document)
|
||||
self.dom_host
|
||||
.document_design_mode_enabled_for_handle(document)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn set_document_design_mode_enabled(&mut self, document: DomHandle, enabled: bool) {
|
||||
if !self.dom_host.node(document).is_some_and(Node::is_document) {
|
||||
return;
|
||||
}
|
||||
if enabled {
|
||||
self.design_mode_documents.insert(document);
|
||||
} else {
|
||||
self.design_mode_documents.remove(&document);
|
||||
}
|
||||
self.dom_host
|
||||
.set_document_design_mode_enabled_for_handle(document, enabled);
|
||||
}
|
||||
|
||||
pub(crate) fn snapshot_document(&self) -> NativeDom {
|
||||
|
||||
@@ -73,7 +73,6 @@ impl DocumentRuntime {
|
||||
selector_engine: QueryEngine,
|
||||
selector_debug: SelectorDebugCounters::default(),
|
||||
document,
|
||||
design_mode_documents: HashSet::new(),
|
||||
script_execution_control: Default::default(),
|
||||
author_styles_disabled: false,
|
||||
bypass_content_security_policy: false,
|
||||
@@ -206,7 +205,6 @@ impl DocumentRuntime {
|
||||
selector_engine: _,
|
||||
selector_debug: _,
|
||||
document: _,
|
||||
design_mode_documents: _,
|
||||
script_execution_control: _,
|
||||
author_styles_disabled: _,
|
||||
bypass_content_security_policy: _,
|
||||
|
||||
@@ -2045,25 +2045,42 @@ fn document_design_mode_controls_content_editable_without_crossing_shadow_roots(
|
||||
const inside = document.createElement('span');
|
||||
root.appendChild(inside);
|
||||
document.body.append(inherited, disabled);
|
||||
const before = [document.designMode, inherited.isContentEditable].join(':');
|
||||
const before = [
|
||||
document.designMode,
|
||||
inherited.isContentEditable,
|
||||
inherited.matches(':read-write'),
|
||||
inherited.matches(':read-only')
|
||||
].join(':');
|
||||
document.designMode = 'ON';
|
||||
const enabled = [
|
||||
document.designMode,
|
||||
inherited.isContentEditable,
|
||||
disabled.isContentEditable,
|
||||
inside.isContentEditable
|
||||
inside.isContentEditable,
|
||||
inherited.matches(':read-write'),
|
||||
inherited.matches(':read-only'),
|
||||
disabled.matches(':read-write'),
|
||||
inside.matches(':read-write')
|
||||
].join(':');
|
||||
document.designMode = 'invalid';
|
||||
const afterInvalid = document.designMode;
|
||||
document.designMode = 'off';
|
||||
const disabledAgain = [document.designMode, inherited.isContentEditable].join(':');
|
||||
const disabledAgain = [
|
||||
document.designMode,
|
||||
inherited.isContentEditable,
|
||||
inherited.matches(':read-write'),
|
||||
inherited.matches(':read-only')
|
||||
].join(':');
|
||||
return [before, enabled, afterInvalid, disabledAgain].join('|');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("Document designMode contenteditable probe should evaluate");
|
||||
|
||||
assert_eq!(result, "off:false|on:true:false:false|on|off:false");
|
||||
assert_eq!(
|
||||
result,
|
||||
"off:false:false:true|on:true:false:false:true:false:false:false|on|off:false:false:true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1482,6 +1482,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dom_api_selectors_design_mode_marks_connected_elements_read_write() {
|
||||
let url = url::Url::parse("https://example.test/").unwrap();
|
||||
let mut host = DomHost::from_dom(NativeDom::new_html(url));
|
||||
host.reset_html_document_shell();
|
||||
let body = host.document_body_handle().unwrap();
|
||||
let paragraph = host.create_element("p");
|
||||
let blocked = host.create_element("div");
|
||||
assert!(host.set_attribute(blocked, "contenteditable", "false"));
|
||||
let detached = host.create_element("section");
|
||||
assert!(host.append_child(body, paragraph));
|
||||
assert!(host.append_child(body, blocked));
|
||||
assert!(host.set_document_design_mode_enabled_for_handle(host.document_handle(), true));
|
||||
|
||||
let engine = QueryEngine;
|
||||
assert!(
|
||||
engine
|
||||
.matches_host(&host, paragraph, ":read-write")
|
||||
.unwrap()
|
||||
);
|
||||
assert!(!engine.matches_host(&host, paragraph, ":read-only").unwrap());
|
||||
assert!(!engine.matches_host(&host, blocked, ":read-write").unwrap());
|
||||
assert!(engine.matches_host(&host, blocked, ":read-only").unwrap());
|
||||
assert!(!engine.matches_host(&host, detached, ":read-write").unwrap());
|
||||
assert!(engine.matches_host(&host, detached, ":read-only").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dom_api_selectors_lang_uses_nearest_language_attribute() {
|
||||
let url = url::Url::parse("https://example.test/").unwrap();
|
||||
|
||||
@@ -344,13 +344,19 @@ impl<'a> QueryElement<'a> {
|
||||
pub(super) fn is_editable(self) -> bool {
|
||||
let mut current = Some(self.handle);
|
||||
while let Some(handle) = current {
|
||||
if let Some(element) = self.host.node(handle).and_then(Node::as_element)
|
||||
let Some(node) = self.host.node(handle) else {
|
||||
return false;
|
||||
};
|
||||
if let Some(document) = node.as_document() {
|
||||
return document.design_mode_enabled();
|
||||
}
|
||||
if let Some(element) = node.as_element()
|
||||
&& let Some(value) = element.attribute("contenteditable")
|
||||
&& let Some(is_editable) = contenteditable_value_is_editable(value)
|
||||
{
|
||||
return is_editable;
|
||||
}
|
||||
current = self.host.node(handle).and_then(Node::parent_node);
|
||||
current = node.parent_node();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user