зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1559076 - Implement shadow part forwarding (minus invalidation). r=heycam
Some of the stuff, in particular inside GeckoBindings stuff should be refactored to be less ugly and duplicate a bit less code, but the rest of the code should be landable as is. Some invalidation changes are already needed because we weren't matching with the right shadow host during invalidation (which made existing ::part() tests fail). Pending invalidation work: * Making exportparts work right on the snapshots. * Invalidating parts from descendant hosts. They're not very hard but I need to think how to best implement it: * Maybe get rid of ShadowRoot::mParts and just walk DOM descendants in the Shadow DOM. * Maybe implement a ElementHasExportPartsAttr much like HasPartAttr and use that to keep the list of elements. * Maybe invalidate :host and ::part() together in here[1] * Maybe something else. Opinions? [1]: https://searchfox.org/mozilla-central/rev/131338e5017bc0283d86fb73844407b9a2155c98/servo/components/style/invalidation/element/invalidator.rs#561 Differential Revision: https://phabricator.services.mozilla.com/D53730 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
08e22cc986
Коммит
74823e8b90
|
@ -36,6 +36,7 @@ struct MiscContainer;
|
|||
|
||||
namespace mozilla {
|
||||
class DeclarationBlock;
|
||||
class ShadowParts;
|
||||
} // namespace mozilla
|
||||
|
||||
#define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
|
||||
|
@ -212,6 +213,7 @@ class nsAttrValue {
|
|||
inline nsIURI* GetURLValue() const;
|
||||
inline double GetDoubleValue() const;
|
||||
bool GetIntMarginValue(nsIntMargin& aMargin) const;
|
||||
inline const mozilla::ShadowParts& GetShadowPartsValue() const;
|
||||
|
||||
/**
|
||||
* Returns the string corresponding to the stored enum value.
|
||||
|
|
|
@ -253,4 +253,9 @@ inline void nsAttrValue::ToString(mozilla::dom::DOMString& aResult) const {
|
|||
}
|
||||
}
|
||||
|
||||
inline const mozilla::ShadowParts& nsAttrValue::GetShadowPartsValue() const {
|
||||
MOZ_ASSERT(Type() == eShadowParts);
|
||||
return *GetMiscContainer()->mValue.mShadowParts;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ServoElementSnapshot.h"
|
||||
#include "mozilla/ShadowParts.h"
|
||||
#include "mozilla/StaticPrefs_layout.h"
|
||||
#include "mozilla/RestyleManager.h"
|
||||
#include "mozilla/SizeOfState.h"
|
||||
|
@ -1933,3 +1934,21 @@ void Gecko_LoadData_DeregisterLoad(const StyleLoadData* aData) {
|
|||
void Gecko_PrintfStderr(const nsCString* aStr) {
|
||||
printf_stderr("%s", aStr->get());
|
||||
}
|
||||
|
||||
nsAtom* Gecko_Element_ImportedPart(const mozilla::dom::Element* aElement,
|
||||
nsAtom* aPartName) {
|
||||
const nsAttrValue* v = aElement->GetParsedAttr(nsGkAtoms::exportparts);
|
||||
if (!v || v->Type() != nsAttrValue::eShadowParts) {
|
||||
return nullptr;
|
||||
}
|
||||
return v->GetShadowPartsValue().GetReverse(aPartName);
|
||||
}
|
||||
|
||||
nsAtom* Gecko_Element_ExportedPart(const mozilla::dom::Element* aElement,
|
||||
nsAtom* aPartName) {
|
||||
const nsAttrValue* v = aElement->GetParsedAttr(nsGkAtoms::exportparts);
|
||||
if (!v || v->Type() != nsAttrValue::eShadowParts) {
|
||||
return nullptr;
|
||||
}
|
||||
return v->GetShadowPartsValue().Get(aPartName);
|
||||
}
|
||||
|
|
|
@ -105,6 +105,9 @@ void Gecko_DestroyStyleChildrenIterator(mozilla::dom::StyleChildrenIterator*);
|
|||
|
||||
const nsINode* Gecko_GetNextStyleChild(mozilla::dom::StyleChildrenIterator*);
|
||||
|
||||
nsAtom* Gecko_Element_ImportedPart(const mozilla::dom::Element*, nsAtom*);
|
||||
nsAtom* Gecko_Element_ExportedPart(const mozilla::dom::Element*, nsAtom*);
|
||||
|
||||
NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::css::SheetLoadDataHolder,
|
||||
SheetLoadDataHolder);
|
||||
|
||||
|
|
|
@ -105,7 +105,9 @@ ShadowParts ShadowParts::Parse(const nsAString& aString) {
|
|||
MOZ_ASSERT(!mapping.second);
|
||||
continue;
|
||||
}
|
||||
nsAtom* second = mapping.second.get();
|
||||
parts.mMappings.GetOrInsert(mapping.first) = std::move(mapping.second);
|
||||
parts.mReverseMappings.GetOrInsert(second) = std::move(mapping.first);
|
||||
}
|
||||
|
||||
return parts;
|
||||
|
|
|
@ -25,6 +25,10 @@ class ShadowParts final {
|
|||
return mMappings.GetWeak(aName);
|
||||
}
|
||||
|
||||
nsAtom* GetReverse(nsAtom* aName) const {
|
||||
return mReverseMappings.GetWeak(aName);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void Dump() const;
|
||||
#endif
|
||||
|
@ -32,7 +36,10 @@ class ShadowParts final {
|
|||
private:
|
||||
ShadowParts() = default;
|
||||
|
||||
// TODO(emilio): If the two hashtables take a lot of memory we should consider
|
||||
// just using an nsTArray<Pair<>> or something.
|
||||
nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, nsAtom> mMappings;
|
||||
nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, nsAtom> mReverseMappings;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::nth_index_cache::NthIndexCacheInner;
|
|||
use crate::parser::{AncestorHashes, Combinator, Component, LocalName};
|
||||
use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList};
|
||||
use crate::tree::Element;
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::Borrow;
|
||||
use std::iter;
|
||||
|
||||
|
@ -667,7 +668,41 @@ where
|
|||
|
||||
match *selector {
|
||||
Component::Combinator(_) => unreachable!(),
|
||||
Component::Part(ref parts) => parts.iter().all(|part| element.is_part(part)),
|
||||
Component::Part(ref parts) => {
|
||||
let mut hosts = SmallVec::<[E; 4]>::new();
|
||||
|
||||
let mut host = match element.containing_shadow_host() {
|
||||
Some(h) => h,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
loop {
|
||||
let outer_host = host.containing_shadow_host();
|
||||
if outer_host.as_ref().map(|h| h.opaque()) == context.shared.current_host {
|
||||
break;
|
||||
}
|
||||
let outer_host = match outer_host {
|
||||
Some(h) => h,
|
||||
None => return false,
|
||||
};
|
||||
// TODO(emilio): if worth it, we could early return if
|
||||
// host doesn't have the exportparts attribute.
|
||||
hosts.push(host);
|
||||
host = outer_host;
|
||||
}
|
||||
|
||||
// Translate the part into the right scope.
|
||||
parts.iter().all(|part| {
|
||||
let mut part = part.clone();
|
||||
for host in hosts.iter().rev() {
|
||||
part = match host.imported_part(&part) {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
}
|
||||
element.is_part(&part)
|
||||
})
|
||||
},
|
||||
Component::Slotted(ref selector) => {
|
||||
// <slots> are never flattened tree slottables.
|
||||
!element.is_html_slot_element() &&
|
||||
|
|
|
@ -117,6 +117,20 @@ pub trait Element: Sized + Clone + Debug {
|
|||
case_sensitivity: CaseSensitivity,
|
||||
) -> bool;
|
||||
|
||||
/// Returns the mapping from the `exportparts` attribute in the regular
|
||||
/// direction, that is, inner-tree -> outer-tree.
|
||||
fn exported_part(
|
||||
&self,
|
||||
name: &<Self::Impl as SelectorImpl>::PartName,
|
||||
) -> Option<<Self::Impl as SelectorImpl>::PartName>;
|
||||
|
||||
/// Returns the mapping from the `exportparts` attribute in the reverse
|
||||
/// direction, that is, in an outer-tree -> inner-tree direction.
|
||||
fn imported_part(
|
||||
&self,
|
||||
name: &<Self::Impl as SelectorImpl>::PartName,
|
||||
) -> Option<<Self::Impl as SelectorImpl>::PartName>;
|
||||
|
||||
fn is_part(&self, name: &<Self::Impl as SelectorImpl>::PartName) -> bool;
|
||||
|
||||
/// Returns whether this element matches `:empty`.
|
||||
|
|
|
@ -172,7 +172,11 @@ where
|
|||
};
|
||||
|
||||
for selector in self.selector_list.0.iter() {
|
||||
target_vector.push(Invalidation::new(selector, 0))
|
||||
target_vector.push(Invalidation::new(
|
||||
selector,
|
||||
self.matching_context.current_host.clone(),
|
||||
0,
|
||||
))
|
||||
}
|
||||
|
||||
false
|
||||
|
|
|
@ -2216,6 +2216,28 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn imported_part(&self, name: &Atom) -> Option<Atom> {
|
||||
let imported = unsafe {
|
||||
bindings::Gecko_Element_ImportedPart(self.0, name.as_ptr())
|
||||
};
|
||||
if imported.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { Atom::from_raw(imported) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exported_part(&self, name: &Atom) -> Option<Atom> {
|
||||
let exported = unsafe {
|
||||
bindings::Gecko_Element_ExportedPart(self.0, name.as_ptr())
|
||||
};
|
||||
if exported.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { Atom::from_raw(exported) })
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||
let attr = match self.get_class_attr() {
|
||||
|
|
|
@ -79,7 +79,13 @@ where
|
|||
continue;
|
||||
}
|
||||
|
||||
self_invalidations.push(Invalidation::new(&dependency.selector, 0));
|
||||
// We pass `None` as a scope, as document state selectors aren't
|
||||
// affected by the current scope.
|
||||
self_invalidations.push(Invalidation::new(
|
||||
&dependency.selector,
|
||||
/* scope = */ None,
|
||||
0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -365,6 +365,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn exported_part(&self, name: &Atom) -> Option<Atom> {
|
||||
// FIXME(emilio): Implement for proper invalidation.
|
||||
self.element.exported_part(name)
|
||||
}
|
||||
|
||||
fn imported_part(&self, name: &Atom) -> Option<Atom> {
|
||||
// FIXME(emilio): Implement for proper invalidation.
|
||||
self.element.imported_part(name)
|
||||
}
|
||||
|
||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||
match self.snapshot() {
|
||||
Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
use crate::context::StackLimitChecker;
|
||||
use crate::dom::{TElement, TNode, TShadowRoot};
|
||||
use crate::selector_parser::SelectorImpl;
|
||||
use selectors::OpaqueElement;
|
||||
use selectors::matching::matches_compound_selector_from;
|
||||
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
|
||||
use selectors::parser::{Combinator, Component, Selector};
|
||||
|
@ -127,6 +128,8 @@ enum InvalidationKind {
|
|||
#[derive(Clone)]
|
||||
pub struct Invalidation<'a> {
|
||||
selector: &'a Selector<SelectorImpl>,
|
||||
/// The right shadow host from where the rule came from, if any.
|
||||
scope: Option<OpaqueElement>,
|
||||
/// The offset of the selector pointing to a compound selector.
|
||||
///
|
||||
/// This order is a "parse order" offset, that is, zero is the leftmost part
|
||||
|
@ -143,9 +146,14 @@ pub struct Invalidation<'a> {
|
|||
|
||||
impl<'a> Invalidation<'a> {
|
||||
/// Create a new invalidation for a given selector and offset.
|
||||
pub fn new(selector: &'a Selector<SelectorImpl>, offset: usize) -> Self {
|
||||
pub fn new(
|
||||
selector: &'a Selector<SelectorImpl>,
|
||||
scope: Option<OpaqueElement>,
|
||||
offset: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
selector,
|
||||
scope,
|
||||
offset,
|
||||
matched_by_any_previous: false,
|
||||
}
|
||||
|
@ -483,6 +491,9 @@ where
|
|||
|
||||
let mut any = false;
|
||||
let mut sibling_invalidations = InvalidationVector::new();
|
||||
|
||||
// FIXME(emilio): We also need to invalidate parts in descendant shadow
|
||||
// hosts that have exportparts attributes.
|
||||
for element in shadow.parts() {
|
||||
any |= self.invalidate_child(
|
||||
*element,
|
||||
|
@ -721,12 +732,17 @@ where
|
|||
self.element, invalidation, invalidation_kind
|
||||
);
|
||||
|
||||
let matching_result = matches_compound_selector_from(
|
||||
let matching_result = {
|
||||
let mut context = self.processor.matching_context();
|
||||
context.current_host = invalidation.scope;
|
||||
|
||||
matches_compound_selector_from(
|
||||
&invalidation.selector,
|
||||
invalidation.offset,
|
||||
self.processor.matching_context(),
|
||||
context,
|
||||
&self.element,
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
let mut invalidated_self = false;
|
||||
let mut matched = false;
|
||||
|
@ -809,6 +825,7 @@ where
|
|||
|
||||
let next_invalidation = Invalidation {
|
||||
selector: invalidation.selector,
|
||||
scope: invalidation.scope,
|
||||
offset: next_combinator_offset + 1,
|
||||
matched_by_any_previous: false,
|
||||
};
|
||||
|
|
|
@ -457,6 +457,7 @@ where
|
|||
|
||||
let invalidation = Invalidation::new(
|
||||
&dependency.selector,
|
||||
self.matching_context.current_host.clone(),
|
||||
dependency.selector.len() - dependency.selector_offset + 1,
|
||||
);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
//! Collects a series of applicable rules for a given element.
|
||||
|
||||
use crate::Atom;
|
||||
use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
|
||||
use crate::dom::{TElement, TNode, TShadowRoot};
|
||||
use crate::properties::{AnimationRules, PropertyDeclarationBlock};
|
||||
|
@ -328,14 +329,23 @@ where
|
|||
return;
|
||||
}
|
||||
|
||||
let shadow = match self.rule_hash_target.containing_shadow() {
|
||||
let mut inner_shadow = match self.rule_hash_target.containing_shadow() {
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let host = shadow.host();
|
||||
let containing_shadow = host.containing_shadow();
|
||||
let part_rules = match containing_shadow {
|
||||
let mut shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree();
|
||||
|
||||
let mut parts = SmallVec::<[Atom; 3]>::new();
|
||||
self.rule_hash_target.each_part(|p| parts.push(p.clone()));
|
||||
|
||||
loop {
|
||||
if parts.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let outer_shadow = inner_shadow.host().containing_shadow();
|
||||
let part_rules = match outer_shadow {
|
||||
Some(shadow) => shadow
|
||||
.style_data()
|
||||
.and_then(|data| data.part_rules(self.pseudo_element)),
|
||||
|
@ -346,13 +356,9 @@ where
|
|||
.part_rules(self.pseudo_element),
|
||||
};
|
||||
|
||||
// TODO(emilio): Cascade order will need to increment for each tree when
|
||||
// we implement forwarding.
|
||||
let shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree();
|
||||
if let Some(part_rules) = part_rules {
|
||||
let containing_host = containing_shadow.map(|s| s.host());
|
||||
let containing_host = outer_shadow.map(|s| s.host());
|
||||
let element = self.element;
|
||||
let rule_hash_target = self.rule_hash_target;
|
||||
let rules = &mut self.rules;
|
||||
let flags_setter = &mut self.flags_setter;
|
||||
let cascade_level = CascadeLevel::AuthorNormal {
|
||||
|
@ -360,7 +366,7 @@ where
|
|||
};
|
||||
let start = rules.len();
|
||||
self.context.with_shadow_host(containing_host, |context| {
|
||||
rule_hash_target.each_part(|p| {
|
||||
for p in &parts {
|
||||
if let Some(part_rules) = part_rules.get(p) {
|
||||
SelectorMap::get_matching_rules(
|
||||
element,
|
||||
|
@ -371,9 +377,27 @@ where
|
|||
cascade_level,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
sort_rules_from(rules, start);
|
||||
shadow_cascade_order.inc();
|
||||
}
|
||||
|
||||
let inner_shadow_host = inner_shadow.host();
|
||||
|
||||
inner_shadow = match outer_shadow {
|
||||
Some(s) => s,
|
||||
None => break, // Nowhere to export to.
|
||||
};
|
||||
|
||||
parts.retain(|part| {
|
||||
let exported_part = match inner_shadow_host.exported_part(part) {
|
||||
Some(part) => part,
|
||||
None => return false,
|
||||
};
|
||||
std::mem::replace(part, exported_part);
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[double-forward.html]
|
||||
[Part in inner host is forwarded through the middle host for styling by document style sheet]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[invalidation-change-part-name-forward.html]
|
||||
[Part in selected host changed color]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[precedence-part-vs-part.html]
|
||||
[Style from document overrides style from outer CE]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[simple-forward-shorthand.html]
|
||||
[Part in inner host is forwarded, under the same name, for styling by document style sheet]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[simple-forward.html]
|
||||
[Part in inner host is forwarded for styling by document style sheet]
|
||||
expected: FAIL
|
||||
|
Загрузка…
Ссылка в новой задаче