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