animation.rs (21079B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for properties related to animations and transitions. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId}; 10 use crate::values::generics::animation as generics; 11 use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time}; 12 use crate::values::{CustomIdent, DashedIdent, KeyframesName}; 13 use crate::Atom; 14 use cssparser::{match_ignore_ascii_case, Parser}; 15 use std::fmt::{self, Write}; 16 use style_traits::{ 17 CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, 18 }; 19 20 /// A given transition property, that is either `All`, a longhand or shorthand 21 /// property, or an unsupported or custom property. 22 #[derive( 23 Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 24 )] 25 #[repr(u8)] 26 pub enum TransitionProperty { 27 /// A non-custom property. 28 NonCustom(NonCustomPropertyId), 29 /// A custom property. 30 Custom(Atom), 31 /// Unrecognized property which could be any non-transitionable, custom property, or 32 /// unknown property. 33 Unsupported(CustomIdent), 34 } 35 36 impl ToCss for TransitionProperty { 37 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 38 where 39 W: Write, 40 { 41 match *self { 42 TransitionProperty::NonCustom(ref id) => id.to_css(dest), 43 TransitionProperty::Custom(ref name) => { 44 dest.write_str("--")?; 45 crate::values::serialize_atom_name(name, dest) 46 }, 47 TransitionProperty::Unsupported(ref i) => i.to_css(dest), 48 } 49 } 50 } 51 52 impl Parse for TransitionProperty { 53 fn parse<'i, 't>( 54 context: &ParserContext, 55 input: &mut Parser<'i, 't>, 56 ) -> Result<Self, ParseError<'i>> { 57 let location = input.current_source_location(); 58 let ident = input.expect_ident()?; 59 60 let id = match PropertyId::parse_ignoring_rule_type(&ident, context) { 61 Ok(id) => id, 62 Err(..) => { 63 // None is not acceptable as a single transition-property. 64 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident( 65 location, 66 ident, 67 &["none"], 68 )?)); 69 }, 70 }; 71 72 Ok(match id { 73 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()), 74 PropertyId::Custom(name) => TransitionProperty::Custom(name), 75 }) 76 } 77 } 78 79 impl SpecifiedValueInfo for TransitionProperty { 80 fn collect_completion_keywords(f: KeywordsCollectFn) { 81 // `transition-property` can actually accept all properties and 82 // arbitrary identifiers, but `all` is a special one we'd like 83 // to list. 84 f(&["all"]); 85 } 86 } 87 88 impl TransitionProperty { 89 /// Returns the `none` value. 90 #[inline] 91 pub fn none() -> Self { 92 TransitionProperty::Unsupported(CustomIdent(atom!("none"))) 93 } 94 95 /// Returns whether we're the `none` value. 96 #[inline] 97 pub fn is_none(&self) -> bool { 98 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none")) 99 } 100 101 /// Returns `all`. 102 #[inline] 103 pub fn all() -> Self { 104 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All)) 105 } 106 107 /// Returns true if it is `all`. 108 #[inline] 109 pub fn is_all(&self) -> bool { 110 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand( 111 ShorthandId::All, 112 )) 113 } 114 } 115 116 /// A specified value for <transition-behavior-value>. 117 /// 118 /// https://drafts.csswg.org/css-transitions-2/#transition-behavior-property 119 #[derive( 120 Clone, 121 Copy, 122 Debug, 123 MallocSizeOf, 124 Parse, 125 PartialEq, 126 SpecifiedValueInfo, 127 ToComputedValue, 128 ToCss, 129 ToResolvedValue, 130 ToShmem, 131 )] 132 #[repr(u8)] 133 pub enum TransitionBehavior { 134 /// Transitions will not be started for discrete properties, only for interpolable properties. 135 Normal, 136 /// Transitions will be started for discrete properties as well as interpolable properties. 137 AllowDiscrete, 138 } 139 140 impl TransitionBehavior { 141 /// Return normal, the initial value. 142 #[inline] 143 pub fn normal() -> Self { 144 Self::Normal 145 } 146 147 /// Return true if it is normal. 148 #[inline] 149 pub fn is_normal(&self) -> bool { 150 matches!(*self, Self::Normal) 151 } 152 } 153 154 /// A specified value for the `animation-duration` property. 155 pub type AnimationDuration = generics::GenericAnimationDuration<Time>; 156 157 impl Parse for AnimationDuration { 158 fn parse<'i, 't>( 159 context: &ParserContext, 160 input: &mut Parser<'i, 't>, 161 ) -> Result<Self, ParseError<'i>> { 162 if static_prefs::pref!("layout.css.scroll-driven-animations.enabled") 163 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() 164 { 165 return Ok(Self::auto()); 166 } 167 168 Time::parse_non_negative(context, input).map(AnimationDuration::Time) 169 } 170 } 171 172 /// https://drafts.csswg.org/css-animations/#animation-iteration-count 173 #[derive( 174 Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped, 175 )] 176 pub enum AnimationIterationCount { 177 /// A `<number>` value. 178 Number(NonNegativeNumber), 179 /// The `infinite` keyword. 180 Infinite, 181 } 182 183 impl AnimationIterationCount { 184 /// Returns the value `1.0`. 185 #[inline] 186 pub fn one() -> Self { 187 Self::Number(NonNegativeNumber::new(1.0)) 188 } 189 190 /// Returns true if it's `1.0`. 191 #[inline] 192 pub fn is_one(&self) -> bool { 193 *self == Self::one() 194 } 195 } 196 197 /// A value for the `animation-name` property. 198 #[derive( 199 Clone, 200 Debug, 201 Eq, 202 Hash, 203 MallocSizeOf, 204 PartialEq, 205 SpecifiedValueInfo, 206 ToComputedValue, 207 ToCss, 208 ToResolvedValue, 209 ToShmem, 210 ToTyped, 211 )] 212 #[value_info(other_values = "none")] 213 #[repr(C)] 214 pub struct AnimationName(pub KeyframesName); 215 216 impl AnimationName { 217 /// Get the name of the animation as an `Atom`. 218 pub fn as_atom(&self) -> Option<&Atom> { 219 if self.is_none() { 220 return None; 221 } 222 Some(self.0.as_atom()) 223 } 224 225 /// Returns the `none` value. 226 pub fn none() -> Self { 227 AnimationName(KeyframesName::none()) 228 } 229 230 /// Returns whether this is the none value. 231 pub fn is_none(&self) -> bool { 232 self.0.is_none() 233 } 234 } 235 236 impl Parse for AnimationName { 237 fn parse<'i, 't>( 238 context: &ParserContext, 239 input: &mut Parser<'i, 't>, 240 ) -> Result<Self, ParseError<'i>> { 241 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) { 242 return Ok(AnimationName(name)); 243 } 244 245 input.expect_ident_matching("none")?; 246 Ok(AnimationName(KeyframesName::none())) 247 } 248 } 249 250 /// https://drafts.csswg.org/css-animations/#propdef-animation-direction 251 #[derive( 252 Copy, 253 Clone, 254 Debug, 255 MallocSizeOf, 256 Parse, 257 PartialEq, 258 SpecifiedValueInfo, 259 ToComputedValue, 260 ToCss, 261 ToResolvedValue, 262 ToShmem, 263 ToTyped, 264 )] 265 #[repr(u8)] 266 #[allow(missing_docs)] 267 pub enum AnimationDirection { 268 Normal, 269 Reverse, 270 Alternate, 271 AlternateReverse, 272 } 273 274 impl AnimationDirection { 275 /// Returns true if the name matches any animation-direction keyword. 276 #[inline] 277 pub fn match_keywords(name: &AnimationName) -> bool { 278 if let Some(name) = name.as_atom() { 279 #[cfg(feature = "gecko")] 280 return name.with_str(|n| Self::from_ident(n).is_ok()); 281 #[cfg(feature = "servo")] 282 return Self::from_ident(name).is_ok(); 283 } 284 false 285 } 286 } 287 288 /// https://drafts.csswg.org/css-animations/#animation-play-state 289 #[derive( 290 Copy, 291 Clone, 292 Debug, 293 MallocSizeOf, 294 Parse, 295 PartialEq, 296 SpecifiedValueInfo, 297 ToComputedValue, 298 ToCss, 299 ToResolvedValue, 300 ToShmem, 301 ToTyped, 302 )] 303 #[repr(u8)] 304 #[allow(missing_docs)] 305 pub enum AnimationPlayState { 306 Running, 307 Paused, 308 } 309 310 impl AnimationPlayState { 311 /// Returns true if the name matches any animation-play-state keyword. 312 #[inline] 313 pub fn match_keywords(name: &AnimationName) -> bool { 314 if let Some(name) = name.as_atom() { 315 #[cfg(feature = "gecko")] 316 return name.with_str(|n| Self::from_ident(n).is_ok()); 317 #[cfg(feature = "servo")] 318 return Self::from_ident(name).is_ok(); 319 } 320 false 321 } 322 } 323 324 /// https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode 325 #[derive( 326 Copy, 327 Clone, 328 Debug, 329 MallocSizeOf, 330 Parse, 331 PartialEq, 332 SpecifiedValueInfo, 333 ToComputedValue, 334 ToCss, 335 ToResolvedValue, 336 ToShmem, 337 ToTyped, 338 )] 339 #[repr(u8)] 340 #[allow(missing_docs)] 341 pub enum AnimationFillMode { 342 None, 343 Forwards, 344 Backwards, 345 Both, 346 } 347 348 impl AnimationFillMode { 349 /// Returns true if the name matches any animation-fill-mode keyword. 350 /// Note: animation-name:none is its initial value, so we don't have to match none here. 351 #[inline] 352 pub fn match_keywords(name: &AnimationName) -> bool { 353 if let Some(name) = name.as_atom() { 354 #[cfg(feature = "gecko")] 355 return name.with_str(|n| Self::from_ident(n).is_ok()); 356 #[cfg(feature = "servo")] 357 return Self::from_ident(name).is_ok(); 358 } 359 false 360 } 361 } 362 363 /// https://drafts.csswg.org/css-animations-2/#animation-composition 364 #[derive( 365 Copy, 366 Clone, 367 Debug, 368 MallocSizeOf, 369 Parse, 370 PartialEq, 371 SpecifiedValueInfo, 372 ToComputedValue, 373 ToCss, 374 ToResolvedValue, 375 ToShmem, 376 ToTyped, 377 )] 378 #[repr(u8)] 379 #[allow(missing_docs)] 380 pub enum AnimationComposition { 381 Replace, 382 Add, 383 Accumulate, 384 } 385 386 /// A value for the <Scroller> used in scroll(). 387 /// 388 /// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller 389 #[derive( 390 Copy, 391 Clone, 392 Debug, 393 Eq, 394 Hash, 395 MallocSizeOf, 396 Parse, 397 PartialEq, 398 SpecifiedValueInfo, 399 ToComputedValue, 400 ToCss, 401 ToResolvedValue, 402 ToShmem, 403 )] 404 #[repr(u8)] 405 pub enum Scroller { 406 /// The nearest ancestor scroll container. (Default.) 407 Nearest, 408 /// The document viewport as the scroll container. 409 Root, 410 /// Specifies to use the element’s own principal box as the scroll container. 411 #[css(keyword = "self")] 412 SelfElement, 413 } 414 415 impl Scroller { 416 /// Returns true if it is default. 417 #[inline] 418 fn is_default(&self) -> bool { 419 matches!(*self, Self::Nearest) 420 } 421 } 422 423 impl Default for Scroller { 424 fn default() -> Self { 425 Self::Nearest 426 } 427 } 428 429 /// A value for the <Axis> used in scroll(), or a value for {scroll|view}-timeline-axis. 430 /// 431 /// https://drafts.csswg.org/scroll-animations-1/#typedef-axis 432 /// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis 433 /// https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis 434 #[derive( 435 Copy, 436 Clone, 437 Debug, 438 Eq, 439 Hash, 440 MallocSizeOf, 441 Parse, 442 PartialEq, 443 SpecifiedValueInfo, 444 ToComputedValue, 445 ToCss, 446 ToResolvedValue, 447 ToShmem, 448 )] 449 #[repr(u8)] 450 pub enum ScrollAxis { 451 /// The block axis of the scroll container. (Default.) 452 Block = 0, 453 /// The inline axis of the scroll container. 454 Inline = 1, 455 /// The horizontal axis of the scroll container. 456 X = 2, 457 /// The vertical axis of the scroll container. 458 Y = 3, 459 } 460 461 impl ScrollAxis { 462 /// Returns true if it is default. 463 #[inline] 464 pub fn is_default(&self) -> bool { 465 matches!(*self, Self::Block) 466 } 467 } 468 469 impl Default for ScrollAxis { 470 fn default() -> Self { 471 Self::Block 472 } 473 } 474 475 /// The scroll() notation. 476 /// https://drafts.csswg.org/scroll-animations-1/#scroll-notation 477 #[derive( 478 Copy, 479 Clone, 480 Debug, 481 MallocSizeOf, 482 PartialEq, 483 SpecifiedValueInfo, 484 ToComputedValue, 485 ToCss, 486 ToResolvedValue, 487 ToShmem, 488 )] 489 #[css(function = "scroll")] 490 #[repr(C)] 491 pub struct ScrollFunction { 492 /// The scroll container element whose scroll position drives the progress of the timeline. 493 #[css(skip_if = "Scroller::is_default")] 494 pub scroller: Scroller, 495 /// The axis of scrolling that drives the progress of the timeline. 496 #[css(skip_if = "ScrollAxis::is_default")] 497 pub axis: ScrollAxis, 498 } 499 500 impl ScrollFunction { 501 /// Parse the inner function arguments of `scroll()`. 502 fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { 503 // <scroll()> = scroll( [ <scroller> || <axis> ]? ) 504 // https://drafts.csswg.org/scroll-animations-1/#funcdef-scroll 505 let mut scroller = None; 506 let mut axis = None; 507 loop { 508 if scroller.is_none() { 509 scroller = input.try_parse(Scroller::parse).ok(); 510 } 511 512 if axis.is_none() { 513 axis = input.try_parse(ScrollAxis::parse).ok(); 514 if axis.is_some() { 515 continue; 516 } 517 } 518 break; 519 } 520 521 Ok(Self { 522 scroller: scroller.unwrap_or_default(), 523 axis: axis.unwrap_or_default(), 524 }) 525 } 526 } 527 528 impl generics::ViewFunction<LengthPercentage> { 529 /// Parse the inner function arguments of `view()`. 530 fn parse_arguments<'i, 't>( 531 context: &ParserContext, 532 input: &mut Parser<'i, 't>, 533 ) -> Result<Self, ParseError<'i>> { 534 // <view()> = view( [ <axis> || <'view-timeline-inset'> ]? ) 535 // https://drafts.csswg.org/scroll-animations-1/#funcdef-view 536 let mut axis = None; 537 let mut inset = None; 538 loop { 539 if axis.is_none() { 540 axis = input.try_parse(ScrollAxis::parse).ok(); 541 } 542 543 if inset.is_none() { 544 inset = input 545 .try_parse(|i| ViewTimelineInset::parse(context, i)) 546 .ok(); 547 if inset.is_some() { 548 continue; 549 } 550 } 551 break; 552 } 553 554 Ok(Self { 555 inset: inset.unwrap_or_default(), 556 axis: axis.unwrap_or_default(), 557 }) 558 } 559 } 560 561 /// The typedef of scroll-timeline-name or view-timeline-name. 562 /// 563 /// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name 564 /// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name 565 #[derive( 566 Clone, 567 Debug, 568 Eq, 569 Hash, 570 MallocSizeOf, 571 PartialEq, 572 SpecifiedValueInfo, 573 ToComputedValue, 574 ToResolvedValue, 575 ToShmem, 576 )] 577 #[repr(C)] 578 pub struct TimelineName(DashedIdent); 579 580 impl TimelineName { 581 /// Returns the `none` value. 582 pub fn none() -> Self { 583 Self(DashedIdent::empty()) 584 } 585 586 /// Check if this is `none` value. 587 pub fn is_none(&self) -> bool { 588 self.0.is_empty() 589 } 590 } 591 592 impl Parse for TimelineName { 593 fn parse<'i, 't>( 594 context: &ParserContext, 595 input: &mut Parser<'i, 't>, 596 ) -> Result<Self, ParseError<'i>> { 597 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 598 return Ok(Self::none()); 599 } 600 601 DashedIdent::parse(context, input).map(TimelineName) 602 } 603 } 604 605 impl ToCss for TimelineName { 606 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 607 where 608 W: Write, 609 { 610 if self.is_none() { 611 return dest.write_str("none"); 612 } 613 614 self.0.to_css(dest) 615 } 616 } 617 618 /// A specified value for the `animation-timeline` property. 619 pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>; 620 621 impl Parse for AnimationTimeline { 622 fn parse<'i, 't>( 623 context: &ParserContext, 624 input: &mut Parser<'i, 't>, 625 ) -> Result<Self, ParseError<'i>> { 626 use crate::values::generics::animation::ViewFunction; 627 628 // <single-animation-timeline> = auto | none | <dashed-ident> | <scroll()> | <view()> 629 // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline 630 631 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { 632 return Ok(Self::Auto); 633 } 634 635 // This parses none or <dashed-indent>. 636 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) { 637 return Ok(AnimationTimeline::Timeline(name)); 638 } 639 640 // Parse <scroll()> or <view()>. 641 let location = input.current_source_location(); 642 let function = input.expect_function()?.clone(); 643 input.parse_nested_block(move |i| { 644 match_ignore_ascii_case! { &function, 645 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll), 646 "view" => ViewFunction::parse_arguments(context, i).map(Self::View), 647 _ => { 648 Err(location.new_custom_error( 649 StyleParseErrorKind::UnexpectedFunction(function.clone()) 650 )) 651 }, 652 } 653 }) 654 } 655 } 656 657 /// A specified value for the `view-timeline-inset` property. 658 pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>; 659 660 impl Parse for ViewTimelineInset { 661 fn parse<'i, 't>( 662 context: &ParserContext, 663 input: &mut Parser<'i, 't>, 664 ) -> Result<Self, ParseError<'i>> { 665 use crate::values::specified::LengthPercentageOrAuto; 666 667 let start = LengthPercentageOrAuto::parse(context, input)?; 668 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) { 669 Ok(end) => end, 670 Err(_) => start.clone(), 671 }; 672 673 Ok(Self { start, end }) 674 } 675 } 676 677 /// The view-transition-name: `none | <custom-ident> | match-element`. 678 /// 679 /// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop 680 /// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name 681 // TODO: auto keyword. 682 #[derive( 683 Clone, 684 Debug, 685 Eq, 686 Hash, 687 PartialEq, 688 MallocSizeOf, 689 SpecifiedValueInfo, 690 ToComputedValue, 691 ToResolvedValue, 692 ToShmem, 693 ToTyped, 694 )] 695 #[repr(C, u8)] 696 pub enum ViewTransitionName { 697 /// None keyword. 698 None, 699 /// match-element keyword. 700 /// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name 701 MatchElement, 702 /// A `<custom-ident>`. 703 Ident(Atom), 704 } 705 706 impl ViewTransitionName { 707 /// Returns the `none` value. 708 pub fn none() -> Self { 709 Self::None 710 } 711 } 712 713 impl Parse for ViewTransitionName { 714 fn parse<'i, 't>( 715 _: &ParserContext, 716 input: &mut Parser<'i, 't>, 717 ) -> Result<Self, ParseError<'i>> { 718 let location = input.current_source_location(); 719 let ident = input.expect_ident()?; 720 if ident.eq_ignore_ascii_case("none") { 721 return Ok(Self::none()); 722 } 723 724 if ident.eq_ignore_ascii_case("match-element") { 725 return Ok(Self::MatchElement); 726 } 727 728 // We check none already, so don't need to exclude none here. 729 // Note: "auto" is not supported yet so we exclude it. 730 CustomIdent::from_ident(location, ident, &["auto"]).map(|i| Self::Ident(i.0)) 731 } 732 } 733 734 impl ToCss for ViewTransitionName { 735 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 736 where 737 W: Write, 738 { 739 use crate::values::serialize_atom_identifier; 740 match *self { 741 Self::None => dest.write_str("none"), 742 Self::MatchElement => dest.write_str("match-element"), 743 Self::Ident(ref ident) => serialize_atom_identifier(ident, dest), 744 } 745 } 746 } 747 748 /// The view-transition-class: `none | <custom-ident>+`. 749 /// 750 /// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop 751 /// 752 /// Empty slice represents `none`. 753 #[derive( 754 Clone, 755 Debug, 756 Eq, 757 Hash, 758 PartialEq, 759 MallocSizeOf, 760 SpecifiedValueInfo, 761 ToComputedValue, 762 ToCss, 763 ToResolvedValue, 764 ToShmem, 765 ToTyped, 766 )] 767 #[repr(C)] 768 #[value_info(other_values = "none")] 769 pub struct ViewTransitionClass( 770 #[css(iterable, if_empty = "none")] 771 #[ignore_malloc_size_of = "Arc"] 772 crate::ArcSlice<CustomIdent>, 773 ); 774 775 impl ViewTransitionClass { 776 /// Returns the default value, `none`. We use the default slice (i.e. empty) to represent it. 777 pub fn none() -> Self { 778 Self(Default::default()) 779 } 780 781 /// Returns whether this is the `none` value. 782 pub fn is_none(&self) -> bool { 783 self.0.is_empty() 784 } 785 } 786 787 impl Parse for ViewTransitionClass { 788 fn parse<'i, 't>( 789 _: &ParserContext, 790 input: &mut Parser<'i, 't>, 791 ) -> Result<Self, ParseError<'i>> { 792 use style_traits::{Separator, Space}; 793 794 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 795 return Ok(Self::none()); 796 } 797 798 Ok(Self(crate::ArcSlice::from_iter( 799 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(), 800 ))) 801 } 802 }