Skip to main content

script/dom/html/embedded_content/
htmliframeelement.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::cell::Cell;
6use std::rc::Rc;
7
8use content_security_policy::sandboxing_directive::{
9    SandboxingFlagSet, parse_a_sandboxing_directive,
10};
11use dom_struct::dom_struct;
12use embedder_traits::ViewportDetails;
13use html5ever::{LocalName, Prefix, local_name, ns};
14use js::context::JSContext;
15use js::rust::HandleObject;
16use net_traits::ReferrerPolicy;
17use net_traits::request::Destination;
18use profile_traits::generic_channel::channel;
19use script_bindings::cell::DomRefCell;
20use script_traits::{NewPipelineInfo, UpdatePipelineIdReason};
21use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
22use servo_constellation_traits::{
23    IFrameLoadInfo, IFrameLoadInfoWithData, LoadData, LoadOrigin, NavigationHistoryBehavior,
24    ScriptToConstellationMessage, TargetSnapshotParams,
25};
26use servo_url::ServoUrl;
27use style::attr::{AttrValue, LengthOrPercentageOrAuto};
28use stylo_atoms::Atom;
29
30use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
31use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
32use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
33use crate::dom::bindings::error::Fallible;
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::refcounted::Trusted;
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
38use crate::dom::bindings::str::{DOMString, USVString};
39use crate::dom::document::Document;
40use crate::dom::domtokenlist::DOMTokenList;
41use crate::dom::element::attributes::storage::AttrRef;
42use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
43use crate::dom::eventtarget::EventTarget;
44use crate::dom::globalscope::GlobalScope;
45use crate::dom::html::htmlelement::HTMLElement;
46use crate::dom::node::virtualmethods::VirtualMethods;
47use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
48use crate::dom::performance::performanceresourcetiming::InitiatorType;
49use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
50use crate::dom::windowproxy::WindowProxy;
51use crate::event_loop::document_loader::{LoadBlocker, LoadType};
52use crate::event_loop::script_thread::{ScriptThread, with_script_thread};
53use crate::event_loop::script_window_proxies::ScriptWindowProxies;
54use crate::fetch::network_listener::ResourceTimingListener;
55use crate::navigation::{
56    determine_creation_sandboxing_flags, determine_iframe_element_referrer_policy,
57};
58
59#[derive(PartialEq)]
60enum PipelineType {
61    InitialAboutBlank,
62    Navigation,
63}
64
65#[derive(Clone, Copy, PartialEq)]
66pub(crate) enum ProcessingMode {
67    FirstTime,
68    NotFirstTime,
69}
70
71/// <https://html.spec.whatwg.org/multipage/#lazy-load-resumption-steps>
72#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
73enum LazyLoadResumptionSteps {
74    #[default]
75    None,
76    SrcDoc,
77}
78
79#[dom_struct]
80pub(crate) struct HTMLIFrameElement {
81    htmlelement: HTMLElement,
82    #[no_trace]
83    webview_id: Cell<Option<WebViewId>>,
84    #[no_trace]
85    browsing_context_id: Cell<Option<BrowsingContextId>>,
86    #[no_trace]
87    pipeline_id: Cell<Option<PipelineId>>,
88    #[no_trace]
89    pending_pipeline_id: Cell<Option<PipelineId>>,
90    #[no_trace]
91    about_blank_pipeline_id: Cell<Option<PipelineId>>,
92    sandbox: MutNullableDom<DOMTokenList>,
93    #[no_trace]
94    sandboxing_flag_set: Cell<Option<SandboxingFlagSet>>,
95    load_blocker: DomRefCell<Option<LoadBlocker>>,
96    #[conditional_malloc_size_of]
97    script_window_proxies: Rc<ScriptWindowProxies>,
98    /// <https://html.spec.whatwg.org/multipage/#current-navigation-was-lazy-loaded>
99    current_navigation_was_lazy_loaded: Cell<bool>,
100    /// <https://html.spec.whatwg.org/multipage/#lazy-load-resumption-steps>
101    #[no_trace]
102    lazy_load_resumption_steps: Cell<LazyLoadResumptionSteps>,
103    /// Keeping track of whether the iframe will be navigated
104    /// outside of the processing of it's attribute(for example: form navigation).
105    /// This is necessary to prevent the iframe load event steps
106    /// from asynchronously running for the initial blank document
107    /// while script at this point(when the flag is set)
108    /// expects those to run only for the navigated documented.
109    pending_navigation: Cell<bool>,
110    /// Initial name set by the content of the `name` attribute on the
111    /// iframe. This is frozen in time, since it is only processed once
112    /// on the initial creation of the iframe contents. If the iframe
113    /// itself changes the `window.name`, that takes precedence.
114    frozen_name: DomRefCell<Option<String>>,
115}
116
117impl HTMLIFrameElement {
118    /// <https://html.spec.whatwg.org/multipage/#shared-attribute-processing-steps-for-iframe-and-frame-elements>,
119    fn shared_attribute_processing_steps_for_iframe_and_frame_elements(
120        &self,
121        _mode: ProcessingMode,
122    ) -> Option<ServoUrl> {
123        let element = self.upcast::<Element>();
124        // Step 2. If element has a src attribute specified, and its value is not the empty string, then:
125        let url = element
126            .get_attribute_string_value(&local_name!("src"))
127            .and_then(|url| {
128                if url.is_empty() {
129                    None
130                } else {
131                    // Step 2.1. Let maybeURL be the result of encoding-parsing a URL given that attribute's value,
132                    // relative to element's node document.
133                    // Step 2.2. If maybeURL is not failure, then set url to maybeURL.
134                    self.owner_document().encoding_parse_a_url(&url).ok()
135                }
136            })
137            // Step 1. Let url be the URL record about:blank.
138            .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
139        // Step 3. If the inclusive ancestor navigables of element's node navigable contains
140        // a navigable whose active document's URL equals url with exclude fragments set to true, then return null.
141        // TODO
142
143        // Step 4. If url matches about:blank and initialInsertion is true, then perform the URL and history update steps
144        // given element's content navigable's active document and url.
145        // TODO
146
147        // Step 5. Return url.
148        Some(url)
149    }
150
151    pub(crate) fn navigate_or_reload_child_browsing_context(
152        &self,
153        cx: &mut JSContext,
154        load_data: LoadData,
155        history_handling: NavigationHistoryBehavior,
156        mode: ProcessingMode,
157        target_snapshot_params: TargetSnapshotParams,
158    ) {
159        self.start_new_pipeline(
160            cx,
161            load_data,
162            PipelineType::Navigation,
163            history_handling,
164            mode,
165            target_snapshot_params,
166        );
167    }
168
169    fn start_new_pipeline(
170        &self,
171        cx: &mut JSContext,
172        mut load_data: LoadData,
173        pipeline_type: PipelineType,
174        history_handling: NavigationHistoryBehavior,
175        mode: ProcessingMode,
176        target_snapshot_params: TargetSnapshotParams,
177    ) {
178        let document = self.owner_document();
179
180        {
181            let load_blocker = &self.load_blocker;
182            // Any oustanding load is finished from the point of view of the blocked
183            // document; the new navigation will continue blocking it.
184            LoadBlocker::terminate(load_blocker, cx);
185
186            *load_blocker.borrow_mut() = Some(LoadBlocker::new(
187                &document,
188                LoadType::Subframe(load_data.url.clone()),
189            ));
190        }
191
192        if load_data.url.scheme() != "javascript" {
193            self.continue_navigation(
194                cx,
195                load_data,
196                pipeline_type,
197                history_handling,
198                target_snapshot_params,
199            );
200            return;
201        }
202
203        // TODO(jdm): The spec uses the navigate algorithm here, but
204        //   our iframe navigation is not yet unified enough to follow that.
205        //   Eventually we should remove the task and invoke ScriptThread::navigate instead.
206        let iframe = Trusted::new(self);
207        let doc = Trusted::new(&*document);
208        document
209            .global()
210            .task_manager()
211            .networking_task_source()
212            .queue(task!(navigate_to_javascript: move |cx| {
213                let this = iframe.root();
214                let window_proxy = this.GetContentWindow();
215                if let Some(window_proxy) = window_proxy {
216                    // If this method returns false we are not creating a new
217                    // document and the frame can be considered loaded.
218                    if !ScriptThread::navigate_to_javascript_url(
219                        cx,
220                        &this.owner_global(),
221                        &window_proxy.global(),
222                        &mut load_data,
223                        Some(this.upcast()),
224                        Some(mode == ProcessingMode::FirstTime),
225                    ) {
226                        LoadBlocker::terminate(&this.load_blocker, cx);
227                        return;
228                    }
229                    load_data.about_base_url = doc.root().about_base_url();
230                }
231                this.continue_navigation(cx, load_data, pipeline_type, history_handling, target_snapshot_params);
232            }));
233    }
234
235    fn continue_navigation(
236        &self,
237        cx: &mut JSContext,
238        load_data: LoadData,
239        pipeline_type: PipelineType,
240        history_handling: NavigationHistoryBehavior,
241        target_snapshot_params: TargetSnapshotParams,
242    ) {
243        let browsing_context_id = match self.browsing_context_id() {
244            None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
245            Some(id) => id,
246        };
247
248        let webview_id = match self.webview_id() {
249            None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
250            Some(id) => id,
251        };
252
253        let window = self.owner_window();
254        let old_pipeline_id = self.pipeline_id();
255        let new_pipeline_id = PipelineId::new();
256        self.pending_pipeline_id.set(Some(new_pipeline_id));
257
258        let load_info = IFrameLoadInfo {
259            parent_pipeline_id: window.pipeline_id(),
260            browsing_context_id,
261            webview_id,
262            new_pipeline_id,
263            is_private: false, // FIXME
264            inherited_secure_context: load_data.inherited_secure_context,
265            history_handling,
266            target_snapshot_params,
267            name: self.frozen_name.borrow().clone(),
268        };
269
270        let viewport_details = window
271            .get_iframe_viewport_details_if_known(browsing_context_id)
272            .unwrap_or_else(|| ViewportDetails {
273                hidpi_scale_factor: window.device_pixel_ratio(),
274                ..Default::default()
275            });
276
277        match pipeline_type {
278            PipelineType::InitialAboutBlank => {
279                self.about_blank_pipeline_id.set(Some(new_pipeline_id));
280
281                let load_info = IFrameLoadInfoWithData {
282                    info: load_info,
283                    load_data: load_data.clone(),
284                    old_pipeline_id,
285                    viewport_details,
286                };
287                window
288                    .as_global_scope()
289                    .script_to_constellation_chan()
290                    .send(ScriptToConstellationMessage::ScriptNewIFrame(load_info))
291                    .unwrap();
292
293                let new_pipeline_info = NewPipelineInfo {
294                    webview_state: (*window.webview_state()).clone(),
295                    parent_info: Some(window.pipeline_id()),
296                    new_pipeline_id,
297                    browsing_context_id,
298                    opener: None,
299                    load_data,
300                    viewport_details,
301                    user_content_manager_id: None,
302                    target_snapshot_params,
303                    frame_name: self.frozen_name.borrow().clone(),
304                };
305
306                self.pipeline_id.set(Some(new_pipeline_id));
307                with_script_thread(|script_thread| {
308                    script_thread.spawn_pipeline(cx, new_pipeline_info);
309                });
310            },
311            PipelineType::Navigation => {
312                let load_info = IFrameLoadInfoWithData {
313                    info: load_info,
314                    load_data,
315                    old_pipeline_id,
316                    viewport_details,
317                };
318                window
319                    .as_global_scope()
320                    .script_to_constellation_chan()
321                    .send(ScriptToConstellationMessage::ScriptLoadedURLInIFrame(
322                        load_info,
323                    ))
324                    .unwrap();
325            },
326        }
327    }
328
329    /// When an iframe is first inserted into the document,
330    /// an "about:blank" document is created,
331    /// and synchronously processed by the script thread.
332    /// This initial synchronous load should have no noticeable effect in script.
333    /// See the note in `iframe_load_event_steps`.
334    pub(crate) fn is_initial_blank_document(&self) -> bool {
335        self.pending_pipeline_id.get() == self.about_blank_pipeline_id.get()
336    }
337
338    /// <https://html.spec.whatwg.org/multipage/#navigate-an-iframe-or-frame>
339    fn navigate_an_iframe_or_frame(
340        &self,
341        cx: &mut JSContext,
342        load_data: LoadData,
343        mode: ProcessingMode,
344    ) {
345        // Step 2. If element's content navigable's active document is not completely loaded,
346        // then set historyHandling to "replace".
347        let history_handling = if !self
348            .GetContentDocument()
349            .is_some_and(|doc| doc.completely_loaded())
350        {
351            NavigationHistoryBehavior::Replace
352        } else {
353            // Step 1. Let historyHandling be "auto".
354            NavigationHistoryBehavior::Auto
355        };
356        // Step 3. If element is an iframe, then set element's pending resource-timing start time
357        // to the current high resolution time given element's node document's relevant global object.
358        // TODO
359
360        // Step 4. Navigate element's content navigable to url using element's node document,
361        // with historyHandling set to historyHandling, referrerPolicy set to referrerPolicy,
362        // documentResource set to srcdocString, and initialInsertion set to initialInsertion.
363        let target_snapshot_params = snapshot_self(self);
364        self.navigate_or_reload_child_browsing_context(
365            cx,
366            load_data,
367            history_handling,
368            mode,
369            target_snapshot_params,
370        );
371    }
372
373    /// <https://html.spec.whatwg.org/multipage/#will-lazy-load-element-steps>
374    fn will_lazy_load_element_steps(&self) -> bool {
375        // Step 1. If scripting is disabled for element, then return false.
376        if !self.owner_document().scripting_enabled() {
377            return false;
378        }
379        // Step 2. If element's lazy loading attribute is in the Lazy state, then return true.
380        // Step 3. Return false.
381        self.Loading() == "lazy"
382    }
383
384    /// Step 1.3. of <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
385    fn navigate_to_the_srcdoc_resource(&self, cx: &mut JSContext, mode: ProcessingMode) {
386        // Step 1.3. Navigate to the srcdoc resource: Navigate an iframe or frame given element,
387        // about:srcdoc, the empty string, and the value of element's srcdoc attribute.
388        let url = ServoUrl::parse("about:srcdoc").unwrap();
389        let document = self.owner_document();
390        let window = self.owner_window();
391        let pipeline_id = Some(window.pipeline_id());
392        let mut load_data = LoadData::new(
393            LoadOrigin::Script(document.origin().snapshot()),
394            url,
395            Some(document.base_url()),
396            pipeline_id,
397            window.as_global_scope().get_referrer(),
398            document.get_referrer_policy(),
399            Some(window.as_global_scope().is_secure_context()),
400            Some(document.insecure_requests_policy()),
401            document.has_trustworthy_ancestor_or_current_origin(),
402            self.sandboxing_flag_set(),
403        );
404        load_data.destination = Destination::IFrame;
405        load_data.policy_container = Some(window.as_global_scope().policy_container());
406        load_data.srcdoc = String::from(
407            self.upcast::<Element>()
408                .get_string_attribute(&local_name!("srcdoc")),
409        );
410
411        self.navigate_an_iframe_or_frame(cx, load_data, mode);
412    }
413
414    /// <https://html.spec.whatwg.org/multipage/#the-iframe-element:potentially-delays-the-load-event>
415    fn mark_navigation_as_lazy_loaded(&self, cx: &mut JSContext) {
416        // > An iframe element whose current navigation was lazy loaded boolean is false potentially delays the load event.
417        self.current_navigation_was_lazy_loaded.set(true);
418        let blocker = &self.load_blocker;
419        LoadBlocker::terminate(blocker, cx);
420    }
421
422    /// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
423    fn process_the_iframe_attributes(&self, cx: &mut JSContext, mode: ProcessingMode) {
424        let element = self.upcast::<Element>();
425
426        // Step 1. If `element`'s `srcdoc` attribute is specified, then:
427        //
428        // Note that this also includes the empty string
429        if element.has_attribute(&local_name!("srcdoc")) {
430            // Step 1.1. Set element's current navigation was lazy loaded boolean to false.
431            self.current_navigation_was_lazy_loaded.set(false);
432            // Step 1.2. If the will lazy load element steps given element return true, then:
433            if self.will_lazy_load_element_steps() {
434                // Step 1.2.1. Set element's lazy load resumption steps to the rest of this algorithm
435                // starting with the step labeled navigate to the srcdoc resource.
436                self.lazy_load_resumption_steps
437                    .set(LazyLoadResumptionSteps::SrcDoc);
438                // Step 1.2.2. Set element's current navigation was lazy loaded boolean to true.
439                self.mark_navigation_as_lazy_loaded(cx);
440                // Step 1.2.3. Start intersection-observing a lazy loading element for element.
441                // TODO
442                // Step 1.2.4. Return.
443                return;
444            }
445            // Step 1.3. Navigate to the srcdoc resource: Navigate an iframe or frame given element,
446            // about:srcdoc, the empty string, and the value of element's srcdoc attribute.
447            self.navigate_to_the_srcdoc_resource(cx, mode);
448            return;
449        }
450
451        let window = self.owner_window();
452
453        // Step 2.1. Let url be the result of running the shared attribute processing steps
454        // for iframe and frame elements given element and initialInsertion.
455        let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements(mode)
456        else {
457            // Step 2.2. If url is null, then return.
458            return;
459        };
460
461        // Step 2.3. If url matches about:blank and initialInsertion is true, then:
462        if url.matches_about_blank() && mode == ProcessingMode::FirstTime {
463            // Step 2.3.1. Run the iframe load event steps given element.
464            self.run_iframe_load_event_steps(cx);
465            // Step 2.3.2. Return.
466            return;
467        }
468
469        // Step 2.4: Let referrerPolicy be the current state of element's referrerpolicy content
470        // attribute.
471        let document = self.owner_document();
472        let referrer_policy_token = self.ReferrerPolicy();
473
474        // Note: despite not being explicitly stated in the spec steps, this falls back to
475        // document's referrer policy here because it satisfies the expectations that when unset,
476        // the iframe should inherit the referrer policy of its parent
477        let referrer_policy = match ReferrerPolicy::from(&*referrer_policy_token.str()) {
478            ReferrerPolicy::EmptyString => document.get_referrer_policy(),
479            policy => policy,
480        };
481
482        // TODO(#25748):
483        // By spec, we return early if there's an ancestor browsing context
484        // "whose active document's url, ignoring fragments, is equal".
485        // However, asking about ancestor browsing contexts is more nuanced than
486        // it sounds and not implemented here.
487        // Within a single origin, we can do it by walking window proxies,
488        // and this check covers only that single-origin case, protecting
489        // against simple typo self-includes but nothing more elaborate.
490        let mut ancestor = window.GetParent();
491        while let Some(a) = ancestor {
492            if let Some(ancestor_url) = a.document().map(|d| d.url()) &&
493                ancestor_url.scheme() == url.scheme() &&
494                ancestor_url.username() == url.username() &&
495                ancestor_url.password() == url.password() &&
496                ancestor_url.host() == url.host() &&
497                ancestor_url.port() == url.port() &&
498                ancestor_url.path() == url.path() &&
499                ancestor_url.query() == url.query()
500            {
501                return;
502            }
503            ancestor = a.parent().map(DomRoot::from_ref);
504        }
505
506        let (creator_pipeline_id, about_base_url) = if url.matches_about_blank() {
507            (Some(window.pipeline_id()), Some(document.base_url()))
508        } else {
509            (None, document.about_base_url())
510        };
511
512        let propagate_encoding_to_child_document = url.origin().same_origin(&window.origin());
513        let mut load_data = LoadData::new(
514            LoadOrigin::Script(document.origin().snapshot()),
515            url,
516            about_base_url,
517            creator_pipeline_id,
518            window.as_global_scope().get_referrer(),
519            referrer_policy,
520            Some(window.as_global_scope().is_secure_context()),
521            Some(document.insecure_requests_policy()),
522            document.has_trustworthy_ancestor_or_current_origin(),
523            self.sandboxing_flag_set(),
524        );
525        load_data.destination = Destination::IFrame;
526        load_data.policy_container = Some(window.as_global_scope().policy_container());
527        if propagate_encoding_to_child_document {
528            load_data.container_document_encoding = Some(document.encoding());
529        }
530
531        let pipeline_id = self.pipeline_id();
532        // If the initial `about:blank` page is the current page, load with replacement enabled,
533        // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3
534        let is_about_blank =
535            pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
536
537        let history_handling = if is_about_blank {
538            NavigationHistoryBehavior::Replace
539        } else {
540            NavigationHistoryBehavior::Push
541        };
542
543        let target_snapshot_params = snapshot_self(self);
544        self.navigate_or_reload_child_browsing_context(
545            cx,
546            load_data,
547            history_handling,
548            mode,
549            target_snapshot_params,
550        );
551    }
552
553    /// <https://html.spec.whatwg.org/multipage/#create-a-new-child-navigable>
554    /// Synchronously create a new browsing context; this is not a navigation.
555    fn create_nested_browsing_context(&self, cx: &mut JSContext) {
556        // Step 1. Let parentNavigable be element's node navigable.
557        let document = self.owner_document();
558        let window = self.owner_window();
559        let pipeline_id = Some(window.pipeline_id());
560        // Step 4. Let targetName be null.
561        // Step 5. If element has a name content attribute,
562        // then set targetName to the value of that attribute.
563        *self.frozen_name.borrow_mut() = self
564            .upcast::<Element>()
565            .get_name()
566            .map(|name| name.to_string());
567        // Step 6. Let documentState be a new document state, with
568        let mut load_data = LoadData::new(
569            // > initiator origin
570            // >     document's origin
571            LoadOrigin::Script(document.origin().snapshot()),
572            ServoUrl::parse("about:blank").unwrap(),
573            // > about base URL
574            // >     document's about base URL
575            Some(document.base_url()),
576            pipeline_id,
577            window.as_global_scope().get_referrer(),
578            document.get_referrer_policy(),
579            Some(window.as_global_scope().is_secure_context()),
580            Some(document.insecure_requests_policy()),
581            document.has_trustworthy_ancestor_or_current_origin(),
582            self.sandboxing_flag_set(),
583        );
584        load_data.is_initial_about_blank = true;
585        load_data.destination = Destination::IFrame;
586        load_data.policy_container = Some(window.as_global_scope().policy_container());
587
588        // Step 7. Let navigable be a new navigable.
589        let browsing_context_id = BrowsingContextId::new();
590        let webview_id = window.window_proxy().webview_id();
591        self.pipeline_id.set(None);
592        self.pending_pipeline_id.set(None);
593        self.webview_id.set(Some(webview_id));
594        self.browsing_context_id.set(Some(browsing_context_id));
595        // Step 8. Initialize the navigable navigable given documentState and parentNavigable.
596        self.start_new_pipeline(
597            cx,
598            load_data,
599            PipelineType::InitialAboutBlank,
600            NavigationHistoryBehavior::Push,
601            ProcessingMode::FirstTime,
602            snapshot_self(self),
603        );
604        // > navigable target name
605        // >     targetName
606        if let Some(window) = self.GetContentWindow() &&
607            let Some(window_name) = &*self.frozen_name.borrow()
608        {
609            window.set_name(window_name.as_str().into());
610        }
611    }
612
613    fn destroy_nested_browsing_context(&self) {
614        self.pipeline_id.set(None);
615        self.pending_pipeline_id.set(None);
616        self.about_blank_pipeline_id.set(None);
617        self.webview_id.set(None);
618        if let Some(browsing_context_id) = self.browsing_context_id.take() {
619            self.script_window_proxies.remove(browsing_context_id)
620        }
621    }
622
623    /// Returns true if the contained pipeline was updated, false otherwise.
624    /// This can occur if the iframe's nested browsing context has changed
625    /// since the asynchronous update was started.
626    pub(crate) fn update_pipeline_id(
627        &self,
628        cx: &mut JSContext,
629        new_pipeline_id: PipelineId,
630        reason: UpdatePipelineIdReason,
631    ) -> bool {
632        // For all updates except the one for the initial blank document,
633        // we need to set the flag back to false because the navigation is complete,
634        // because the goal is to, when a navigation is pending, to skip the async load
635        // steps of the initial blank document.
636        if !self.is_initial_blank_document() {
637            self.pending_navigation.set(false);
638        }
639        if self.pending_pipeline_id.get() != Some(new_pipeline_id) &&
640            reason == UpdatePipelineIdReason::Navigation
641        {
642            return false;
643        }
644
645        self.pipeline_id.set(Some(new_pipeline_id));
646
647        // Only terminate the load blocker if the pipeline id was updated due to a traversal.
648        // The load blocker will be terminated for a navigation in iframe_load_event_steps.
649        if reason == UpdatePipelineIdReason::Traversal {
650            let blocker = &self.load_blocker;
651            LoadBlocker::terminate(blocker, cx);
652        }
653
654        self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
655        true
656    }
657
658    fn new_inherited(
659        local_name: LocalName,
660        prefix: Option<Prefix>,
661        document: &Document,
662    ) -> HTMLIFrameElement {
663        HTMLIFrameElement {
664            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
665            browsing_context_id: Cell::new(None),
666            webview_id: Cell::new(None),
667            pipeline_id: Cell::new(None),
668            pending_pipeline_id: Cell::new(None),
669            about_blank_pipeline_id: Cell::new(None),
670            sandbox: Default::default(),
671            sandboxing_flag_set: Cell::new(None),
672            load_blocker: DomRefCell::new(None),
673            script_window_proxies: ScriptThread::window_proxies(),
674            current_navigation_was_lazy_loaded: Default::default(),
675            lazy_load_resumption_steps: Default::default(),
676            pending_navigation: Default::default(),
677            frozen_name: Default::default(),
678        }
679    }
680
681    pub(crate) fn new(
682        cx: &mut JSContext,
683        local_name: LocalName,
684        prefix: Option<Prefix>,
685        document: &Document,
686        proto: Option<HandleObject>,
687    ) -> DomRoot<HTMLIFrameElement> {
688        Node::reflect_node_with_proto(
689            cx,
690            Box::new(HTMLIFrameElement::new_inherited(
691                local_name, prefix, document,
692            )),
693            document,
694            proto,
695        )
696    }
697
698    #[inline]
699    pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
700        self.pipeline_id.get()
701    }
702
703    #[inline]
704    pub(crate) fn browsing_context_id(&self) -> Option<BrowsingContextId> {
705        self.browsing_context_id.get()
706    }
707
708    #[inline]
709    pub(crate) fn webview_id(&self) -> Option<WebViewId> {
710        self.webview_id.get()
711    }
712
713    #[inline]
714    pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
715        self.sandboxing_flag_set
716            .get()
717            .unwrap_or_else(SandboxingFlagSet::empty)
718    }
719
720    /// Note a pending navigation.
721    /// This is used to ignore the async load event steps for
722    /// the initial blank document if those haven't run yet.
723    pub(crate) fn note_pending_navigation(&self) {
724        self.pending_navigation.set(true);
725    }
726
727    /// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps>
728    pub(crate) fn iframe_load_event_steps(&self, cx: &mut JSContext, loaded_pipeline: PipelineId) {
729        // TODO(#9592): assert that the load blocker is present at all times when we
730        //              can guarantee that it's created for the case of iframe.reload().
731        if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
732            return;
733        }
734
735        // TODO 1. Assert: element's content navigable is not null.
736
737        // TODO 2-4 Mark resource timing.
738
739        // TODO 5 Set childDocument's iframe load in progress flag.
740
741        // Note: in the spec, these steps are either run synchronously as part of
742        // "If url matches about:blank and initialInsertion is true, then:"
743        // in `process the iframe attributes`,
744        // or asynchronously when navigation completes.
745        //
746        // In our current implementation,
747        // we arrive here always asynchronously in the following two cases:
748        // 1. as part of loading the initial blank document
749        //    created in `create_nested_browsing_context`
750        // 2. optionally, as part of loading a second document created as
751        //    as part of the first processing of the iframe attributes.
752        //
753        // To preserve the logic of the spec--firing the load event once--in the context of
754        // our current implementation, we must not fire the load event
755        // for the initial blank document if we know that a navigation is ongoing,
756        // which can be deducted from `pending_navigation` or the presence of an src.
757        //
758        // Additionally, to prevent a race condition with navigations,
759        // in all cases, skip the load event if there is a pending navigation.
760        // See #40348
761        //
762        // TODO: run these step synchronously as part of processing the iframe attributes.
763        let should_fire_event = if self.is_initial_blank_document() {
764            // If this is the initial blank doc:
765            // do not fire if there is a pending navigation,
766            // or if the iframe has an src.
767            !self.pending_navigation.get() &&
768                !self.upcast::<Element>().has_attribute(&local_name!("src"))
769        } else {
770            // If this is not the initial blank doc:
771            // do not fire if there is a pending navigation.
772            !self.pending_navigation.get()
773        };
774
775        if should_fire_event {
776            self.run_iframe_load_event_steps(cx);
777        } else {
778            debug!(
779                "suppressing load event for iframe, loaded {:?}",
780                loaded_pipeline
781            );
782        }
783    }
784
785    /// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps>
786    pub(crate) fn run_iframe_load_event_steps(&self, cx: &mut JSContext) {
787        // TODO 1. Assert: element's content navigable is not null.
788
789        // Step 2. Let childDocument be element's content navigable's active document.
790        let child_document = self.GetContentDocument();
791
792        // Step 3. If childDocument has its mute iframe load flag set, then return.
793        // Step 5. Set childDocument's iframe load in progress flag.
794        if let Some(document) = child_document {
795            if document.mute_iframe_load_flag() {
796                let blocker = &self.load_blocker;
797                LoadBlocker::terminate(blocker, cx);
798                return;
799            }
800            document.set_iframe_load_in_progress(true);
801        }
802
803        // Step 4. If element's pending resource-timing start time is not null, then:
804        // TODO
805
806        // Step 6. Fire an event named load at element.
807        self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
808
809        let blocker = &self.load_blocker;
810        LoadBlocker::terminate(blocker, cx);
811
812        // Step 7. Unset childDocument's iframe load in progress flag
813        if let Some(child_document) = self.GetContentDocument() {
814            child_document.set_iframe_load_in_progress(false);
815        }
816    }
817
818    /// Parse the `sandbox` attribute value given the [`Attr`]. This sets the `sandboxing_flag_set`
819    /// property or clears it is the value isn't specified. Notably, an unspecified sandboxing
820    /// attribute (no sandboxing) is different from an empty one (full sandboxing).
821    fn parse_sandbox_attribute(&self) {
822        let sandbox_value =
823            self.upcast::<Element>()
824                .with_attribute(&ns!(), &local_name!("sandbox"), |attribute| {
825                    let tokens: Vec<_> = attribute
826                        .value()
827                        .as_tokens()
828                        .iter()
829                        .map(|atom| atom.to_ascii_lowercase().to_string())
830                        .collect();
831                    parse_a_sandboxing_directive(&tokens)
832                });
833        self.sandboxing_flag_set.set(sandbox_value);
834    }
835
836    /// Step 4.2. of <https://html.spec.whatwg.org/multipage/#destroy-a-document-and-its-descendants>
837    pub(crate) fn destroy_document_and_its_descendants(&self, cx: &mut JSContext) {
838        let Some(pipeline_id) = self.pipeline_id.get() else {
839            return;
840        };
841        // Step 4.2. Destroy a document and its descendants given childNavigable's active document and incrementDestroyed.
842        if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
843            exited_document.destroy_document_and_its_descendants(cx);
844        }
845        self.destroy_nested_browsing_context();
846    }
847
848    /// <https://html.spec.whatwg.org/multipage/#destroy-a-child-navigable>
849    fn destroy_child_navigable(&self, cx: &mut JSContext) {
850        let blocker = &self.load_blocker;
851        LoadBlocker::terminate(blocker, cx);
852
853        // Step 1. Let navigable be container's content navigable.
854        let Some(browsing_context_id) = self.browsing_context_id() else {
855            // Step 2. If navigable is null, then return.
856            return;
857        };
858        // Store now so that we can destroy the context and delete the
859        // document later
860        let pipeline_id = self.pipeline_id.get();
861
862        // Step 3. Set container's content navigable to null.
863        //
864        // Resetting the pipeline_id to None is required here so that
865        // if this iframe is subsequently re-added to the document
866        // the load doesn't think that it's a navigation, but instead
867        // a new iframe. Without this, the constellation gets very
868        // confused.
869        self.destroy_nested_browsing_context();
870
871        // Step 4. Inform the navigation API about child navigable destruction given navigable.
872        // TODO
873
874        // Step 5. Destroy a document and its descendants given navigable's active document.
875        let (sender, receiver) = channel(self.global().time_profiler_chan().clone()).unwrap();
876        let msg = ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, sender);
877        self.owner_window()
878            .as_global_scope()
879            .script_to_constellation_chan()
880            .send(msg)
881            .unwrap();
882        let _exited_pipeline_ids = receiver.recv().unwrap();
883        let Some(pipeline_id) = pipeline_id else {
884            return;
885        };
886        if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
887            exited_document.destroy_document_and_its_descendants(cx);
888        }
889
890        // Step 6. Let parentDocState be container's node navigable's active session history entry's document state.
891        // TODO
892
893        // Step 7. Remove the nested history from parentDocState's nested histories whose id equals navigable's id.
894        // TODO
895
896        // Step 8. Let traversable be container's node navigable's traversable navigable.
897        // TODO
898
899        // Step 9. Append the following session history traversal steps to traversable:
900        // TODO
901
902        // Step 10. Invoke WebDriver BiDi navigable destroyed with navigable.
903        // TODO
904    }
905}
906
907impl LayoutDom<'_, HTMLIFrameElement> {
908    #[inline]
909    pub(crate) fn pipeline_id(self) -> Option<PipelineId> {
910        (self.unsafe_get()).pipeline_id.get()
911    }
912
913    #[inline]
914    pub(crate) fn browsing_context_id(self) -> Option<BrowsingContextId> {
915        (self.unsafe_get()).browsing_context_id.get()
916    }
917
918    pub(crate) fn width(self) -> LengthOrPercentageOrAuto {
919        self.upcast::<Element>()
920            .get_attr_for_layout(&ns!(), &local_name!("width"))
921            .map(AttrValue::as_dimension)
922            .cloned()
923            .unwrap_or(LengthOrPercentageOrAuto::Auto)
924    }
925
926    pub(crate) fn height(self) -> LengthOrPercentageOrAuto {
927        self.upcast::<Element>()
928            .get_attr_for_layout(&ns!(), &local_name!("height"))
929            .map(AttrValue::as_dimension)
930            .cloned()
931            .unwrap_or(LengthOrPercentageOrAuto::Auto)
932    }
933}
934
935impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement {
936    // https://html.spec.whatwg.org/multipage/#dom-iframe-src
937    make_url_getter!(Src, "src");
938
939    // https://html.spec.whatwg.org/multipage/#dom-iframe-src
940    make_url_setter!(SetSrc, "src");
941
942    /// <https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc>
943    fn Srcdoc(&self) -> TrustedHTMLOrString {
944        let element = self.upcast::<Element>();
945        element.get_trusted_html_attribute(&local_name!("srcdoc"))
946    }
947
948    /// <https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc>
949    fn SetSrcdoc(&self, cx: &mut JSContext, value: TrustedHTMLOrString) -> Fallible<()> {
950        // Step 1: Let compliantString be the result of invoking the
951        // Get Trusted Type compliant string algorithm with TrustedHTML,
952        // this's relevant global object, the given value, "HTMLIFrameElement srcdoc", and "script".
953        let element = self.upcast::<Element>();
954        let value = TrustedHTML::get_trusted_type_compliant_string(
955            cx,
956            &element.owner_global(),
957            value,
958            "HTMLIFrameElement srcdoc",
959        )?;
960        // Step 2: Set an attribute value given this, srcdoc's local name, and compliantString.
961        element.set_attribute(
962            cx,
963            &local_name!("srcdoc"),
964            AttrValue::String(value.str().to_owned()),
965        );
966        Ok(())
967    }
968
969    /// <https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox>
970    ///
971    /// The supported tokens for sandbox's DOMTokenList are the allowed values defined in the
972    /// sandbox attribute and supported by the user agent. These range of possible values is
973    /// defined here: <https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox>
974    fn Sandbox(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
975        self.sandbox.or_init(|| {
976            DOMTokenList::new(
977                cx,
978                self.upcast::<Element>(),
979                &local_name!("sandbox"),
980                Some(vec![
981                    Atom::from("allow-downloads"),
982                    Atom::from("allow-forms"),
983                    Atom::from("allow-modals"),
984                    Atom::from("allow-orientation-lock"),
985                    Atom::from("allow-pointer-lock"),
986                    Atom::from("allow-popups"),
987                    Atom::from("allow-popups-to-escape-sandbox"),
988                    Atom::from("allow-presentation"),
989                    Atom::from("allow-same-origin"),
990                    Atom::from("allow-scripts"),
991                    Atom::from("allow-top-navigation"),
992                    Atom::from("allow-top-navigation-by-user-activation"),
993                    Atom::from("allow-top-navigation-to-custom-protocols"),
994                ]),
995            )
996        })
997    }
998
999    /// <https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow>
1000    fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> {
1001        self.browsing_context_id
1002            .get()
1003            .and_then(|id| self.script_window_proxies.find_window_proxy(id))
1004    }
1005
1006    /// <https://html.spec.whatwg.org/multipage/#concept-bcc-content-document>
1007    fn GetContentDocument(&self) -> Option<DomRoot<Document>> {
1008        // Step 1. If container's content navigable is null, then return null.
1009        let pipeline_id = self.pipeline_id.get()?;
1010
1011        // Step 2. Let document be container's content navigable's active document.
1012        // Note that this lookup will fail if the document is dissimilar-origin,
1013        // so we should return None in that case.
1014        let document = ScriptThread::find_document(pipeline_id)?;
1015        // Step 3. If document's origin and container's node document's origin are not same origin-domain, then return null.
1016        if !self
1017            .owner_document()
1018            .origin()
1019            .same_origin_domain(&document.origin())
1020        {
1021            return None;
1022        }
1023        // Step 4. Return document.
1024        Some(document)
1025    }
1026
1027    /// <https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy>
1028    fn ReferrerPolicy(&self) -> DOMString {
1029        reflect_referrer_policy_attribute(self.upcast::<Element>())
1030    }
1031
1032    // https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy
1033    make_setter!(SetReferrerPolicy, "referrerpolicy");
1034
1035    // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
1036    make_bool_getter!(AllowFullscreen, "allowfullscreen");
1037    // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
1038    make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
1039
1040    // <https://html.spec.whatwg.org/multipage/#dom-dim-width>
1041    make_getter!(Width, "width");
1042    // <https://html.spec.whatwg.org/multipage/#dom-dim-width>
1043    make_dimension_setter!(SetWidth, "width");
1044
1045    // <https://html.spec.whatwg.org/multipage/#dom-dim-height>
1046    make_getter!(Height, "height");
1047    // <https://html.spec.whatwg.org/multipage/#dom-dim-height>
1048    make_dimension_setter!(SetHeight, "height");
1049
1050    // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
1051    make_getter!(FrameBorder, "frameborder");
1052    // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
1053    make_setter!(SetFrameBorder, "frameborder");
1054
1055    // https://html.spec.whatwg.org/multipage/#dom-iframe-name
1056    // A child browsing context checks the name of its iframe only at the time
1057    // it is created; subsequent name sets have no special effect.
1058    make_atomic_setter!(SetName, "name");
1059
1060    // https://html.spec.whatwg.org/multipage/#dom-iframe-name
1061    // This is specified as reflecting the name content attribute of the
1062    // element, not the name of the child browsing context.
1063    make_getter!(Name, "name");
1064
1065    // https://html.spec.whatwg.org/multipage/#attr-iframe-loading
1066    // > The loading attribute is a lazy loading attribute. Its purpose is to indicate the policy for loading iframe elements that are outside the viewport.
1067    make_enumerated_getter!(
1068        Loading,
1069        "loading",
1070        "lazy" | "eager",
1071        // https://html.spec.whatwg.org/multipage/#lazy-loading-attribute
1072        // > The attribute's missing value default and invalid value default are both the Eager state.
1073        missing => "eager",
1074        invalid => "eager"
1075    );
1076
1077    // https://html.spec.whatwg.org/multipage/#attr-iframe-loading
1078    make_setter!(SetLoading, "loading");
1079
1080    // https://html.spec.whatwg.org/multipage/#dom-iframe-longdesc
1081    make_url_getter!(LongDesc, "longdesc");
1082
1083    // https://html.spec.whatwg.org/multipage/#dom-iframe-longdesc
1084    make_url_setter!(SetLongDesc, "longdesc");
1085}
1086
1087impl VirtualMethods for HTMLIFrameElement {
1088    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1089        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1090    }
1091
1092    fn attribute_mutated(
1093        &self,
1094        cx: &mut JSContext,
1095        attr: AttrRef<'_>,
1096        mutation: AttributeMutation,
1097    ) {
1098        self.super_type()
1099            .unwrap()
1100            .attribute_mutated(cx, attr, mutation);
1101        match *attr.local_name() {
1102            // From <https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox>:
1103            //
1104            // > When an iframe element's sandbox attribute is set or changed while
1105            // > it has a non-null content navigable, the user agent must parse the
1106            // > sandboxing directive given the attribute's value and the iframe
1107            // > element's iframe sandboxing flag set.
1108            //
1109            // > When an iframe element's sandbox attribute is removed while it has
1110            // > a non-null content navigable, the user agent must empty the iframe
1111            // > element's iframe sandboxing flag set.
1112            local_name!("sandbox") if self.browsing_context_id.get().is_some() => {
1113                self.parse_sandbox_attribute();
1114            },
1115            local_name!("srcdoc") => {
1116                // https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9
1117                // "Whenever an iframe element with a non-null nested browsing context has its
1118                // srcdoc attribute set, changed, or removed, the user agent must process the
1119                // iframe attributes."
1120                // but we can't check that directly, since the child browsing context
1121                // may be in a different script thread. Instead, we check to see if the parent
1122                // is in a document tree and has a browsing context, which is what causes
1123                // the child browsing context to be created.
1124
1125                // trigger the processing of iframe attributes whenever "srcdoc" attribute is set, changed or removed
1126                if self.upcast::<Node>().is_connected_with_browsing_context() {
1127                    debug!("iframe srcdoc modified while in browsing context.");
1128                    self.process_the_iframe_attributes(cx, ProcessingMode::NotFirstTime);
1129                }
1130            },
1131            local_name!("src") => {
1132                // https://html.spec.whatwg.org/multipage/#the-iframe-element
1133                // "Similarly, whenever an iframe element with a non-null nested browsing context
1134                // but with no srcdoc attribute specified has its src attribute set, changed, or removed,
1135                // the user agent must process the iframe attributes,"
1136                // but we can't check that directly, since the child browsing context
1137                // may be in a different script thread. Instead, we check to see if the parent
1138                // is in a document tree and has a browsing context, which is what causes
1139                // the child browsing context to be created.
1140                if self.upcast::<Node>().is_connected_with_browsing_context() {
1141                    debug!("iframe src set while in browsing context.");
1142                    self.process_the_iframe_attributes(cx, ProcessingMode::NotFirstTime);
1143                }
1144            },
1145            local_name!("loading") => {
1146                // https://html.spec.whatwg.org/multipage/#attr-iframe-loading
1147                // > When the loading attribute's state is changed to the Eager state, the user agent must run these steps:
1148                if !mutation.is_removal() && &**attr.value() == "lazy" {
1149                    return;
1150                }
1151
1152                // Step 1. Let resumptionSteps be the iframe element's lazy load resumption steps.
1153                // Step 3. Set the iframe's lazy load resumption steps to null.
1154                let previous_resumption_steps = self
1155                    .lazy_load_resumption_steps
1156                    .replace(LazyLoadResumptionSteps::None);
1157                match previous_resumption_steps {
1158                    // Step 2. If resumptionSteps is null, then return.
1159                    LazyLoadResumptionSteps::None => (),
1160                    LazyLoadResumptionSteps::SrcDoc => {
1161                        // Step 4. Invoke resumptionSteps.
1162                        self.navigate_to_the_srcdoc_resource(cx, ProcessingMode::NotFirstTime);
1163                    },
1164                }
1165            },
1166            _ => {},
1167        }
1168    }
1169
1170    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1171        match attr.local_name() {
1172            &local_name!("width") | &local_name!("height") => true,
1173            _ => self
1174                .super_type()
1175                .unwrap()
1176                .attribute_affects_presentational_hints(attr),
1177        }
1178    }
1179
1180    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1181        match *name {
1182            local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()),
1183            local_name!("width") => AttrValue::from_dimension(value.into()),
1184            local_name!("height") => AttrValue::from_dimension(value.into()),
1185            _ => self
1186                .super_type()
1187                .unwrap()
1188                .parse_plain_attribute(name, value),
1189        }
1190    }
1191
1192    /// <https://html.spec.whatwg.org/multipage/#the-iframe-element:html-element-post-connection-steps>
1193    fn post_connection_steps(&self, cx: &mut JSContext) {
1194        if let Some(s) = self.super_type() {
1195            s.post_connection_steps(cx);
1196        }
1197
1198        // This isn't mentioned any longer in the specification, but still seems important. This is
1199        // likely due to the fact that we have deviated a great deal with it comes to navigables
1200        // and browsing contexts.
1201        if !self.upcast::<Node>().is_connected_with_browsing_context() {
1202            return;
1203        }
1204
1205        debug!("<iframe> running post connection steps");
1206
1207        // Step 1: If insertedNode has a sandbox attribute, then parse the sandboxing directive
1208        // given the attribute's value and insertedNode's iframe sandboxing flag set.
1209        self.parse_sandbox_attribute();
1210
1211        // Step 2. Create a new child navigable for insertedNode.
1212        self.create_nested_browsing_context(cx);
1213
1214        // Step 3. Process the iframe attributes for insertedNode, with initialInsertion set to true.
1215        self.process_the_iframe_attributes(cx, ProcessingMode::FirstTime);
1216    }
1217
1218    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1219        if let Some(super_type) = self.super_type() {
1220            super_type.bind_to_tree(cx, context);
1221        }
1222
1223        self.owner_document().iframes().add(cx.no_gc(), self);
1224    }
1225
1226    /// <https://html.spec.whatwg.org/multipage/#the-iframe-element:html-element-removing-steps>
1227    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1228        if let Some(super_type) = self.super_type() {
1229            super_type.unbind_from_tree(cx, context);
1230        }
1231
1232        // The iframe HTML element removing steps, given removedNode, are to destroy a child
1233        // navigable given removedNode
1234        self.destroy_child_navigable(cx);
1235
1236        self.owner_document().iframes().remove(cx.no_gc(), self);
1237    }
1238}
1239
1240/// IframeContext is a wrapper around [`HTMLIFrameElement`] that implements the [`ResourceTimingListener`] trait.
1241/// Note: this implementation of `resource_timing_global` returns the parent document's global scope, not the iframe's global scope.
1242pub(crate) struct IframeContext<'a> {
1243    // The iframe element that this context is associated with.
1244    element: &'a HTMLIFrameElement,
1245    // The URL of the iframe document.
1246    url: ServoUrl,
1247}
1248
1249impl<'a> IframeContext<'a> {
1250    /// Creates a new IframeContext from a reference to an HTMLIFrameElement.
1251    pub fn new(element: &'a HTMLIFrameElement) -> Self {
1252        Self {
1253            element,
1254            url: element
1255                .shared_attribute_processing_steps_for_iframe_and_frame_elements(
1256                    ProcessingMode::NotFirstTime,
1257                )
1258                .expect("Must always have a URL when navigating"),
1259        }
1260    }
1261}
1262
1263impl<'a> ResourceTimingListener for IframeContext<'a> {
1264    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1265        (
1266            InitiatorType::LocalName("iframe".to_string()),
1267            self.url.clone(),
1268        )
1269    }
1270
1271    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1272        self.element.upcast::<Node>().owner_doc().global()
1273    }
1274}
1275
1276fn snapshot_self(iframe: &HTMLIFrameElement) -> TargetSnapshotParams {
1277    let child_navigable = iframe.GetContentWindow();
1278    TargetSnapshotParams {
1279        sandboxing_flags: determine_creation_sandboxing_flags(
1280            child_navigable.as_deref(),
1281            Some(iframe.upcast()),
1282        ),
1283        iframe_element_referrer_policy: determine_iframe_element_referrer_policy(Some(
1284            iframe.upcast(),
1285        )),
1286    }
1287}