script/dom/html/
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;
6
7use base::id::{BrowsingContextId, PipelineId, WebViewId};
8use bitflags::bitflags;
9use constellation_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
10use constellation_traits::{
11    IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, LoadOrigin,
12    NavigationHistoryBehavior, ScriptToConstellationMessage,
13};
14use dom_struct::dom_struct;
15use embedder_traits::ViewportDetails;
16use html5ever::{LocalName, Prefix, local_name, ns};
17use js::rust::HandleObject;
18use net_traits::ReferrerPolicy;
19use net_traits::request::Destination;
20use profile_traits::ipc as ProfiledIpc;
21use script_traits::{NewLayoutInfo, UpdatePipelineIdReason};
22use servo_url::ServoUrl;
23use style::attr::{AttrValue, LengthOrPercentageOrAuto};
24use stylo_atoms::Atom;
25
26use crate::document_loader::{LoadBlocker, LoadType};
27use crate::dom::attr::Attr;
28use crate::dom::bindings::cell::DomRefCell;
29use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
30use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
31use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
32use crate::dom::bindings::error::Fallible;
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
36use crate::dom::bindings::str::{DOMString, USVString};
37use crate::dom::document::{Document, determine_policy_for_token};
38use crate::dom::domtokenlist::DOMTokenList;
39use crate::dom::element::{
40    AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute,
41};
42use crate::dom::eventtarget::EventTarget;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::html::htmlelement::HTMLElement;
45use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
46use crate::dom::trustedhtml::TrustedHTML;
47use crate::dom::virtualmethods::VirtualMethods;
48use crate::dom::windowproxy::WindowProxy;
49use crate::script_runtime::CanGc;
50use crate::script_thread::ScriptThread;
51
52#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
53struct SandboxAllowance(u8);
54
55bitflags! {
56    impl SandboxAllowance: u8 {
57        const ALLOW_NOTHING = 0x00;
58        const ALLOW_SAME_ORIGIN = 0x01;
59        const ALLOW_TOP_NAVIGATION = 0x02;
60        const ALLOW_FORMS = 0x04;
61        const ALLOW_SCRIPTS = 0x08;
62        const ALLOW_POINTER_LOCK = 0x10;
63        const ALLOW_POPUPS = 0x20;
64    }
65}
66
67#[derive(PartialEq)]
68enum PipelineType {
69    InitialAboutBlank,
70    Navigation,
71}
72
73#[derive(PartialEq)]
74enum ProcessingMode {
75    FirstTime,
76    NotFirstTime,
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    sandbox_allowance: Cell<Option<SandboxAllowance>>,
94    load_blocker: DomRefCell<Option<LoadBlocker>>,
95    throttled: Cell<bool>,
96}
97
98impl HTMLIFrameElement {
99    pub(crate) fn is_sandboxed(&self) -> bool {
100        self.sandbox_allowance.get().is_some()
101    }
102
103    /// <https://html.spec.whatwg.org/multipage/#otherwise-steps-for-iframe-or-frame-elements>,
104    /// step 1.
105    fn get_url(&self) -> ServoUrl {
106        let element = self.upcast::<Element>();
107        element
108            .get_attribute(&ns!(), &local_name!("src"))
109            .and_then(|src| {
110                let url = src.value();
111                if url.is_empty() {
112                    None
113                } else {
114                    self.owner_document().base_url().join(&url).ok()
115                }
116            })
117            .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap())
118    }
119
120    pub(crate) fn navigate_or_reload_child_browsing_context(
121        &self,
122        load_data: LoadData,
123        history_handling: NavigationHistoryBehavior,
124        can_gc: CanGc,
125    ) {
126        self.start_new_pipeline(
127            load_data,
128            PipelineType::Navigation,
129            history_handling,
130            can_gc,
131        );
132    }
133
134    fn start_new_pipeline(
135        &self,
136        mut load_data: LoadData,
137        pipeline_type: PipelineType,
138        history_handling: NavigationHistoryBehavior,
139        can_gc: CanGc,
140    ) {
141        let sandboxed = if self.is_sandboxed() {
142            IFrameSandboxed
143        } else {
144            IFrameUnsandboxed
145        };
146
147        let browsing_context_id = match self.browsing_context_id() {
148            None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
149            Some(id) => id,
150        };
151
152        let webview_id = match self.webview_id() {
153            None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
154            Some(id) => id,
155        };
156
157        let document = self.owner_document();
158
159        {
160            let load_blocker = &self.load_blocker;
161            // Any oustanding load is finished from the point of view of the blocked
162            // document; the new navigation will continue blocking it.
163            LoadBlocker::terminate(load_blocker, can_gc);
164        }
165
166        if load_data.url.scheme() == "javascript" {
167            let window_proxy = self.GetContentWindow();
168            if let Some(window_proxy) = window_proxy {
169                if !ScriptThread::navigate_to_javascript_url(
170                    &document.global(),
171                    &window_proxy.global(),
172                    &mut load_data,
173                    Some(self.upcast()),
174                    can_gc,
175                ) {
176                    return;
177                }
178            }
179        }
180
181        match load_data.js_eval_result {
182            Some(JsEvalResult::NoContent) => (),
183            _ => {
184                let mut load_blocker = self.load_blocker.borrow_mut();
185                *load_blocker = Some(LoadBlocker::new(
186                    &document,
187                    LoadType::Subframe(load_data.url.clone()),
188                ));
189            },
190        };
191
192        let window = self.owner_window();
193        let old_pipeline_id = self.pipeline_id();
194        let new_pipeline_id = PipelineId::new();
195        self.pending_pipeline_id.set(Some(new_pipeline_id));
196
197        let load_info = IFrameLoadInfo {
198            parent_pipeline_id: window.pipeline_id(),
199            browsing_context_id,
200            webview_id,
201            new_pipeline_id,
202            is_private: false, // FIXME
203            inherited_secure_context: load_data.inherited_secure_context,
204            history_handling,
205        };
206
207        let viewport_details = window
208            .get_iframe_viewport_details_if_known(browsing_context_id)
209            .unwrap_or_else(|| ViewportDetails {
210                hidpi_scale_factor: window.device_pixel_ratio(),
211                ..Default::default()
212            });
213
214        match pipeline_type {
215            PipelineType::InitialAboutBlank => {
216                self.about_blank_pipeline_id.set(Some(new_pipeline_id));
217
218                let load_info = IFrameLoadInfoWithData {
219                    info: load_info,
220                    load_data: load_data.clone(),
221                    old_pipeline_id,
222                    sandbox: sandboxed,
223                    viewport_details,
224                    theme: window.theme(),
225                };
226                window
227                    .as_global_scope()
228                    .script_to_constellation_chan()
229                    .send(ScriptToConstellationMessage::ScriptNewIFrame(load_info))
230                    .unwrap();
231
232                let new_layout_info = NewLayoutInfo {
233                    parent_info: Some(window.pipeline_id()),
234                    new_pipeline_id,
235                    browsing_context_id,
236                    webview_id,
237                    opener: None,
238                    load_data,
239                    viewport_details,
240                    theme: window.theme(),
241                };
242
243                self.pipeline_id.set(Some(new_pipeline_id));
244                ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
245            },
246            PipelineType::Navigation => {
247                let load_info = IFrameLoadInfoWithData {
248                    info: load_info,
249                    load_data,
250                    old_pipeline_id,
251                    sandbox: sandboxed,
252                    viewport_details,
253                    theme: window.theme(),
254                };
255                window
256                    .as_global_scope()
257                    .script_to_constellation_chan()
258                    .send(ScriptToConstellationMessage::ScriptLoadedURLInIFrame(
259                        load_info,
260                    ))
261                    .unwrap();
262            },
263        }
264    }
265
266    /// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
267    fn process_the_iframe_attributes(&self, mode: ProcessingMode, can_gc: CanGc) {
268        // > 1. If `element`'s `srcdoc` attribute is specified, then:
269        if self
270            .upcast::<Element>()
271            .has_attribute(&local_name!("srcdoc"))
272        {
273            let url = ServoUrl::parse("about:srcdoc").unwrap();
274            let document = self.owner_document();
275            let window = self.owner_window();
276            let pipeline_id = Some(window.pipeline_id());
277            let mut load_data = LoadData::new(
278                LoadOrigin::Script(document.origin().immutable().clone()),
279                url,
280                pipeline_id,
281                window.as_global_scope().get_referrer(),
282                document.get_referrer_policy(),
283                Some(window.as_global_scope().is_secure_context()),
284                Some(document.insecure_requests_policy()),
285                document.has_trustworthy_ancestor_or_current_origin(),
286            );
287            load_data.destination = Destination::IFrame;
288            load_data.policy_container = Some(window.as_global_scope().policy_container());
289            let element = self.upcast::<Element>();
290            load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc")));
291            self.navigate_or_reload_child_browsing_context(
292                load_data,
293                NavigationHistoryBehavior::Push,
294                can_gc,
295            );
296            return;
297        }
298
299        let window = self.owner_window();
300
301        // https://html.spec.whatwg.org/multipage/#attr-iframe-name
302        // Note: the spec says to set the name 'when the nested browsing context is created'.
303        // The current implementation sets the name on the window,
304        // when the iframe attributes are first processed.
305        if mode == ProcessingMode::FirstTime {
306            if let Some(window) = self.GetContentWindow() {
307                window.set_name(
308                    self.upcast::<Element>()
309                        .get_name()
310                        .map_or(DOMString::from(""), |n| DOMString::from(&*n)),
311                );
312            }
313        }
314
315        if mode == ProcessingMode::FirstTime &&
316            !self.upcast::<Element>().has_attribute(&local_name!("src"))
317        {
318            return;
319        }
320
321        // > 2. Otherwise, if `element` has a `src` attribute specified, or
322        // >    `initialInsertion` is false, then run the shared attribute
323        // >    processing steps for `iframe` and `frame` elements given
324        // >    `element`.
325        let url = self.get_url();
326
327        // Step 2.4: Let referrerPolicy be the current state of element's referrerpolicy content
328        // attribute.
329        let document = self.owner_document();
330        let referrer_policy_token = self.ReferrerPolicy();
331
332        // Note: despite not being explicitly stated in the spec steps, this falls back to
333        // document's referrer policy here because it satisfies the expectations that when unset,
334        // the iframe should inherit the referrer policy of its parent
335        let referrer_policy = match determine_policy_for_token(referrer_policy_token.str()) {
336            ReferrerPolicy::EmptyString => document.get_referrer_policy(),
337            policy => policy,
338        };
339
340        // TODO(#25748):
341        // By spec, we return early if there's an ancestor browsing context
342        // "whose active document's url, ignoring fragments, is equal".
343        // However, asking about ancestor browsing contexts is more nuanced than
344        // it sounds and not implemented here.
345        // Within a single origin, we can do it by walking window proxies,
346        // and this check covers only that single-origin case, protecting
347        // against simple typo self-includes but nothing more elaborate.
348        let mut ancestor = window.GetParent();
349        while let Some(a) = ancestor {
350            if let Some(ancestor_url) = a.document().map(|d| d.url()) {
351                if ancestor_url.scheme() == url.scheme() &&
352                    ancestor_url.username() == url.username() &&
353                    ancestor_url.password() == url.password() &&
354                    ancestor_url.host() == url.host() &&
355                    ancestor_url.port() == url.port() &&
356                    ancestor_url.path() == url.path() &&
357                    ancestor_url.query() == url.query()
358                {
359                    return;
360                }
361            }
362            ancestor = a.parent().map(DomRoot::from_ref);
363        }
364
365        let creator_pipeline_id = if url.as_str() == "about:blank" {
366            Some(window.pipeline_id())
367        } else {
368            None
369        };
370
371        let mut load_data = LoadData::new(
372            LoadOrigin::Script(document.origin().immutable().clone()),
373            url,
374            creator_pipeline_id,
375            window.as_global_scope().get_referrer(),
376            referrer_policy,
377            Some(window.as_global_scope().is_secure_context()),
378            Some(document.insecure_requests_policy()),
379            document.has_trustworthy_ancestor_or_current_origin(),
380        );
381        load_data.destination = Destination::IFrame;
382        load_data.policy_container = Some(window.as_global_scope().policy_container());
383
384        let pipeline_id = self.pipeline_id();
385        // If the initial `about:blank` page is the current page, load with replacement enabled,
386        // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3
387        let is_about_blank =
388            pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
389
390        let history_handling = if is_about_blank {
391            NavigationHistoryBehavior::Replace
392        } else {
393            NavigationHistoryBehavior::Push
394        };
395
396        self.navigate_or_reload_child_browsing_context(load_data, history_handling, can_gc);
397    }
398
399    fn create_nested_browsing_context(&self, can_gc: CanGc) {
400        // Synchronously create a new browsing context, which will present
401        // `about:blank`. (This is not a navigation.)
402        //
403        // The pipeline started here will synchronously "completely finish
404        // loading", which will then asynchronously call
405        // `iframe_load_event_steps`.
406        //
407        // The precise event timing differs between implementations and
408        // remains controversial:
409        //
410        //  - [Unclear "iframe load event steps" for initial load of about:blank
411        //    in an iframe #490](https://github.com/whatwg/html/issues/490)
412        //  - [load event handling for iframes with no src may not be web
413        //    compatible #4965](https://github.com/whatwg/html/issues/4965)
414        //
415        let url = ServoUrl::parse("about:blank").unwrap();
416        let document = self.owner_document();
417        let window = self.owner_window();
418        let pipeline_id = Some(window.pipeline_id());
419        let mut load_data = LoadData::new(
420            LoadOrigin::Script(document.origin().immutable().clone()),
421            url,
422            pipeline_id,
423            window.as_global_scope().get_referrer(),
424            document.get_referrer_policy(),
425            Some(window.as_global_scope().is_secure_context()),
426            Some(document.insecure_requests_policy()),
427            document.has_trustworthy_ancestor_or_current_origin(),
428        );
429        load_data.destination = Destination::IFrame;
430        load_data.policy_container = Some(window.as_global_scope().policy_container());
431        let browsing_context_id = BrowsingContextId::new();
432        let webview_id = window.window_proxy().webview_id();
433        self.pipeline_id.set(None);
434        self.pending_pipeline_id.set(None);
435        self.webview_id.set(Some(webview_id));
436        self.browsing_context_id.set(Some(browsing_context_id));
437        self.start_new_pipeline(
438            load_data,
439            PipelineType::InitialAboutBlank,
440            NavigationHistoryBehavior::Push,
441            can_gc,
442        );
443    }
444
445    fn destroy_nested_browsing_context(&self) {
446        self.pipeline_id.set(None);
447        self.pending_pipeline_id.set(None);
448        self.about_blank_pipeline_id.set(None);
449        self.webview_id.set(None);
450        self.browsing_context_id.set(None);
451    }
452
453    pub(crate) fn update_pipeline_id(
454        &self,
455        new_pipeline_id: PipelineId,
456        reason: UpdatePipelineIdReason,
457        can_gc: CanGc,
458    ) {
459        if self.pending_pipeline_id.get() != Some(new_pipeline_id) &&
460            reason == UpdatePipelineIdReason::Navigation
461        {
462            return;
463        }
464
465        self.pipeline_id.set(Some(new_pipeline_id));
466
467        // Only terminate the load blocker if the pipeline id was updated due to a traversal.
468        // The load blocker will be terminated for a navigation in iframe_load_event_steps.
469        if reason == UpdatePipelineIdReason::Traversal {
470            let blocker = &self.load_blocker;
471            LoadBlocker::terminate(blocker, can_gc);
472        }
473
474        self.upcast::<Node>().dirty(NodeDamage::Other);
475    }
476
477    fn new_inherited(
478        local_name: LocalName,
479        prefix: Option<Prefix>,
480        document: &Document,
481    ) -> HTMLIFrameElement {
482        HTMLIFrameElement {
483            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
484            browsing_context_id: Cell::new(None),
485            webview_id: Cell::new(None),
486            pipeline_id: Cell::new(None),
487            pending_pipeline_id: Cell::new(None),
488            about_blank_pipeline_id: Cell::new(None),
489            sandbox: Default::default(),
490            sandbox_allowance: Cell::new(None),
491            load_blocker: DomRefCell::new(None),
492            throttled: Cell::new(false),
493        }
494    }
495
496    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
497    pub(crate) fn new(
498        local_name: LocalName,
499        prefix: Option<Prefix>,
500        document: &Document,
501        proto: Option<HandleObject>,
502        can_gc: CanGc,
503    ) -> DomRoot<HTMLIFrameElement> {
504        Node::reflect_node_with_proto(
505            Box::new(HTMLIFrameElement::new_inherited(
506                local_name, prefix, document,
507            )),
508            document,
509            proto,
510            can_gc,
511        )
512    }
513
514    #[inline]
515    pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
516        self.pipeline_id.get()
517    }
518
519    #[inline]
520    pub(crate) fn browsing_context_id(&self) -> Option<BrowsingContextId> {
521        self.browsing_context_id.get()
522    }
523
524    #[inline]
525    pub(crate) fn webview_id(&self) -> Option<WebViewId> {
526        self.webview_id.get()
527    }
528
529    pub(crate) fn set_throttled(&self, throttled: bool) {
530        if self.throttled.get() != throttled {
531            self.throttled.set(throttled);
532        }
533    }
534
535    /// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps> steps 1-4
536    pub(crate) fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId, can_gc: CanGc) {
537        // TODO(#9592): assert that the load blocker is present at all times when we
538        //              can guarantee that it's created for the case of iframe.reload().
539        if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
540            return;
541        }
542
543        // TODO A cross-origin child document would not be easily accessible
544        //      from this script thread. It's unclear how to implement
545        //      steps 2, 3, and 5 efficiently in this case.
546        // TODO Step 2 - check child document `mute iframe load` flag
547        // TODO Step 3 - set child document  `mut iframe load` flag
548
549        // Step 4
550        self.upcast::<EventTarget>()
551            .fire_event(atom!("load"), can_gc);
552
553        let blocker = &self.load_blocker;
554        LoadBlocker::terminate(blocker, can_gc);
555
556        // TODO Step 5 - unset child document `mut iframe load` flag
557    }
558}
559
560pub(crate) trait HTMLIFrameElementLayoutMethods {
561    fn pipeline_id(self) -> Option<PipelineId>;
562    fn browsing_context_id(self) -> Option<BrowsingContextId>;
563    fn get_width(self) -> LengthOrPercentageOrAuto;
564    fn get_height(self) -> LengthOrPercentageOrAuto;
565}
566
567impl HTMLIFrameElementLayoutMethods for LayoutDom<'_, HTMLIFrameElement> {
568    #[inline]
569    fn pipeline_id(self) -> Option<PipelineId> {
570        (self.unsafe_get()).pipeline_id.get()
571    }
572
573    #[inline]
574    fn browsing_context_id(self) -> Option<BrowsingContextId> {
575        (self.unsafe_get()).browsing_context_id.get()
576    }
577
578    fn get_width(self) -> LengthOrPercentageOrAuto {
579        self.upcast::<Element>()
580            .get_attr_for_layout(&ns!(), &local_name!("width"))
581            .map(AttrValue::as_dimension)
582            .cloned()
583            .unwrap_or(LengthOrPercentageOrAuto::Auto)
584    }
585
586    fn get_height(self) -> LengthOrPercentageOrAuto {
587        self.upcast::<Element>()
588            .get_attr_for_layout(&ns!(), &local_name!("height"))
589            .map(AttrValue::as_dimension)
590            .cloned()
591            .unwrap_or(LengthOrPercentageOrAuto::Auto)
592    }
593}
594
595impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement {
596    // https://html.spec.whatwg.org/multipage/#dom-iframe-src
597    make_url_getter!(Src, "src");
598
599    // https://html.spec.whatwg.org/multipage/#dom-iframe-src
600    make_url_setter!(SetSrc, "src");
601
602    // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc
603    fn Srcdoc(&self) -> TrustedHTMLOrString {
604        let element = self.upcast::<Element>();
605        element.get_trusted_html_attribute(&local_name!("srcdoc"))
606    }
607
608    // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc
609    fn SetSrcdoc(&self, value: TrustedHTMLOrString, can_gc: CanGc) -> Fallible<()> {
610        // Step 1: Let compliantString be the result of invoking the
611        // Get Trusted Type compliant string algorithm with TrustedHTML,
612        // this's relevant global object, the given value, "HTMLIFrameElement srcdoc", and "script".
613        let element = self.upcast::<Element>();
614        let value = TrustedHTML::get_trusted_script_compliant_string(
615            &element.owner_global(),
616            value,
617            "HTMLIFrameElement srcdoc",
618            can_gc,
619        )?;
620        // Step 2: Set an attribute value given this, srcdoc's local name, and compliantString.
621        element.set_attribute(
622            &local_name!("srcdoc"),
623            AttrValue::String(value.as_ref().to_owned()),
624            can_gc,
625        );
626        Ok(())
627    }
628
629    // https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox
630    fn Sandbox(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
631        self.sandbox.or_init(|| {
632            DOMTokenList::new(
633                self.upcast::<Element>(),
634                &local_name!("sandbox"),
635                Some(vec![
636                    Atom::from("allow-same-origin"),
637                    Atom::from("allow-forms"),
638                    Atom::from("allow-pointer-lock"),
639                    Atom::from("allow-popups"),
640                    Atom::from("allow-scripts"),
641                    Atom::from("allow-top-navigation"),
642                ]),
643                can_gc,
644            )
645        })
646    }
647
648    // https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow
649    fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> {
650        self.browsing_context_id
651            .get()
652            .and_then(ScriptThread::find_window_proxy)
653    }
654
655    // https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument
656    // https://html.spec.whatwg.org/multipage/#concept-bcc-content-document
657    fn GetContentDocument(&self) -> Option<DomRoot<Document>> {
658        // Step 1.
659        let pipeline_id = self.pipeline_id.get()?;
660
661        // Step 2-3.
662        // Note that this lookup will fail if the document is dissimilar-origin,
663        // so we should return None in that case.
664        let document = ScriptThread::find_document(pipeline_id)?;
665
666        // Step 4.
667        let current = GlobalScope::current()
668            .expect("No current global object")
669            .as_window()
670            .Document();
671        if !current.origin().same_origin_domain(document.origin()) {
672            return None;
673        }
674        // Step 5.
675        Some(document)
676    }
677
678    /// <https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy>
679    fn ReferrerPolicy(&self) -> DOMString {
680        reflect_referrer_policy_attribute(self.upcast::<Element>())
681    }
682
683    // https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy
684    make_setter!(SetReferrerPolicy, "referrerpolicy");
685
686    // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
687    make_bool_getter!(AllowFullscreen, "allowfullscreen");
688    // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
689    make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
690
691    // https://html.spec.whatwg.org/multipage/#dom-dim-width
692    make_getter!(Width, "width");
693    // https://html.spec.whatwg.org/multipage/#dom-dim-width
694    make_dimension_setter!(SetWidth, "width");
695
696    // https://html.spec.whatwg.org/multipage/#dom-dim-height
697    make_getter!(Height, "height");
698    // https://html.spec.whatwg.org/multipage/#dom-dim-height
699    make_dimension_setter!(SetHeight, "height");
700
701    // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
702    make_getter!(FrameBorder, "frameborder");
703    // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
704    make_setter!(SetFrameBorder, "frameborder");
705
706    // https://html.spec.whatwg.org/multipage/#dom-iframe-name
707    // A child browsing context checks the name of its iframe only at the time
708    // it is created; subsequent name sets have no special effect.
709    make_atomic_setter!(SetName, "name");
710
711    // https://html.spec.whatwg.org/multipage/#dom-iframe-name
712    // This is specified as reflecting the name content attribute of the
713    // element, not the name of the child browsing context.
714    make_getter!(Name, "name");
715}
716
717impl VirtualMethods for HTMLIFrameElement {
718    fn super_type(&self) -> Option<&dyn VirtualMethods> {
719        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
720    }
721
722    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
723        self.super_type()
724            .unwrap()
725            .attribute_mutated(attr, mutation, can_gc);
726        match *attr.local_name() {
727            local_name!("sandbox") => {
728                self.sandbox_allowance
729                    .set(mutation.new_value(attr).map(|value| {
730                        let mut modes = SandboxAllowance::ALLOW_NOTHING;
731                        for token in value.as_tokens() {
732                            modes |= match &*token.to_ascii_lowercase() {
733                                "allow-same-origin" => SandboxAllowance::ALLOW_SAME_ORIGIN,
734                                "allow-forms" => SandboxAllowance::ALLOW_FORMS,
735                                "allow-pointer-lock" => SandboxAllowance::ALLOW_POINTER_LOCK,
736                                "allow-popups" => SandboxAllowance::ALLOW_POPUPS,
737                                "allow-scripts" => SandboxAllowance::ALLOW_SCRIPTS,
738                                "allow-top-navigation" => SandboxAllowance::ALLOW_TOP_NAVIGATION,
739                                _ => SandboxAllowance::ALLOW_NOTHING,
740                            };
741                        }
742                        modes
743                    }));
744            },
745            local_name!("srcdoc") => {
746                // https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9
747                // "Whenever an iframe element with a non-null nested browsing context has its
748                // srcdoc attribute set, changed, or removed, the user agent must process the
749                // iframe attributes."
750                // but we can't check that directly, since the child browsing context
751                // may be in a different script thread. Instead, we check to see if the parent
752                // is in a document tree and has a browsing context, which is what causes
753                // the child browsing context to be created.
754
755                // trigger the processing of iframe attributes whenever "srcdoc" attribute is set, changed or removed
756                if self.upcast::<Node>().is_connected_with_browsing_context() {
757                    debug!("iframe srcdoc modified while in browsing context.");
758                    self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
759                }
760            },
761            local_name!("src") => {
762                // https://html.spec.whatwg.org/multipage/#the-iframe-element
763                // "Similarly, whenever an iframe element with a non-null nested browsing context
764                // but with no srcdoc attribute specified has its src attribute set, changed, or removed,
765                // the user agent must process the iframe attributes,"
766                // but we can't check that directly, since the child browsing context
767                // may be in a different script thread. Instead, we check to see if the parent
768                // is in a document tree and has a browsing context, which is what causes
769                // the child browsing context to be created.
770                if self.upcast::<Node>().is_connected_with_browsing_context() {
771                    debug!("iframe src set while in browsing context.");
772                    self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
773                }
774            },
775            _ => {},
776        }
777    }
778
779    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
780        match *name {
781            local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()),
782            local_name!("width") => AttrValue::from_dimension(value.into()),
783            local_name!("height") => AttrValue::from_dimension(value.into()),
784            _ => self
785                .super_type()
786                .unwrap()
787                .parse_plain_attribute(name, value),
788        }
789    }
790
791    fn post_connection_steps(&self) {
792        if let Some(s) = self.super_type() {
793            s.post_connection_steps();
794        }
795
796        // https://html.spec.whatwg.org/multipage/#the-iframe-element
797        // "When an iframe element is inserted into a document that has
798        // a browsing context, the user agent must create a new
799        // browsing context, set the element's nested browsing context
800        // to the newly-created browsing context, and then process the
801        // iframe attributes for the "first time"."
802        if self.upcast::<Node>().is_connected_with_browsing_context() {
803            debug!("iframe bound to browsing context.");
804            self.create_nested_browsing_context(CanGc::note());
805            self.process_the_iframe_attributes(ProcessingMode::FirstTime, CanGc::note());
806        }
807    }
808
809    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
810        if let Some(s) = self.super_type() {
811            s.bind_to_tree(context, can_gc);
812        }
813        self.owner_document().invalidate_iframes_collection();
814    }
815
816    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
817        self.super_type().unwrap().unbind_from_tree(context, can_gc);
818
819        let blocker = &self.load_blocker;
820        LoadBlocker::terminate(blocker, CanGc::note());
821
822        // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
823        let window = self.owner_window();
824        let (sender, receiver) =
825            ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
826
827        // Ask the constellation to remove the iframe, and tell us the
828        // pipeline ids of the closed pipelines.
829        let browsing_context_id = match self.browsing_context_id() {
830            None => return warn!("Unbinding already unbound iframe."),
831            Some(id) => id,
832        };
833        debug!("Unbinding frame {}.", browsing_context_id);
834
835        let msg = ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, sender);
836        window
837            .as_global_scope()
838            .script_to_constellation_chan()
839            .send(msg)
840            .unwrap();
841        let exited_pipeline_ids = receiver.recv().unwrap();
842
843        // The spec for discarding is synchronous,
844        // so we need to discard the browsing contexts now, rather than
845        // when the `PipelineExit` message arrives.
846        for exited_pipeline_id in exited_pipeline_ids {
847            // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
848            if let Some(exited_document) = ScriptThread::find_document(exited_pipeline_id) {
849                debug!(
850                    "Discarding browsing context for pipeline {}",
851                    exited_pipeline_id
852                );
853                let exited_window = exited_document.window();
854                exited_window.discard_browsing_context();
855                for exited_iframe in exited_document.iframes().iter() {
856                    debug!("Discarding nested browsing context");
857                    exited_iframe.destroy_nested_browsing_context();
858                }
859            }
860        }
861
862        // Resetting the pipeline_id to None is required here so that
863        // if this iframe is subsequently re-added to the document
864        // the load doesn't think that it's a navigation, but instead
865        // a new iframe. Without this, the constellation gets very
866        // confused.
867        self.destroy_nested_browsing_context();
868
869        self.owner_document().invalidate_iframes_collection();
870    }
871}