1use 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::ipc as ProfiledIpc;
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::document_loader::{LoadBlocker, LoadType};
31use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
32use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
33use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
34use crate::dom::bindings::error::Fallible;
35use crate::dom::bindings::inheritance::Castable;
36use crate::dom::bindings::refcounted::Trusted;
37use crate::dom::bindings::reflector::DomGlobal;
38use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
39use crate::dom::bindings::str::{DOMString, USVString};
40use crate::dom::document::Document;
41use crate::dom::domtokenlist::DOMTokenList;
42use crate::dom::element::attributes::storage::AttrRef;
43use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
44use crate::dom::eventtarget::EventTarget;
45use crate::dom::globalscope::GlobalScope;
46use crate::dom::html::htmlelement::HTMLElement;
47use crate::dom::node::virtualmethods::VirtualMethods;
48use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
49use crate::dom::performance::performanceresourcetiming::InitiatorType;
50use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
51use crate::dom::windowproxy::WindowProxy;
52use crate::navigation::{
53 determine_creation_sandboxing_flags, determine_iframe_element_referrer_policy,
54};
55use crate::network_listener::ResourceTimingListener;
56use crate::script_thread::{ScriptThread, with_script_thread};
57use crate::script_window_proxies::ScriptWindowProxies;
58
59#[derive(PartialEq)]
60enum PipelineType {
61 InitialAboutBlank,
62 Navigation,
63}
64
65#[derive(Clone, Copy, PartialEq)]
66pub(crate) enum ProcessingMode {
67 FirstTime,
68 NotFirstTime,
69}
70
71#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
73enum LazyLoadResumptionSteps {
74 #[default]
75 None,
76 SrcDoc,
77}
78
79#[dom_struct]
80pub(crate) struct HTMLIFrameElement {
81 htmlelement: HTMLElement,
82 #[no_trace]
83 webview_id: Cell<Option<WebViewId>>,
84 #[no_trace]
85 browsing_context_id: Cell<Option<BrowsingContextId>>,
86 #[no_trace]
87 pipeline_id: Cell<Option<PipelineId>>,
88 #[no_trace]
89 pending_pipeline_id: Cell<Option<PipelineId>>,
90 #[no_trace]
91 about_blank_pipeline_id: Cell<Option<PipelineId>>,
92 sandbox: MutNullableDom<DOMTokenList>,
93 #[no_trace]
94 sandboxing_flag_set: Cell<Option<SandboxingFlagSet>>,
95 load_blocker: DomRefCell<Option<LoadBlocker>>,
96 throttled: Cell<bool>,
97 #[conditional_malloc_size_of]
98 script_window_proxies: Rc<ScriptWindowProxies>,
99 current_navigation_was_lazy_loaded: Cell<bool>,
101 #[no_trace]
103 lazy_load_resumption_steps: Cell<LazyLoadResumptionSteps>,
104 pending_navigation: Cell<bool>,
111 already_fired_synchronous_load_event: Cell<bool>,
115}
116
117impl HTMLIFrameElement {
118 fn shared_attribute_processing_steps_for_iframe_and_frame_elements(
120 &self,
121 _mode: ProcessingMode,
122 ) -> Option<ServoUrl> {
123 let element = self.upcast::<Element>();
124 let url = element
126 .get_attribute_string_value(&local_name!("src"))
127 .and_then(|url| {
128 if url.is_empty() {
129 None
130 } else {
131 self.owner_document().encoding_parse_a_url(&url).ok()
135 }
136 })
137 .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
139 Some(url)
149 }
150
151 pub(crate) fn navigate_or_reload_child_browsing_context(
152 &self,
153 load_data: LoadData,
154 history_handling: NavigationHistoryBehavior,
155 mode: ProcessingMode,
156 target_snapshot_params: TargetSnapshotParams,
157 cx: &mut JSContext,
158 ) {
159 self.already_fired_synchronous_load_event.set(false);
166
167 self.start_new_pipeline(
168 cx,
169 load_data,
170 PipelineType::Navigation,
171 history_handling,
172 mode,
173 target_snapshot_params,
174 );
175 }
176
177 fn start_new_pipeline(
178 &self,
179 cx: &mut JSContext,
180 mut load_data: LoadData,
181 pipeline_type: PipelineType,
182 history_handling: NavigationHistoryBehavior,
183 mode: ProcessingMode,
184 target_snapshot_params: TargetSnapshotParams,
185 ) {
186 let document = self.owner_document();
187
188 {
189 let load_blocker = &self.load_blocker;
190 LoadBlocker::terminate(load_blocker, cx);
193
194 *load_blocker.borrow_mut() = Some(LoadBlocker::new(
195 &document,
196 LoadType::Subframe(load_data.url.clone()),
197 ));
198 }
199
200 if load_data.url.scheme() != "javascript" {
201 self.continue_navigation(
202 cx,
203 load_data,
204 pipeline_type,
205 history_handling,
206 target_snapshot_params,
207 );
208 return;
209 }
210
211 let iframe = Trusted::new(self);
215 let doc = Trusted::new(&*document);
216 document
217 .global()
218 .task_manager()
219 .networking_task_source()
220 .queue(task!(navigate_to_javascript: move |cx| {
221 let this = iframe.root();
222 let window_proxy = this.GetContentWindow();
223 if let Some(window_proxy) = window_proxy {
224 if !ScriptThread::navigate_to_javascript_url(
227 cx,
228 &this.owner_global(),
229 &window_proxy.global(),
230 &mut load_data,
231 Some(this.upcast()),
232 Some(mode == ProcessingMode::FirstTime),
233 ) {
234 LoadBlocker::terminate(&this.load_blocker, cx);
235 return;
236 }
237 load_data.about_base_url = doc.root().about_base_url();
238 }
239 this.continue_navigation(cx, load_data, pipeline_type, history_handling, target_snapshot_params);
240 }));
241 }
242
243 fn continue_navigation(
244 &self,
245 cx: &mut JSContext,
246 load_data: LoadData,
247 pipeline_type: PipelineType,
248 history_handling: NavigationHistoryBehavior,
249 target_snapshot_params: TargetSnapshotParams,
250 ) {
251 let browsing_context_id = match self.browsing_context_id() {
252 None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
253 Some(id) => id,
254 };
255
256 let webview_id = match self.webview_id() {
257 None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
258 Some(id) => id,
259 };
260
261 let window = self.owner_window();
262 let old_pipeline_id = self.pipeline_id();
263 let new_pipeline_id = PipelineId::new();
264 self.pending_pipeline_id.set(Some(new_pipeline_id));
265
266 let load_info = IFrameLoadInfo {
267 parent_pipeline_id: window.pipeline_id(),
268 browsing_context_id,
269 webview_id,
270 new_pipeline_id,
271 is_private: false, inherited_secure_context: load_data.inherited_secure_context,
273 history_handling,
274 target_snapshot_params,
275 };
276
277 let viewport_details = window
278 .get_iframe_viewport_details_if_known(browsing_context_id)
279 .unwrap_or_else(|| ViewportDetails {
280 hidpi_scale_factor: window.device_pixel_ratio(),
281 ..Default::default()
282 });
283
284 match pipeline_type {
285 PipelineType::InitialAboutBlank => {
286 self.about_blank_pipeline_id.set(Some(new_pipeline_id));
287
288 let load_info = IFrameLoadInfoWithData {
289 info: load_info,
290 load_data: load_data.clone(),
291 old_pipeline_id,
292 viewport_details,
293 theme: window.theme(),
294 };
295 window
296 .as_global_scope()
297 .script_to_constellation_chan()
298 .send(ScriptToConstellationMessage::ScriptNewIFrame(load_info))
299 .unwrap();
300
301 let new_pipeline_info = NewPipelineInfo {
302 parent_info: Some(window.pipeline_id()),
303 new_pipeline_id,
304 browsing_context_id,
305 webview_id,
306 opener: None,
307 load_data,
308 viewport_details,
309 user_content_manager_id: None,
310 theme: window.theme(),
311 target_snapshot_params,
312 };
313
314 self.pipeline_id.set(Some(new_pipeline_id));
315 with_script_thread(|script_thread| {
316 script_thread.spawn_pipeline(cx, new_pipeline_info);
317 });
318 },
319 PipelineType::Navigation => {
320 let load_info = IFrameLoadInfoWithData {
321 info: load_info,
322 load_data,
323 old_pipeline_id,
324 viewport_details,
325 theme: window.theme(),
326 };
327 window
328 .as_global_scope()
329 .script_to_constellation_chan()
330 .send(ScriptToConstellationMessage::ScriptLoadedURLInIFrame(
331 load_info,
332 ))
333 .unwrap();
334 },
335 }
336 }
337
338 pub(crate) fn is_initial_blank_document(&self) -> bool {
344 self.pending_pipeline_id.get() == self.about_blank_pipeline_id.get()
345 }
346
347 fn navigate_an_iframe_or_frame(
349 &self,
350 cx: &mut JSContext,
351 load_data: LoadData,
352 mode: ProcessingMode,
353 ) {
354 let history_handling = if !self
357 .GetContentDocument()
358 .is_some_and(|doc| doc.completely_loaded())
359 {
360 NavigationHistoryBehavior::Replace
361 } else {
362 NavigationHistoryBehavior::Auto
364 };
365 let target_snapshot_params = snapshot_self(self);
373 self.navigate_or_reload_child_browsing_context(
374 load_data,
375 history_handling,
376 mode,
377 target_snapshot_params,
378 cx,
379 );
380 }
381
382 fn will_lazy_load_element_steps(&self) -> bool {
384 if !self.owner_document().scripting_enabled() {
386 return false;
387 }
388 self.Loading() == "lazy"
391 }
392
393 fn navigate_to_the_srcdoc_resource(&self, mode: ProcessingMode, cx: &mut JSContext) {
395 let url = ServoUrl::parse("about:srcdoc").unwrap();
398 let document = self.owner_document();
399 let window = self.owner_window();
400 let pipeline_id = Some(window.pipeline_id());
401 let mut load_data = LoadData::new(
402 LoadOrigin::Script(document.origin().snapshot()),
403 url,
404 Some(document.base_url()),
405 pipeline_id,
406 window.as_global_scope().get_referrer(),
407 document.get_referrer_policy(),
408 Some(window.as_global_scope().is_secure_context()),
409 Some(document.insecure_requests_policy()),
410 document.has_trustworthy_ancestor_or_current_origin(),
411 self.sandboxing_flag_set(),
412 );
413 load_data.destination = Destination::IFrame;
414 load_data.policy_container = Some(window.as_global_scope().policy_container());
415 load_data.srcdoc = String::from(
416 self.upcast::<Element>()
417 .get_string_attribute(&local_name!("srcdoc")),
418 );
419
420 self.navigate_an_iframe_or_frame(cx, load_data, mode);
421 }
422
423 fn mark_navigation_as_lazy_loaded(&self, cx: &mut JSContext) {
425 self.current_navigation_was_lazy_loaded.set(true);
427 let blocker = &self.load_blocker;
428 LoadBlocker::terminate(blocker, cx);
429 }
430
431 fn process_the_iframe_attributes(&self, mode: ProcessingMode, cx: &mut JSContext) {
433 let element = self.upcast::<Element>();
434
435 if element.has_attribute(&local_name!("srcdoc")) {
439 self.current_navigation_was_lazy_loaded.set(false);
441 if self.will_lazy_load_element_steps() {
443 self.lazy_load_resumption_steps
446 .set(LazyLoadResumptionSteps::SrcDoc);
447 self.mark_navigation_as_lazy_loaded(cx);
449 return;
453 }
454 self.navigate_to_the_srcdoc_resource(mode, cx);
457 return;
458 }
459
460 let window = self.owner_window();
461
462 if mode == ProcessingMode::FirstTime &&
467 let Some(window) = self.GetContentWindow()
468 {
469 window.set_name(
470 element
471 .get_name()
472 .map_or(DOMString::from(""), |n| DOMString::from(&*n)),
473 );
474 }
475
476 let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements(mode)
479 else {
480 return;
482 };
483
484 if url.matches_about_blank() && mode == ProcessingMode::FirstTime {
486 self.already_fired_synchronous_load_event.set(true);
488 self.run_iframe_load_event_steps(cx);
490 return;
492 }
493
494 let document = self.owner_document();
497 let referrer_policy_token = self.ReferrerPolicy();
498
499 let referrer_policy = match ReferrerPolicy::from(&*referrer_policy_token.str()) {
503 ReferrerPolicy::EmptyString => document.get_referrer_policy(),
504 policy => policy,
505 };
506
507 let mut ancestor = window.GetParent();
516 while let Some(a) = ancestor {
517 if let Some(ancestor_url) = a.document().map(|d| d.url()) &&
518 ancestor_url.scheme() == url.scheme() &&
519 ancestor_url.username() == url.username() &&
520 ancestor_url.password() == url.password() &&
521 ancestor_url.host() == url.host() &&
522 ancestor_url.port() == url.port() &&
523 ancestor_url.path() == url.path() &&
524 ancestor_url.query() == url.query()
525 {
526 return;
527 }
528 ancestor = a.parent().map(DomRoot::from_ref);
529 }
530
531 let (creator_pipeline_id, about_base_url) = if url.matches_about_blank() {
532 (Some(window.pipeline_id()), Some(document.base_url()))
533 } else {
534 (None, document.about_base_url())
535 };
536
537 let propagate_encoding_to_child_document = url.origin().same_origin(&window.origin());
538 let mut load_data = LoadData::new(
539 LoadOrigin::Script(document.origin().snapshot()),
540 url,
541 about_base_url,
542 creator_pipeline_id,
543 window.as_global_scope().get_referrer(),
544 referrer_policy,
545 Some(window.as_global_scope().is_secure_context()),
546 Some(document.insecure_requests_policy()),
547 document.has_trustworthy_ancestor_or_current_origin(),
548 self.sandboxing_flag_set(),
549 );
550 load_data.destination = Destination::IFrame;
551 load_data.policy_container = Some(window.as_global_scope().policy_container());
552 if propagate_encoding_to_child_document {
553 load_data.container_document_encoding = Some(document.encoding());
554 }
555
556 let pipeline_id = self.pipeline_id();
557 let is_about_blank =
560 pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
561
562 let history_handling = if is_about_blank {
563 NavigationHistoryBehavior::Replace
564 } else {
565 NavigationHistoryBehavior::Push
566 };
567
568 let target_snapshot_params = snapshot_self(self);
569 self.navigate_or_reload_child_browsing_context(
570 load_data,
571 history_handling,
572 mode,
573 target_snapshot_params,
574 cx,
575 );
576 }
577
578 fn create_nested_browsing_context(&self, cx: &mut JSContext) {
586 let url = ServoUrl::parse("about:blank").unwrap();
587 let document = self.owner_document();
588 let window = self.owner_window();
589 let pipeline_id = Some(window.pipeline_id());
590 let mut load_data = LoadData::new(
591 LoadOrigin::Script(document.origin().snapshot()),
592 url,
593 Some(document.base_url()),
594 pipeline_id,
595 window.as_global_scope().get_referrer(),
596 document.get_referrer_policy(),
597 Some(window.as_global_scope().is_secure_context()),
598 Some(document.insecure_requests_policy()),
599 document.has_trustworthy_ancestor_or_current_origin(),
600 self.sandboxing_flag_set(),
601 );
602 load_data.is_initial_about_blank = true;
603 load_data.destination = Destination::IFrame;
604 load_data.policy_container = Some(window.as_global_scope().policy_container());
605
606 let browsing_context_id = BrowsingContextId::new();
607 let webview_id = window.window_proxy().webview_id();
608 self.pipeline_id.set(None);
609 self.pending_pipeline_id.set(None);
610 self.webview_id.set(Some(webview_id));
611 self.browsing_context_id.set(Some(browsing_context_id));
612 self.start_new_pipeline(
613 cx,
614 load_data,
615 PipelineType::InitialAboutBlank,
616 NavigationHistoryBehavior::Push,
617 ProcessingMode::FirstTime,
618 snapshot_self(self),
619 );
620 }
621
622 fn destroy_nested_browsing_context(&self) {
623 self.pipeline_id.set(None);
624 self.pending_pipeline_id.set(None);
625 self.about_blank_pipeline_id.set(None);
626 self.webview_id.set(None);
627 if let Some(browsing_context_id) = self.browsing_context_id.take() {
628 self.script_window_proxies.remove(browsing_context_id)
629 }
630 }
631
632 pub(crate) fn update_pipeline_id(
636 &self,
637 new_pipeline_id: PipelineId,
638 reason: UpdatePipelineIdReason,
639 cx: &mut JSContext,
640 ) -> bool {
641 if !self.is_initial_blank_document() {
646 self.pending_navigation.set(false);
647 }
648 if self.pending_pipeline_id.get() != Some(new_pipeline_id) &&
649 reason == UpdatePipelineIdReason::Navigation
650 {
651 return false;
652 }
653
654 self.pipeline_id.set(Some(new_pipeline_id));
655
656 if reason == UpdatePipelineIdReason::Traversal {
659 let blocker = &self.load_blocker;
660 LoadBlocker::terminate(blocker, cx);
661 }
662
663 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
664 true
665 }
666
667 fn new_inherited(
668 local_name: LocalName,
669 prefix: Option<Prefix>,
670 document: &Document,
671 ) -> HTMLIFrameElement {
672 HTMLIFrameElement {
673 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
674 browsing_context_id: Cell::new(None),
675 webview_id: Cell::new(None),
676 pipeline_id: Cell::new(None),
677 pending_pipeline_id: Cell::new(None),
678 about_blank_pipeline_id: Cell::new(None),
679 sandbox: Default::default(),
680 sandboxing_flag_set: Cell::new(None),
681 load_blocker: DomRefCell::new(None),
682 throttled: Cell::new(false),
683 script_window_proxies: ScriptThread::window_proxies(),
684 current_navigation_was_lazy_loaded: Default::default(),
685 lazy_load_resumption_steps: Default::default(),
686 pending_navigation: Default::default(),
687 already_fired_synchronous_load_event: Default::default(),
688 }
689 }
690
691 pub(crate) fn new(
692 cx: &mut JSContext,
693 local_name: LocalName,
694 prefix: Option<Prefix>,
695 document: &Document,
696 proto: Option<HandleObject>,
697 ) -> DomRoot<HTMLIFrameElement> {
698 Node::reflect_node_with_proto(
699 cx,
700 Box::new(HTMLIFrameElement::new_inherited(
701 local_name, prefix, document,
702 )),
703 document,
704 proto,
705 )
706 }
707
708 #[inline]
709 pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
710 self.pipeline_id.get()
711 }
712
713 #[inline]
714 pub(crate) fn browsing_context_id(&self) -> Option<BrowsingContextId> {
715 self.browsing_context_id.get()
716 }
717
718 #[inline]
719 pub(crate) fn webview_id(&self) -> Option<WebViewId> {
720 self.webview_id.get()
721 }
722
723 #[inline]
724 pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
725 self.sandboxing_flag_set
726 .get()
727 .unwrap_or_else(SandboxingFlagSet::empty)
728 }
729
730 pub(crate) fn set_throttled(&self, throttled: bool) {
731 if self.throttled.get() != throttled {
732 self.throttled.set(throttled);
733 }
734 }
735
736 pub(crate) fn note_pending_navigation(&self) {
740 self.pending_navigation.set(true);
741 }
742
743 pub(crate) fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId, cx: &mut JSContext) {
745 if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
748 return;
749 }
750
751 let should_fire_event = if self.is_initial_blank_document() {
780 !self.pending_navigation.get() &&
784 !self.upcast::<Element>().has_attribute(&local_name!("src"))
785 } else {
786 !self.pending_navigation.get()
789 };
790
791 let should_fire_event =
794 !self.already_fired_synchronous_load_event.replace(false) && should_fire_event;
795 if should_fire_event {
796 self.run_iframe_load_event_steps(cx);
797 } else {
798 debug!(
799 "suppressing load event for iframe, loaded {:?}",
800 loaded_pipeline
801 );
802 }
803 }
804
805 pub(crate) fn run_iframe_load_event_steps(&self, cx: &mut JSContext) {
807 let child_document = self.GetContentDocument();
811
812 if let Some(document) = child_document {
815 if document.mute_iframe_load_flag() {
816 let blocker = &self.load_blocker;
817 LoadBlocker::terminate(blocker, cx);
818 return;
819 }
820 document.set_iframe_load_in_progress(true);
821 }
822
823 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
828
829 let blocker = &self.load_blocker;
830 LoadBlocker::terminate(blocker, cx);
831
832 if let Some(child_document) = self.GetContentDocument() {
834 child_document.set_iframe_load_in_progress(false);
835 }
836 }
837
838 fn parse_sandbox_attribute(&self) {
842 let sandbox_value =
843 self.upcast::<Element>()
844 .with_attribute(&ns!(), &local_name!("sandbox"), |attribute| {
845 let tokens: Vec<_> = attribute
846 .value()
847 .as_tokens()
848 .iter()
849 .map(|atom| atom.to_string().to_ascii_lowercase())
850 .collect();
851 parse_a_sandboxing_directive(&tokens)
852 });
853 self.sandboxing_flag_set.set(sandbox_value);
854 }
855
856 pub(crate) fn destroy_document_and_its_descendants(&self, cx: &mut JSContext) {
858 let Some(pipeline_id) = self.pipeline_id.get() else {
859 return;
860 };
861 if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
863 exited_document.destroy_document_and_its_descendants(cx);
864 }
865 self.destroy_nested_browsing_context();
866 }
867
868 fn destroy_child_navigable(&self, cx: &mut JSContext) {
870 let blocker = &self.load_blocker;
871 LoadBlocker::terminate(blocker, cx);
872
873 let Some(browsing_context_id) = self.browsing_context_id() else {
875 return;
877 };
878 let pipeline_id = self.pipeline_id.get();
881
882 self.destroy_nested_browsing_context();
890
891 let (sender, receiver) =
896 ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
897 let msg = ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, sender);
898 self.owner_window()
899 .as_global_scope()
900 .script_to_constellation_chan()
901 .send(msg)
902 .unwrap();
903 let _exited_pipeline_ids = receiver.recv().unwrap();
904 let Some(pipeline_id) = pipeline_id else {
905 return;
906 };
907 if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
908 exited_document.destroy_document_and_its_descendants(cx);
909 }
910
911 }
926}
927
928impl LayoutDom<'_, HTMLIFrameElement> {
929 #[inline]
930 pub(crate) fn pipeline_id(self) -> Option<PipelineId> {
931 (self.unsafe_get()).pipeline_id.get()
932 }
933
934 #[inline]
935 pub(crate) fn browsing_context_id(self) -> Option<BrowsingContextId> {
936 (self.unsafe_get()).browsing_context_id.get()
937 }
938
939 pub(crate) fn get_width(self) -> LengthOrPercentageOrAuto {
940 self.upcast::<Element>()
941 .get_attr_for_layout(&ns!(), &local_name!("width"))
942 .map(AttrValue::as_dimension)
943 .cloned()
944 .unwrap_or(LengthOrPercentageOrAuto::Auto)
945 }
946
947 pub(crate) fn get_height(self) -> LengthOrPercentageOrAuto {
948 self.upcast::<Element>()
949 .get_attr_for_layout(&ns!(), &local_name!("height"))
950 .map(AttrValue::as_dimension)
951 .cloned()
952 .unwrap_or(LengthOrPercentageOrAuto::Auto)
953 }
954}
955
956impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement {
957 make_url_getter!(Src, "src");
959
960 make_url_setter!(SetSrc, "src");
962
963 fn Srcdoc(&self) -> TrustedHTMLOrString {
965 let element = self.upcast::<Element>();
966 element.get_trusted_html_attribute(&local_name!("srcdoc"))
967 }
968
969 fn SetSrcdoc(&self, cx: &mut JSContext, value: TrustedHTMLOrString) -> Fallible<()> {
971 let element = self.upcast::<Element>();
975 let value = TrustedHTML::get_trusted_type_compliant_string(
976 cx,
977 &element.owner_global(),
978 value,
979 "HTMLIFrameElement srcdoc",
980 )?;
981 element.set_attribute(
983 cx,
984 &local_name!("srcdoc"),
985 AttrValue::String(value.str().to_owned()),
986 );
987 Ok(())
988 }
989
990 fn Sandbox(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
996 self.sandbox.or_init(|| {
997 DOMTokenList::new(
998 cx,
999 self.upcast::<Element>(),
1000 &local_name!("sandbox"),
1001 Some(vec![
1002 Atom::from("allow-downloads"),
1003 Atom::from("allow-forms"),
1004 Atom::from("allow-modals"),
1005 Atom::from("allow-orientation-lock"),
1006 Atom::from("allow-pointer-lock"),
1007 Atom::from("allow-popups"),
1008 Atom::from("allow-popups-to-escape-sandbox"),
1009 Atom::from("allow-presentation"),
1010 Atom::from("allow-same-origin"),
1011 Atom::from("allow-scripts"),
1012 Atom::from("allow-top-navigation"),
1013 Atom::from("allow-top-navigation-by-user-activation"),
1014 Atom::from("allow-top-navigation-to-custom-protocols"),
1015 ]),
1016 )
1017 })
1018 }
1019
1020 fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> {
1022 self.browsing_context_id
1023 .get()
1024 .and_then(|id| self.script_window_proxies.find_window_proxy(id))
1025 }
1026
1027 fn GetContentDocument(&self) -> Option<DomRoot<Document>> {
1029 let pipeline_id = self.pipeline_id.get()?;
1031
1032 let document = ScriptThread::find_document(pipeline_id)?;
1036 if !self
1038 .owner_document()
1039 .origin()
1040 .same_origin_domain(&document.origin())
1041 {
1042 return None;
1043 }
1044 Some(document)
1046 }
1047
1048 fn ReferrerPolicy(&self) -> DOMString {
1050 reflect_referrer_policy_attribute(self.upcast::<Element>())
1051 }
1052
1053 make_setter!(SetReferrerPolicy, "referrerpolicy");
1055
1056 make_bool_getter!(AllowFullscreen, "allowfullscreen");
1058 make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
1060
1061 make_getter!(Width, "width");
1063 make_dimension_setter!(SetWidth, "width");
1065
1066 make_getter!(Height, "height");
1068 make_dimension_setter!(SetHeight, "height");
1070
1071 make_getter!(FrameBorder, "frameborder");
1073 make_setter!(SetFrameBorder, "frameborder");
1075
1076 make_atomic_setter!(SetName, "name");
1080
1081 make_getter!(Name, "name");
1085
1086 make_enumerated_getter!(
1089 Loading,
1090 "loading",
1091 "lazy" | "eager",
1092 missing => "eager",
1095 invalid => "eager"
1096 );
1097
1098 make_setter!(SetLoading, "loading");
1100
1101 make_url_getter!(LongDesc, "longdesc");
1103
1104 make_url_setter!(SetLongDesc, "longdesc");
1106}
1107
1108impl VirtualMethods for HTMLIFrameElement {
1109 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1110 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1111 }
1112
1113 fn attribute_mutated(
1114 &self,
1115 cx: &mut JSContext,
1116 attr: AttrRef<'_>,
1117 mutation: AttributeMutation,
1118 ) {
1119 self.super_type()
1120 .unwrap()
1121 .attribute_mutated(cx, attr, mutation);
1122 match *attr.local_name() {
1123 local_name!("sandbox") if self.browsing_context_id.get().is_some() => {
1134 self.parse_sandbox_attribute();
1135 },
1136 local_name!("srcdoc") => {
1137 if self.upcast::<Node>().is_connected_with_browsing_context() {
1148 debug!("iframe srcdoc modified while in browsing context.");
1149 self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
1150 }
1151 },
1152 local_name!("src") => {
1153 if self.upcast::<Node>().is_connected_with_browsing_context() {
1162 debug!("iframe src set while in browsing context.");
1163 self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
1164 }
1165 },
1166 local_name!("loading") => {
1167 if !mutation.is_removal() && &**attr.value() == "lazy" {
1170 return;
1171 }
1172
1173 let previous_resumption_steps = self
1176 .lazy_load_resumption_steps
1177 .replace(LazyLoadResumptionSteps::None);
1178 match previous_resumption_steps {
1179 LazyLoadResumptionSteps::None => (),
1181 LazyLoadResumptionSteps::SrcDoc => {
1182 self.navigate_to_the_srcdoc_resource(ProcessingMode::NotFirstTime, cx);
1184 },
1185 }
1186 },
1187 _ => {},
1188 }
1189 }
1190
1191 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
1192 match attr.local_name() {
1193 &local_name!("width") | &local_name!("height") => true,
1194 _ => self
1195 .super_type()
1196 .unwrap()
1197 .attribute_affects_presentational_hints(attr),
1198 }
1199 }
1200
1201 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1202 match *name {
1203 local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()),
1204 local_name!("width") => AttrValue::from_dimension(value.into()),
1205 local_name!("height") => AttrValue::from_dimension(value.into()),
1206 _ => self
1207 .super_type()
1208 .unwrap()
1209 .parse_plain_attribute(name, value),
1210 }
1211 }
1212
1213 fn post_connection_steps(&self, cx: &mut JSContext) {
1215 if let Some(s) = self.super_type() {
1216 s.post_connection_steps(cx);
1217 }
1218
1219 if !self.upcast::<Node>().is_connected_with_browsing_context() {
1223 return;
1224 }
1225
1226 debug!("<iframe> running post connection steps");
1227
1228 self.parse_sandbox_attribute();
1231
1232 self.create_nested_browsing_context(cx);
1234
1235 self.process_the_iframe_attributes(ProcessingMode::FirstTime, cx);
1237 }
1238
1239 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1240 if let Some(super_type) = self.super_type() {
1241 super_type.bind_to_tree(cx, context);
1242 }
1243
1244 self.owner_document().iframes_mut().add(self);
1245 }
1246
1247 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1249 if let Some(super_type) = self.super_type() {
1250 super_type.unbind_from_tree(cx, context);
1251 }
1252
1253 self.destroy_child_navigable(cx);
1256
1257 self.owner_document().iframes_mut().remove(self);
1258 }
1259}
1260
1261pub(crate) struct IframeContext<'a> {
1264 element: &'a HTMLIFrameElement,
1266 url: ServoUrl,
1268}
1269
1270impl<'a> IframeContext<'a> {
1271 pub fn new(element: &'a HTMLIFrameElement) -> Self {
1273 Self {
1274 element,
1275 url: element
1276 .shared_attribute_processing_steps_for_iframe_and_frame_elements(
1277 ProcessingMode::NotFirstTime,
1278 )
1279 .expect("Must always have a URL when navigating"),
1280 }
1281 }
1282}
1283
1284impl<'a> ResourceTimingListener for IframeContext<'a> {
1285 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1286 (
1287 InitiatorType::LocalName("iframe".to_string()),
1288 self.url.clone(),
1289 )
1290 }
1291
1292 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1293 self.element.upcast::<Node>().owner_doc().global()
1294 }
1295}
1296
1297fn snapshot_self(iframe: &HTMLIFrameElement) -> TargetSnapshotParams {
1298 let child_navigable = iframe.GetContentWindow();
1299 TargetSnapshotParams {
1300 sandboxing_flags: determine_creation_sandboxing_flags(
1301 child_navigable.as_deref(),
1302 Some(iframe.upcast()),
1303 ),
1304 iframe_element_referrer_policy: determine_iframe_element_referrer_policy(Some(
1305 iframe.upcast(),
1306 )),
1307 }
1308}