mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
style: Track @container condition id in style rules
Much like we track layer rules. Consolidate that "containing rule state we pass down while building the cascade data" in a single struct that we can easily restore. For now, do nothing with it. I want to land this patch separately because it touches the Rule struct and CascadeData rebuilds, which both are performance sensitive. Its layout shouldn't change because I also changed LayerId to be a u16 (this shouldn't matter in practice, since LayerOrder is already a u16). Differential Revision: https://phabricator.services.mozilla.com/D145243
This commit is contained in:
parent
b05552369f
commit
82c5be08c8
2 changed files with 135 additions and 54 deletions
|
@ -61,17 +61,6 @@ impl LayerOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The id of a given layer, a sequentially-increasing identifier.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
|
|
||||||
pub struct LayerId(pub u32);
|
|
||||||
|
|
||||||
impl LayerId {
|
|
||||||
/// The id of the root layer.
|
|
||||||
pub const fn root() -> Self {
|
|
||||||
Self(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
|
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
|
||||||
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
|
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
|
||||||
|
|
|
@ -29,7 +29,7 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
|
||||||
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
|
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
|
||||||
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
|
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
|
||||||
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
|
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
|
||||||
use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder};
|
use crate::stylesheets::layer_rule::{LayerName, LayerOrder};
|
||||||
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
|
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use crate::stylesheets::{
|
use crate::stylesheets::{
|
||||||
|
@ -548,6 +548,47 @@ impl From<StyleRuleInclusion> for RuleInclusion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A struct containing state from ancestor rules like @layer / @import /
|
||||||
|
/// @container.
|
||||||
|
struct ContainingRuleState {
|
||||||
|
layer_name: LayerName,
|
||||||
|
layer_id: LayerId,
|
||||||
|
container_condition_id: ContainerConditionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContainingRuleState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
layer_name: LayerName::new_empty(),
|
||||||
|
layer_id: LayerId::root(),
|
||||||
|
container_condition_id: ContainerConditionId::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SavedContainingRuleState {
|
||||||
|
layer_name_len: usize,
|
||||||
|
layer_id: LayerId,
|
||||||
|
container_condition_id: ContainerConditionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainingRuleState {
|
||||||
|
fn save(&self) -> SavedContainingRuleState {
|
||||||
|
SavedContainingRuleState {
|
||||||
|
layer_name_len: self.layer_name.0.len(),
|
||||||
|
layer_id: self.layer_id,
|
||||||
|
container_condition_id: self.container_condition_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, saved: &SavedContainingRuleState) {
|
||||||
|
debug_assert!(self.layer_name.0.len() >= saved.layer_name_len);
|
||||||
|
self.layer_name.0.truncate(saved.layer_name_len);
|
||||||
|
self.layer_id = saved.layer_id;
|
||||||
|
self.container_condition_id = saved.container_condition_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Stylist {
|
impl Stylist {
|
||||||
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
|
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
|
||||||
/// If more members are added here, think about whether they should
|
/// If more members are added here, think about whether they should
|
||||||
|
@ -2029,6 +2070,17 @@ impl PartElementAndPseudoRules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The id of a given layer, a sequentially-increasing identifier.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct LayerId(u16);
|
||||||
|
|
||||||
|
impl LayerId {
|
||||||
|
/// The id of the root layer.
|
||||||
|
pub const fn root() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, MallocSizeOf)]
|
#[derive(Clone, Debug, MallocSizeOf)]
|
||||||
struct CascadeLayer {
|
struct CascadeLayer {
|
||||||
id: LayerId,
|
id: LayerId,
|
||||||
|
@ -2046,6 +2098,33 @@ impl CascadeLayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The id of a given container condition, a sequentially-increasing identifier
|
||||||
|
/// for a given style set.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct ContainerConditionId(u16);
|
||||||
|
|
||||||
|
impl ContainerConditionId {
|
||||||
|
/// A special id that represents no container rule all.
|
||||||
|
pub const fn none() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, MallocSizeOf)]
|
||||||
|
struct ContainerCondition {
|
||||||
|
parent: ContainerConditionId,
|
||||||
|
// TODO: condition: Option<Arc<ContainerCondition>> (or so).
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerCondition {
|
||||||
|
const fn none() -> Self {
|
||||||
|
Self {
|
||||||
|
parent: ContainerConditionId::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Data resulting from performing the CSS cascade that is specific to a given
|
/// Data resulting from performing the CSS cascade that is specific to a given
|
||||||
/// origin.
|
/// origin.
|
||||||
///
|
///
|
||||||
|
@ -2117,6 +2196,9 @@ pub struct CascadeData {
|
||||||
/// The list of cascade layers, indexed by their layer id.
|
/// The list of cascade layers, indexed by their layer id.
|
||||||
layers: SmallVec<[CascadeLayer; 1]>,
|
layers: SmallVec<[CascadeLayer; 1]>,
|
||||||
|
|
||||||
|
/// The list of container conditions, indexed by their id.
|
||||||
|
container_conditions: SmallVec<[ContainerCondition; 1]>,
|
||||||
|
|
||||||
/// Effective media query results cached from the last rebuild.
|
/// Effective media query results cached from the last rebuild.
|
||||||
effective_media_query_results: EffectiveMediaQueryResults,
|
effective_media_query_results: EffectiveMediaQueryResults,
|
||||||
|
|
||||||
|
@ -2159,6 +2241,7 @@ impl CascadeData {
|
||||||
animations: Default::default(),
|
animations: Default::default(),
|
||||||
layer_id: Default::default(),
|
layer_id: Default::default(),
|
||||||
layers: smallvec::smallvec![CascadeLayer::root()],
|
layers: smallvec::smallvec![CascadeLayer::root()],
|
||||||
|
container_conditions: smallvec::smallvec![ContainerCondition::none()],
|
||||||
extra_data: ExtraStyleData::default(),
|
extra_data: ExtraStyleData::default(),
|
||||||
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
||||||
rules_source_order: 0,
|
rules_source_order: 0,
|
||||||
|
@ -2394,8 +2477,7 @@ impl CascadeData {
|
||||||
stylesheet: &S,
|
stylesheet: &S,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
rebuild_kind: SheetRebuildKind,
|
rebuild_kind: SheetRebuildKind,
|
||||||
mut current_layer: &mut LayerName,
|
containing_rule_state: &mut ContainingRuleState,
|
||||||
current_layer_id: LayerId,
|
|
||||||
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
|
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
|
||||||
) -> Result<(), AllocErr>
|
) -> Result<(), AllocErr>
|
||||||
where
|
where
|
||||||
|
@ -2418,7 +2500,7 @@ impl CascadeData {
|
||||||
if pseudo.is_precomputed() {
|
if pseudo.is_precomputed() {
|
||||||
debug_assert!(selector.is_universal());
|
debug_assert!(selector.is_universal());
|
||||||
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
|
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
|
||||||
debug_assert_eq!(current_layer_id, LayerId::root());
|
debug_assert_eq!(containing_rule_state.layer_id, LayerId::root());
|
||||||
|
|
||||||
precomputed_pseudo_element_decls
|
precomputed_pseudo_element_decls
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
@ -2445,7 +2527,8 @@ impl CascadeData {
|
||||||
hashes,
|
hashes,
|
||||||
locked.clone(),
|
locked.clone(),
|
||||||
self.rules_source_order,
|
self.rules_source_order,
|
||||||
current_layer_id,
|
containing_rule_state.layer_id,
|
||||||
|
containing_rule_state.container_condition_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if rebuild_kind.should_rebuild_invalidation() {
|
if rebuild_kind.should_rebuild_invalidation() {
|
||||||
|
@ -2523,7 +2606,7 @@ impl CascadeData {
|
||||||
self.animations.try_insert_with(
|
self.animations.try_insert_with(
|
||||||
name,
|
name,
|
||||||
animation,
|
animation,
|
||||||
current_layer_id,
|
containing_rule_state.layer_id,
|
||||||
compare_keyframes_in_same_layer,
|
compare_keyframes_in_same_layer,
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
|
@ -2532,25 +2615,35 @@ impl CascadeData {
|
||||||
// Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue
|
// Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue
|
||||||
// https://github.com/w3c/csswg-drafts/issues/6674 gets landed.
|
// https://github.com/w3c/csswg-drafts/issues/6674 gets landed.
|
||||||
self.extra_data
|
self.extra_data
|
||||||
.add_scroll_timeline(guard, rule, current_layer_id)?;
|
.add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?;
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::FontFace(ref rule) => {
|
CssRule::FontFace(ref rule) => {
|
||||||
self.extra_data.add_font_face(rule, current_layer_id);
|
// NOTE(emilio): We don't care about container_condition_id
|
||||||
|
// because:
|
||||||
|
//
|
||||||
|
// Global, name-defining at-rules such as @keyframes or
|
||||||
|
// @font-face or @layer that are defined inside container
|
||||||
|
// queries are not constrained by the container query
|
||||||
|
// conditions.
|
||||||
|
//
|
||||||
|
// https://drafts.csswg.org/css-contain-3/#container-rule
|
||||||
|
// (Same elsewhere)
|
||||||
|
self.extra_data.add_font_face(rule, containing_rule_state.layer_id);
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::FontFeatureValues(ref rule) => {
|
CssRule::FontFeatureValues(ref rule) => {
|
||||||
self.extra_data
|
self.extra_data
|
||||||
.add_font_feature_values(rule, current_layer_id);
|
.add_font_feature_values(rule, containing_rule_state.layer_id);
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::CounterStyle(ref rule) => {
|
CssRule::CounterStyle(ref rule) => {
|
||||||
self.extra_data
|
self.extra_data
|
||||||
.add_counter_style(guard, rule, current_layer_id)?;
|
.add_counter_style(guard, rule, containing_rule_state.layer_id)?;
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::Page(ref rule) => {
|
CssRule::Page(ref rule) => {
|
||||||
self.extra_data.add_page(guard, rule, current_layer_id)?;
|
self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?;
|
||||||
},
|
},
|
||||||
CssRule::Viewport(..) => {},
|
CssRule::Viewport(..) => {},
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -2591,7 +2684,7 @@ impl CascadeData {
|
||||||
if let Some(id) = data.layer_id.get(layer) {
|
if let Some(id) = data.layer_id.get(layer) {
|
||||||
return *id;
|
return *id;
|
||||||
}
|
}
|
||||||
let id = LayerId(data.layers.len() as u32);
|
let id = LayerId(data.layers.len() as u16);
|
||||||
|
|
||||||
let parent_layer_id = if layer.layer_names().len() > 1 {
|
let parent_layer_id = if layer.layer_names().len() > 1 {
|
||||||
let mut parent = layer.clone();
|
let mut parent = layer.clone();
|
||||||
|
@ -2622,9 +2715,8 @@ impl CascadeData {
|
||||||
fn maybe_register_layers(
|
fn maybe_register_layers(
|
||||||
data: &mut CascadeData,
|
data: &mut CascadeData,
|
||||||
name: Option<&LayerName>,
|
name: Option<&LayerName>,
|
||||||
current_layer: &mut LayerName,
|
containing_rule_state: &mut ContainingRuleState,
|
||||||
pushed_layers: &mut usize,
|
) {
|
||||||
) -> LayerId {
|
|
||||||
let anon_name;
|
let anon_name;
|
||||||
let name = match name {
|
let name = match name {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
|
@ -2633,19 +2725,14 @@ impl CascadeData {
|
||||||
&anon_name
|
&anon_name
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut id = LayerId::root();
|
|
||||||
for name in name.layer_names() {
|
for name in name.layer_names() {
|
||||||
current_layer.0.push(name.clone());
|
containing_rule_state.layer_name.0.push(name.clone());
|
||||||
id = maybe_register_layer(data, ¤t_layer);
|
containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name);
|
||||||
*pushed_layers += 1;
|
|
||||||
}
|
}
|
||||||
debug_assert_ne!(id, LayerId::root());
|
debug_assert_ne!(containing_rule_state.layer_id, LayerId::root());
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut layer_names_to_pop = 0;
|
let saved_containing_rule_state = containing_rule_state.save();
|
||||||
let mut children_layer_id = current_layer_id;
|
|
||||||
match *rule {
|
match *rule {
|
||||||
CssRule::Import(ref lock) => {
|
CssRule::Import(ref lock) => {
|
||||||
let import_rule = lock.read_with(guard);
|
let import_rule = lock.read_with(guard);
|
||||||
|
@ -2654,11 +2741,10 @@ impl CascadeData {
|
||||||
.saw_effective(import_rule);
|
.saw_effective(import_rule);
|
||||||
}
|
}
|
||||||
if let Some(ref layer) = import_rule.layer {
|
if let Some(ref layer) = import_rule.layer {
|
||||||
children_layer_id = maybe_register_layers(
|
maybe_register_layers(
|
||||||
self,
|
self,
|
||||||
layer.name.as_ref(),
|
layer.name.as_ref(),
|
||||||
&mut current_layer,
|
containing_rule_state
|
||||||
&mut layer_names_to_pop,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2670,25 +2756,28 @@ impl CascadeData {
|
||||||
},
|
},
|
||||||
CssRule::LayerBlock(ref lock) => {
|
CssRule::LayerBlock(ref lock) => {
|
||||||
let layer_rule = lock.read_with(guard);
|
let layer_rule = lock.read_with(guard);
|
||||||
children_layer_id = maybe_register_layers(
|
maybe_register_layers(
|
||||||
self,
|
self,
|
||||||
layer_rule.name.as_ref(),
|
layer_rule.name.as_ref(),
|
||||||
&mut current_layer,
|
containing_rule_state,
|
||||||
&mut layer_names_to_pop,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
CssRule::LayerStatement(ref lock) => {
|
CssRule::LayerStatement(ref lock) => {
|
||||||
let layer_rule = lock.read_with(guard);
|
let layer_rule = lock.read_with(guard);
|
||||||
for name in &*layer_rule.names {
|
for name in &*layer_rule.names {
|
||||||
let mut pushed = 0;
|
|
||||||
// There are no children, so we can ignore the
|
// There are no children, so we can ignore the
|
||||||
// return value.
|
// return value.
|
||||||
maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed);
|
maybe_register_layers(self, Some(name), containing_rule_state);
|
||||||
for _ in 0..pushed {
|
|
||||||
current_layer.0.pop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CssRule::Container(ref lock) => {
|
||||||
|
let _container_rule = lock.read_with(guard);
|
||||||
|
let id = ContainerConditionId(self.container_conditions.len() as u16);
|
||||||
|
self.container_conditions.push(ContainerCondition {
|
||||||
|
parent: containing_rule_state.container_condition_id,
|
||||||
|
});
|
||||||
|
containing_rule_state.container_condition_id = id;
|
||||||
|
},
|
||||||
// We don't care about any other rule.
|
// We don't care about any other rule.
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
@ -2701,15 +2790,12 @@ impl CascadeData {
|
||||||
stylesheet,
|
stylesheet,
|
||||||
guard,
|
guard,
|
||||||
rebuild_kind,
|
rebuild_kind,
|
||||||
current_layer,
|
containing_rule_state,
|
||||||
children_layer_id,
|
|
||||||
precomputed_pseudo_element_decls.as_deref_mut(),
|
precomputed_pseudo_element_decls.as_deref_mut(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..layer_names_to_pop {
|
containing_rule_state.restore(&saved_containing_rule_state);
|
||||||
current_layer.0.pop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -2738,7 +2824,7 @@ impl CascadeData {
|
||||||
self.effective_media_query_results.saw_effective(contents);
|
self.effective_media_query_results.saw_effective(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current_layer = LayerName::new_empty();
|
let mut state = ContainingRuleState::default();
|
||||||
self.add_rule_list(
|
self.add_rule_list(
|
||||||
contents.rules(guard).iter(),
|
contents.rules(guard).iter(),
|
||||||
device,
|
device,
|
||||||
|
@ -2746,8 +2832,7 @@ impl CascadeData {
|
||||||
stylesheet,
|
stylesheet,
|
||||||
guard,
|
guard,
|
||||||
rebuild_kind,
|
rebuild_kind,
|
||||||
&mut current_layer,
|
&mut state,
|
||||||
LayerId::root(),
|
|
||||||
precomputed_pseudo_element_decls.as_deref_mut(),
|
precomputed_pseudo_element_decls.as_deref_mut(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -2873,6 +2958,8 @@ impl CascadeData {
|
||||||
self.layer_id.clear();
|
self.layer_id.clear();
|
||||||
self.layers.clear();
|
self.layers.clear();
|
||||||
self.layers.push(CascadeLayer::root());
|
self.layers.push(CascadeLayer::root());
|
||||||
|
self.container_conditions.clear();
|
||||||
|
self.container_conditions.push(ContainerCondition::none());
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
{
|
{
|
||||||
self.extra_data.clear();
|
self.extra_data.clear();
|
||||||
|
@ -2967,6 +3054,9 @@ pub struct Rule {
|
||||||
/// The current layer id of this style rule.
|
/// The current layer id of this style rule.
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
|
|
||||||
|
/// The current @container rule id.
|
||||||
|
pub container_condition_id: ContainerConditionId,
|
||||||
|
|
||||||
/// The actual style rule.
|
/// The actual style rule.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "gecko",
|
feature = "gecko",
|
||||||
|
@ -3012,6 +3102,7 @@ impl Rule {
|
||||||
style_rule: Arc<Locked<StyleRule>>,
|
style_rule: Arc<Locked<StyleRule>>,
|
||||||
source_order: u32,
|
source_order: u32,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
|
container_condition_id: ContainerConditionId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Rule {
|
Rule {
|
||||||
selector,
|
selector,
|
||||||
|
@ -3019,6 +3110,7 @@ impl Rule {
|
||||||
style_rule,
|
style_rule,
|
||||||
source_order,
|
source_order,
|
||||||
layer_id,
|
layer_id,
|
||||||
|
container_condition_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue