mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
script: Implement DocumentOrShadowDOM.adoptedStylesheet
with FrozenArray
(#38163)
Spec: https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets Implement `DocumentOrShadowDOM.adoptedStylesheet`. Due to `ObservableArray` being a massive issue on its own, it will be as it was a `FrozenArray` at first. This approach is similar to how Gecko implement adopted stylesheet. See https://phabricator.services.mozilla.com/D144547#change-IXyOzxxFn8sU. All of the changes will be gated behind a preference `dom_adoptedstylesheet_enabled`. Adopted stylesheet is implemented by adding the setter and getter of it. While the getter works like a normal attribute getter, the setter need to consider the inner working of document and shadow root StylesheetSet, specifically the ordering and the invalidations. Particularly for setter, we will clear all of the adopted stylesheet within the StylesheetSet and readd them. Possible optimization exist, but the focus should be directed to implementing `ObservableArray`. More context about the implementations https://hackmd.io/vtJAn4UyS_O0Idvk5dCO_w. Testing: Existing WPT Coverage Fixes: https://github.com/servo/servo/issues/37561 --------- Signed-off-by: Jo Steven Novaryo <jo.steven.novaryo@huawei.com>
This commit is contained in:
parent
d2e5137201
commit
f523445fc3
24 changed files with 434 additions and 139 deletions
|
@ -2,12 +2,17 @@
|
|||
* 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::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
use embedder_traits::UntrustedNodeAddress;
|
||||
use euclid::default::Point2D;
|
||||
use js::rust::HandleValue;
|
||||
use layout_api::{NodesFromPointQueryType, QueryMsg};
|
||||
use script_bindings::error::{Error, ErrorResult};
|
||||
use script_bindings::script_runtime::JSContext;
|
||||
use servo_arc::Arc;
|
||||
use servo_config::pref;
|
||||
use style::invalidation::media_queries::{MediaListKey, ToMediaListKey};
|
||||
use style::media_queries::MediaList;
|
||||
use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard};
|
||||
|
@ -19,6 +24,7 @@ use super::bindings::trace::HashMapTracedValues;
|
|||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
|
||||
use crate::dom::bindings::conversions::{ConversionResult, SafeFromJSValConvertible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
|
@ -26,6 +32,7 @@ use crate::dom::element::Element;
|
|||
use crate::dom::htmlelement::HTMLElement;
|
||||
use crate::dom::node::{self, Node, VecPreOrderInsertionHelper};
|
||||
use crate::dom::shadowroot::ShadowRoot;
|
||||
use crate::dom::stylesheetlist::StyleSheetListOwner;
|
||||
use crate::dom::types::CSSStyleSheet;
|
||||
use crate::dom::window::Window;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
@ -37,8 +44,6 @@ use crate::stylesheet_set::StylesheetSetRef;
|
|||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
pub(crate) enum StylesheetSource {
|
||||
Element(Dom<Element>),
|
||||
// TODO(stevennovaryo): This type of enum would not be used until we implement adopted stylesheet
|
||||
#[allow(dead_code)]
|
||||
Constructed(Dom<CSSStyleSheet>),
|
||||
}
|
||||
|
||||
|
@ -56,6 +61,10 @@ impl StylesheetSource {
|
|||
StylesheetSource::Constructed(ss) => ss.is_constructed(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_constructed(&self) -> bool {
|
||||
matches!(self, StylesheetSource::Constructed(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
|
@ -285,6 +294,10 @@ impl DocumentOrShadowRoot {
|
|||
) {
|
||||
debug_assert!(owner.is_a_valid_owner(), "Wat");
|
||||
|
||||
if owner.is_constructed() && !pref!(dom_adoptedstylesheet_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sheet = ServoStylesheetInDocument { sheet, owner };
|
||||
|
||||
let guard = style_shared_lock.read();
|
||||
|
@ -345,4 +358,111 @@ impl DocumentOrShadowRoot {
|
|||
let elements = id_map.entry(id.clone()).or_default();
|
||||
elements.insert_pre_order(element, &root);
|
||||
}
|
||||
|
||||
/// Inner part of adopted stylesheet. We are setting it by, assuming it is a FrozenArray
|
||||
/// instead of an ObservableArray. Thus, it would have a completely different workflow
|
||||
/// compared to the spec. The workflow here is actually following Gecko's implementation
|
||||
/// of AdoptedStylesheet before the implementation of ObservableArray.
|
||||
///
|
||||
/// The main purpose from this function is to set the `&mut adopted_stylesheet` to match
|
||||
/// `incoming_stylesheet` and update the corresponding Styleset in a Document or a ShadowRoot.
|
||||
/// In case of duplicates, the setter will respect the last duplicates.
|
||||
///
|
||||
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
|
||||
// TODO: Handle duplicated adoptedstylesheet correctly, Stylo is preventing duplicates inside a
|
||||
// Stylesheet Set. But this is not ideal. https://bugzilla.mozilla.org/show_bug.cgi?id=1978755
|
||||
fn set_adopted_stylesheet(
|
||||
adopted_stylesheets: &mut Vec<Dom<CSSStyleSheet>>,
|
||||
incoming_stylesheets: &[Dom<CSSStyleSheet>],
|
||||
owner: &StyleSheetListOwner,
|
||||
) -> ErrorResult {
|
||||
if !pref!(dom_adoptedstylesheet_enabled) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let owner_doc = match owner {
|
||||
StyleSheetListOwner::Document(doc) => doc,
|
||||
StyleSheetListOwner::ShadowRoot(root) => root.owner_doc(),
|
||||
};
|
||||
|
||||
for sheet in incoming_stylesheets.iter() {
|
||||
// > If value’s constructed flag is not set, or its constructor document is not equal
|
||||
// > to this DocumentOrShadowRoot’s node document, throw a "NotAllowedError" DOMException.
|
||||
if !sheet.constructor_document_matches(owner_doc) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// The set to check for the duplicates when removing the old stylesheets.
|
||||
let mut stylesheet_remove_set = HashSet::with_capacity(adopted_stylesheets.len());
|
||||
|
||||
// Remove the old stylesheets from the StyleSet. This workflow is limited by utilities
|
||||
// Stylo StyleSet given to us.
|
||||
// TODO(stevennovaryo): we could optimize this by maintaining the longest common prefix
|
||||
// but we should consider the implementation of ObservableArray as well.
|
||||
for sheet_to_remove in adopted_stylesheets.iter() {
|
||||
// Check for duplicates, only proceed with the removal if the stylesheet is not removed yet.
|
||||
if stylesheet_remove_set.insert(sheet_to_remove) {
|
||||
owner.remove_stylesheet(
|
||||
StylesheetSource::Constructed(sheet_to_remove.clone()),
|
||||
sheet_to_remove.style_stylesheet_arc(),
|
||||
);
|
||||
sheet_to_remove.remove_adopter(owner);
|
||||
}
|
||||
}
|
||||
|
||||
// The set to check for the duplicates when adding a new stylesheet.
|
||||
let mut stylesheet_add_set = HashSet::with_capacity(incoming_stylesheets.len());
|
||||
|
||||
// Readd all stylesheet to the StyleSet. This workflow is limited by the utilities
|
||||
// Stylo StyleSet given to us.
|
||||
for sheet in incoming_stylesheets.iter() {
|
||||
// Check for duplicates.
|
||||
if !stylesheet_add_set.insert(sheet) {
|
||||
// The idea is that this case is rare, so we pay the price of removing the
|
||||
// old sheet from the styles and append it later rather than the other way
|
||||
// around.
|
||||
owner.remove_stylesheet(
|
||||
StylesheetSource::Constructed(sheet.clone()),
|
||||
sheet.style_stylesheet_arc(),
|
||||
);
|
||||
} else {
|
||||
sheet.add_adopter(owner.clone());
|
||||
}
|
||||
|
||||
owner.append_constructed_stylesheet(sheet);
|
||||
}
|
||||
|
||||
*adopted_stylesheets = incoming_stylesheets.to_vec();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set adoptedStylesheet given a js value by converting and passing the converted
|
||||
/// values to the inner [DocumentOrShadowRoot::set_adopted_stylesheet].
|
||||
pub(crate) fn set_adopted_stylesheet_from_jsval(
|
||||
context: JSContext,
|
||||
adopted_stylesheets: &mut Vec<Dom<CSSStyleSheet>>,
|
||||
incoming_value: HandleValue,
|
||||
owner: &StyleSheetListOwner,
|
||||
) -> ErrorResult {
|
||||
let maybe_stylesheets =
|
||||
Vec::<DomRoot<CSSStyleSheet>>::safe_from_jsval(context, incoming_value, ());
|
||||
|
||||
match maybe_stylesheets {
|
||||
Ok(ConversionResult::Success(stylesheets)) => {
|
||||
rooted_vec!(let stylesheets <- stylesheets.to_owned().iter().map(|s| s.as_traced()));
|
||||
|
||||
DocumentOrShadowRoot::set_adopted_stylesheet(
|
||||
adopted_stylesheets,
|
||||
&stylesheets,
|
||||
owner,
|
||||
)
|
||||
},
|
||||
Ok(ConversionResult::Failure(msg)) => Err(Error::Type(msg.to_string())),
|
||||
Err(_) => Err(Error::Type(
|
||||
"The provided value is not a sequence of 'CSSStylesheet'.".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue