script/dom/
timeranges.rs

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
5use std::fmt;
6
7use dom_struct::dom_struct;
8
9use crate::dom::bindings::codegen::Bindings::TimeRangesBinding::TimeRangesMethods;
10use crate::dom::bindings::error::{Error, Fallible};
11use crate::dom::bindings::num::Finite;
12use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
13use crate::dom::bindings::root::DomRoot;
14use crate::dom::window::Window;
15use crate::script_runtime::CanGc;
16
17#[derive(Clone, JSTraceable, MallocSizeOf)]
18struct TimeRange {
19    start: f64,
20    end: f64,
21}
22
23impl TimeRange {
24    pub(crate) fn union(&mut self, other: &TimeRange) {
25        self.start = f64::min(self.start, other.start);
26        self.end = f64::max(self.end, other.end);
27    }
28
29    fn contains(&self, time: f64) -> bool {
30        self.start <= time && time < self.end
31    }
32
33    fn is_overlapping(&self, other: &TimeRange) -> bool {
34        // This also covers the case where `self` is entirely contained within `other`,
35        // for example: `self` = [2,3) and `other` = [1,4).
36        self.contains(other.start) || self.contains(other.end) || other.contains(self.start)
37    }
38
39    fn is_contiguous(&self, other: &TimeRange) -> bool {
40        other.start == self.end || other.end == self.start
41    }
42
43    pub(crate) fn is_before(&self, other: &TimeRange) -> bool {
44        other.start >= self.end
45    }
46}
47
48impl fmt::Debug for TimeRange {
49    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
50        write!(fmt, "[{},{})", self.start, self.end)
51    }
52}
53
54#[derive(Debug)]
55pub enum TimeRangesError {
56    EndOlderThanStart,
57    OutOfRange,
58}
59
60#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)]
61pub struct TimeRangesContainer {
62    ranges: Vec<TimeRange>,
63}
64
65impl TimeRangesContainer {
66    #[allow(clippy::len_without_is_empty)]
67    pub fn len(&self) -> u32 {
68        self.ranges.len() as u32
69    }
70
71    pub(crate) fn is_empty(&self) -> bool {
72        self.ranges.is_empty()
73    }
74
75    pub fn start(&self, index: u32) -> Result<f64, TimeRangesError> {
76        self.ranges
77            .get(index as usize)
78            .map(|r| r.start)
79            .ok_or(TimeRangesError::OutOfRange)
80    }
81
82    pub fn end(&self, index: u32) -> Result<f64, TimeRangesError> {
83        self.ranges
84            .get(index as usize)
85            .map(|r| r.end)
86            .ok_or(TimeRangesError::OutOfRange)
87    }
88
89    pub fn add(&mut self, start: f64, end: f64) -> Result<(), TimeRangesError> {
90        if start > end {
91            return Err(TimeRangesError::EndOlderThanStart);
92        }
93
94        let mut new_range = TimeRange { start, end };
95
96        // For each present range check if we need to:
97        // - merge with the added range, in case we are overlapping or contiguous,
98        // - insert in place, we are completely, not overlapping and not contiguous
99        //   in between two ranges.
100        let mut idx = 0;
101        while idx < self.ranges.len() {
102            if new_range.is_overlapping(&self.ranges[idx]) ||
103                new_range.is_contiguous(&self.ranges[idx])
104            {
105                // The ranges are either overlapping or contiguous,
106                // we need to merge the new range with the existing one.
107                new_range.union(&self.ranges[idx]);
108                self.ranges.remove(idx);
109            } else if new_range.is_before(&self.ranges[idx]) &&
110                (idx == 0 || self.ranges[idx - 1].is_before(&new_range))
111            {
112                // We are exactly after the current previous range and before the current
113                // range, while not overlapping with none of them.
114                // Or we are simply at the beginning.
115                self.ranges.insert(idx, new_range);
116                return Ok(());
117            } else {
118                idx += 1;
119            }
120        }
121
122        // Insert at the end.
123        self.ranges.insert(idx, new_range);
124
125        Ok(())
126    }
127}
128
129#[dom_struct]
130pub(crate) struct TimeRanges {
131    reflector_: Reflector,
132    ranges: TimeRangesContainer,
133}
134
135impl TimeRanges {
136    fn new_inherited(ranges: TimeRangesContainer) -> TimeRanges {
137        Self {
138            reflector_: Reflector::new(),
139            ranges,
140        }
141    }
142
143    pub(crate) fn new(
144        window: &Window,
145        ranges: TimeRangesContainer,
146        can_gc: CanGc,
147    ) -> DomRoot<TimeRanges> {
148        reflect_dom_object(Box::new(TimeRanges::new_inherited(ranges)), window, can_gc)
149    }
150}
151
152impl TimeRangesMethods<crate::DomTypeHolder> for TimeRanges {
153    // https://html.spec.whatwg.org/multipage/#dom-timeranges-length
154    fn Length(&self) -> u32 {
155        self.ranges.len()
156    }
157
158    // https://html.spec.whatwg.org/multipage/#dom-timeranges-start
159    fn Start(&self, index: u32) -> Fallible<Finite<f64>> {
160        self.ranges
161            .start(index)
162            .map(Finite::wrap)
163            .map_err(|_| Error::IndexSize)
164    }
165
166    // https://html.spec.whatwg.org/multipage/#dom-timeranges-end
167    fn End(&self, index: u32) -> Fallible<Finite<f64>> {
168        self.ranges
169            .end(index)
170            .map(Finite::wrap)
171            .map_err(|_| Error::IndexSize)
172    }
173}