1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* This Source Code Form is subject to the terms of the Mozilla Public
 * 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::{HashMap, HashSet};

use base::id::{BrowsingContextGroupId, BrowsingContextId, PipelineId, TopLevelBrowsingContextId};
use euclid::Size2D;
use log::warn;
use style_traits::CSSPixel;

use crate::pipeline::Pipeline;

/// Because a browsing context is only constructed once the document that's
/// going to be in it becomes active (i.e. not when a pipeline is spawned), some
/// values needed in browsing context are not easily available at the point of
/// constructing it. Thus, every time a pipeline is created for a browsing
/// context which doesn't exist yet, these values needed for the new browsing
/// context are stored here so that they may be available later.
#[derive(Debug)]
pub struct NewBrowsingContextInfo {
    /// The parent pipeline that contains this browsing context. `None` if this
    /// is a top level browsing context.
    pub parent_pipeline_id: Option<PipelineId>,

    /// Whether this browsing context is in private browsing mode.
    pub is_private: bool,

    /// Whether this browsing context inherits a secure context.
    pub inherited_secure_context: Option<bool>,

    /// Whether this browsing context should be throttled, using less resources
    /// by stopping animations and running timers at a heavily limited rate.
    pub throttled: bool,
}

/// The constellation's view of a browsing context.
/// Each browsing context has a session history, caused by navigation and
/// traversing the history. Each browsing context has its current entry, plus
/// past and future entries. The past is sorted chronologically, the future is
/// sorted reverse chronologically: in particular prev.pop() is the latest
/// past entry, and next.pop() is the earliest future entry.
pub struct BrowsingContext {
    /// The browsing context group id where the top-level of this bc is found.
    pub bc_group_id: BrowsingContextGroupId,

    /// The browsing context id.
    pub id: BrowsingContextId,

    /// The top-level browsing context ancestor
    pub top_level_id: TopLevelBrowsingContextId,

    /// The size of the frame.
    pub size: Size2D<f32, CSSPixel>,

    /// Whether this browsing context is in private browsing mode.
    pub is_private: bool,

    /// Whether this browsing context inherits a secure context.
    pub inherited_secure_context: Option<bool>,

    /// Whether this browsing context should be throttled, using less resources
    /// by stopping animations and running timers at a heavily limited rate.
    pub throttled: bool,

    /// The pipeline for the current session history entry.
    pub pipeline_id: PipelineId,

    /// The parent pipeline that contains this browsing context. `None` if this
    /// is a top level browsing context.
    pub parent_pipeline_id: Option<PipelineId>,

    /// All the pipelines that have been presented or will be presented in
    /// this browsing context.
    pub pipelines: HashSet<PipelineId>,
}

impl BrowsingContext {
    /// Create a new browsing context.
    /// Note this just creates the browsing context, it doesn't add it to the constellation's set of browsing contexts.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        bc_group_id: BrowsingContextGroupId,
        id: BrowsingContextId,
        top_level_id: TopLevelBrowsingContextId,
        pipeline_id: PipelineId,
        parent_pipeline_id: Option<PipelineId>,
        size: Size2D<f32, CSSPixel>,
        is_private: bool,
        inherited_secure_context: Option<bool>,
        throttled: bool,
    ) -> BrowsingContext {
        let mut pipelines = HashSet::new();
        pipelines.insert(pipeline_id);
        BrowsingContext {
            bc_group_id,
            id,
            top_level_id,
            size,
            is_private,
            inherited_secure_context,
            throttled,
            pipeline_id,
            parent_pipeline_id,
            pipelines,
        }
    }

    pub fn update_current_entry(&mut self, pipeline_id: PipelineId) {
        self.pipeline_id = pipeline_id;
    }

    /// Is this a top-level browsing context?
    pub fn is_top_level(&self) -> bool {
        self.id == self.top_level_id
    }
}

/// An iterator over browsing contexts, returning the descendant
/// contexts whose active documents are fully active, in depth-first
/// order.
pub struct FullyActiveBrowsingContextsIterator<'a> {
    /// The browsing contexts still to iterate over.
    pub stack: Vec<BrowsingContextId>,

    /// The set of all browsing contexts.
    pub browsing_contexts: &'a HashMap<BrowsingContextId, BrowsingContext>,

    /// The set of all pipelines.  We use this to find the active
    /// children of a frame, which are the iframes in the currently
    /// active document.
    pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}

impl<'a> Iterator for FullyActiveBrowsingContextsIterator<'a> {
    type Item = &'a BrowsingContext;
    fn next(&mut self) -> Option<&'a BrowsingContext> {
        loop {
            let browsing_context_id = self.stack.pop()?;
            let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
                Some(browsing_context) => browsing_context,
                None => {
                    warn!(
                        "BrowsingContext {:?} iterated after closure.",
                        browsing_context_id
                    );
                    continue;
                },
            };
            let pipeline = match self.pipelines.get(&browsing_context.pipeline_id) {
                Some(pipeline) => pipeline,
                None => {
                    warn!(
                        "Pipeline {:?} iterated after closure.",
                        browsing_context.pipeline_id
                    );
                    continue;
                },
            };
            self.stack.extend(pipeline.children.iter());
            return Some(browsing_context);
        }
    }
}

/// An iterator over browsing contexts, returning all descendant
/// contexts in depth-first order. Note that this iterator returns all
/// contexts, not just the fully active ones.
pub struct AllBrowsingContextsIterator<'a> {
    /// The browsing contexts still to iterate over.
    pub stack: Vec<BrowsingContextId>,

    /// The set of all browsing contexts.
    pub browsing_contexts: &'a HashMap<BrowsingContextId, BrowsingContext>,

    /// The set of all pipelines.  We use this to find the
    /// children of a browsing context, which are the iframes in all documents
    /// in the session history.
    pub pipelines: &'a HashMap<PipelineId, Pipeline>,
}

impl<'a> Iterator for AllBrowsingContextsIterator<'a> {
    type Item = &'a BrowsingContext;
    fn next(&mut self) -> Option<&'a BrowsingContext> {
        let pipelines = self.pipelines;
        loop {
            let browsing_context_id = self.stack.pop()?;
            let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
                Some(browsing_context) => browsing_context,
                None => {
                    warn!(
                        "BrowsingContext {:?} iterated after closure.",
                        browsing_context_id
                    );
                    continue;
                },
            };
            let child_browsing_context_ids = browsing_context
                .pipelines
                .iter()
                .filter_map(|pipeline_id| pipelines.get(pipeline_id))
                .flat_map(|pipeline| pipeline.children.iter());
            self.stack.extend(child_browsing_context_ids);
            return Some(browsing_context);
        }
    }
}