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::script_runtime::temp_cx;
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::attr::Attr;
32use crate::dom::bindings::cell::DomRefCell;
33use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
34use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
35use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
36use crate::dom::bindings::error::Fallible;
37use crate::dom::bindings::inheritance::Castable;
38use crate::dom::bindings::refcounted::Trusted;
39use crate::dom::bindings::reflector::DomGlobal;
40use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
41use crate::dom::bindings::str::{DOMString, USVString};
42use crate::dom::document::Document;
43use crate::dom::domtokenlist::DOMTokenList;
44use crate::dom::element::{
45 AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute,
46};
47use crate::dom::eventtarget::EventTarget;
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::html::htmlelement::HTMLElement;
50use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
51use crate::dom::performance::performanceresourcetiming::InitiatorType;
52use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
53use crate::dom::virtualmethods::VirtualMethods;
54use crate::dom::windowproxy::WindowProxy;
55use crate::navigation::{
56 determine_creation_sandboxing_flags, determine_iframe_element_referrer_policy,
57};
58use crate::network_listener::ResourceTimingListener;
59use crate::script_runtime::CanGc;
60use crate::script_thread::{ScriptThread, with_script_thread};
61use crate::script_window_proxies::ScriptWindowProxies;
62
63#[derive(PartialEq)]
64enum PipelineType {
65 InitialAboutBlank,
66 Navigation,
67}
68
69#[derive(Clone, Copy, PartialEq)]
70pub(crate) enum ProcessingMode {
71 FirstTime,
72 NotFirstTime,
73}
74
75#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
77enum LazyLoadResumptionSteps {
78 #[default]
79 None,
80 SrcDoc,
81}
82
83#[dom_struct]
84pub(crate) struct HTMLIFrameElement {
85 htmlelement: HTMLElement,
86 #[no_trace]
87 webview_id: Cell<Option<WebViewId>>,
88 #[no_trace]
89 browsing_context_id: Cell<Option<BrowsingContextId>>,
90 #[no_trace]
91 pipeline_id: Cell<Option<PipelineId>>,
92 #[no_trace]
93 pending_pipeline_id: Cell<Option<PipelineId>>,
94 #[no_trace]
95 about_blank_pipeline_id: Cell<Option<PipelineId>>,
96 sandbox: MutNullableDom<DOMTokenList>,
97 #[no_trace]
98 sandboxing_flag_set: Cell<Option<SandboxingFlagSet>>,
99 load_blocker: DomRefCell<Option<LoadBlocker>>,
100 throttled: Cell<bool>,
101 #[conditional_malloc_size_of]
102 script_window_proxies: Rc<ScriptWindowProxies>,
103 current_navigation_was_lazy_loaded: Cell<bool>,
105 #[no_trace]
107 lazy_load_resumption_steps: Cell<LazyLoadResumptionSteps>,
108 pending_navigation: Cell<bool>,
115 already_fired_synchronous_load_event: Cell<bool>,
119}
120
121impl HTMLIFrameElement {
122 fn shared_attribute_processing_steps_for_iframe_and_frame_elements(
124 &self,
125 _mode: ProcessingMode,
126 ) -> Option<ServoUrl> {
127 let element = self.upcast::<Element>();
128 let url = element
130 .get_attribute(&local_name!("src"))
131 .and_then(|src| {
132 let url = src.value();
133 if url.is_empty() {
134 None
135 } else {
136 self.owner_document().encoding_parse_a_url(&url).ok()
140 }
141 })
142 .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
144 Some(url)
154 }
155
156 pub(crate) fn navigate_or_reload_child_browsing_context(
157 &self,
158 load_data: LoadData,
159 history_handling: NavigationHistoryBehavior,
160 mode: ProcessingMode,
161 target_snapshot_params: TargetSnapshotParams,
162 cx: &mut js::context::JSContext,
163 ) {
164 self.already_fired_synchronous_load_event.set(false);
171
172 self.start_new_pipeline(
173 load_data,
174 PipelineType::Navigation,
175 history_handling,
176 mode,
177 target_snapshot_params,
178 cx,
179 );
180 }
181
182 fn start_new_pipeline(
183 &self,
184 mut load_data: LoadData,
185 pipeline_type: PipelineType,
186 history_handling: NavigationHistoryBehavior,
187 mode: ProcessingMode,
188 target_snapshot_params: TargetSnapshotParams,
189 cx: &mut js::context::JSContext,
190 ) {
191 let document = self.owner_document();
192
193 {
194 let load_blocker = &self.load_blocker;
195 LoadBlocker::terminate(load_blocker, cx);
198
199 *load_blocker.borrow_mut() = Some(LoadBlocker::new(
200 &document,
201 LoadType::Subframe(load_data.url.clone()),
202 ));
203 }
204
205 if load_data.url.scheme() != "javascript" {
206 self.continue_navigation(
207 load_data,
208 pipeline_type,
209 history_handling,
210 target_snapshot_params,
211 );
212 return;
213 }
214
215 let iframe = Trusted::new(self);
219 let doc = Trusted::new(&*document);
220 document
221 .global()
222 .task_manager()
223 .networking_task_source()
224 .queue(task!(navigate_to_javascript: move |cx| {
225 let this = iframe.root();
226 let window_proxy = this.GetContentWindow();
227 if let Some(window_proxy) = window_proxy {
228 if !ScriptThread::navigate_to_javascript_url(
231 cx,
232 &this.owner_global(),
233 &window_proxy.global(),
234 &mut load_data,
235 Some(this.upcast()),
236 Some(mode == ProcessingMode::FirstTime),
237 ) {
238 LoadBlocker::terminate(&this.load_blocker, cx);
239 return;
240 }
241 load_data.about_base_url = doc.root().about_base_url();
242 }
243 this.continue_navigation(load_data, pipeline_type, history_handling, target_snapshot_params);
244 }));
245 }
246
247 fn continue_navigation(
248 &self,
249 load_data: LoadData,
250 pipeline_type: PipelineType,
251 history_handling: NavigationHistoryBehavior,
252 target_snapshot_params: TargetSnapshotParams,
253 ) {
254 let browsing_context_id = match self.browsing_context_id() {
255 None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
256 Some(id) => id,
257 };
258
259 let webview_id = match self.webview_id() {
260 None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
261 Some(id) => id,
262 };
263
264 let window = self.owner_window();
265 let old_pipeline_id = self.pipeline_id();
266 let new_pipeline_id = PipelineId::new();
267 self.pending_pipeline_id.set(Some(new_pipeline_id));
268
269 let load_info = IFrameLoadInfo {
270 parent_pipeline_id: window.pipeline_id(),
271 browsing_context_id,
272 webview_id,
273 new_pipeline_id,
274 is_private: false, inherited_secure_context: load_data.inherited_secure_context,
276 history_handling,
277 target_snapshot_params,
278 };
279
280 let viewport_details = window
281 .get_iframe_viewport_details_if_known(browsing_context_id)
282 .unwrap_or_else(|| ViewportDetails {
283 hidpi_scale_factor: window.device_pixel_ratio(),
284 ..Default::default()
285 });
286
287 match pipeline_type {
288 PipelineType::InitialAboutBlank => {
289 self.about_blank_pipeline_id.set(Some(new_pipeline_id));
290
291 let load_info = IFrameLoadInfoWithData {
292 info: load_info,
293 load_data: load_data.clone(),
294 old_pipeline_id,
295 viewport_details,
296 theme: window.theme(),
297 };
298 window
299 .as_global_scope()
300 .script_to_constellation_chan()
301 .send(ScriptToConstellationMessage::ScriptNewIFrame(load_info))
302 .unwrap();
303
304 let new_pipeline_info = NewPipelineInfo {
305 parent_info: Some(window.pipeline_id()),
306 new_pipeline_id,
307 browsing_context_id,
308 webview_id,
309 opener: None,
310 load_data,
311 viewport_details,
312 user_content_manager_id: None,
313 theme: window.theme(),
314 target_snapshot_params,
315 };
316
317 self.pipeline_id.set(Some(new_pipeline_id));
318 with_script_thread(|script_thread| {
319 script_thread.spawn_pipeline(new_pipeline_info);
320 });
321 },
322 PipelineType::Navigation => {
323 let load_info = IFrameLoadInfoWithData {
324 info: load_info,
325 load_data,
326 old_pipeline_id,
327 viewport_details,
328 theme: window.theme(),
329 };
330 window
331 .as_global_scope()
332 .script_to_constellation_chan()
333 .send(ScriptToConstellationMessage::ScriptLoadedURLInIFrame(
334 load_info,
335 ))
336 .unwrap();
337 },
338 }
339 }
340
341 pub(crate) fn is_initial_blank_document(&self) -> bool {
347 self.pending_pipeline_id.get() == self.about_blank_pipeline_id.get()
348 }
349
350 fn navigate_an_iframe_or_frame(
352 &self,
353 cx: &mut js::context::JSContext,
354 load_data: LoadData,
355 mode: ProcessingMode,
356 ) {
357 let history_handling = if !self
360 .GetContentDocument()
361 .is_some_and(|doc| doc.completely_loaded())
362 {
363 NavigationHistoryBehavior::Replace
364 } else {
365 NavigationHistoryBehavior::Auto
367 };
368 let target_snapshot_params = snapshot_self(self);
376 self.navigate_or_reload_child_browsing_context(
377 load_data,
378 history_handling,
379 mode,
380 target_snapshot_params,
381 cx,
382 );
383 }
384
385 fn will_lazy_load_element_steps(&self) -> bool {
387 if !self.owner_document().scripting_enabled() {
389 return false;
390 }
391 self.Loading() == "lazy"
394 }
395
396 fn navigate_to_the_srcdoc_resource(
398 &self,
399 mode: ProcessingMode,
400 cx: &mut js::context::JSContext,
401 ) {
402 let url = ServoUrl::parse("about:srcdoc").unwrap();
405 let document = self.owner_document();
406 let window = self.owner_window();
407 let pipeline_id = Some(window.pipeline_id());
408 let mut load_data = LoadData::new(
409 LoadOrigin::Script(document.origin().snapshot()),
410 url,
411 Some(document.base_url()),
412 pipeline_id,
413 window.as_global_scope().get_referrer(),
414 document.get_referrer_policy(),
415 Some(window.as_global_scope().is_secure_context()),
416 Some(document.insecure_requests_policy()),
417 document.has_trustworthy_ancestor_or_current_origin(),
418 self.sandboxing_flag_set(),
419 );
420 load_data.destination = Destination::IFrame;
421 load_data.policy_container = Some(window.as_global_scope().policy_container());
422 load_data.srcdoc = String::from(
423 self.upcast::<Element>()
424 .get_string_attribute(&local_name!("srcdoc")),
425 );
426
427 self.navigate_an_iframe_or_frame(cx, load_data, mode);
428 }
429
430 fn mark_navigation_as_lazy_loaded(&self, cx: &mut js::context::JSContext) {
432 self.current_navigation_was_lazy_loaded.set(true);
434 let blocker = &self.load_blocker;
435 LoadBlocker::terminate(blocker, cx);
436 }
437
438 fn process_the_iframe_attributes(&self, mode: ProcessingMode, cx: &mut js::context::JSContext) {
440 let element = self.upcast::<Element>();
441
442 if element.has_attribute(&local_name!("srcdoc")) {
446 self.current_navigation_was_lazy_loaded.set(false);
448 if self.will_lazy_load_element_steps() {
450 self.lazy_load_resumption_steps
453 .set(LazyLoadResumptionSteps::SrcDoc);
454 self.mark_navigation_as_lazy_loaded(cx);
456 return;
460 }
461 self.navigate_to_the_srcdoc_resource(mode, cx);
464 return;
465 }
466
467 let window = self.owner_window();
468
469 if mode == ProcessingMode::FirstTime {
474 if let Some(window) = self.GetContentWindow() {
475 window.set_name(
476 element
477 .get_name()
478 .map_or(DOMString::from(""), |n| DOMString::from(&*n)),
479 );
480 }
481 }
482
483 let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements(mode)
486 else {
487 return;
489 };
490
491 if url.matches_about_blank() && mode == ProcessingMode::FirstTime {
493 self.already_fired_synchronous_load_event.set(true);
495 self.run_iframe_load_event_steps(cx);
497 return;
499 }
500
501 let document = self.owner_document();
504 let referrer_policy_token = self.ReferrerPolicy();
505
506 let referrer_policy = match ReferrerPolicy::from(&*referrer_policy_token.str()) {
510 ReferrerPolicy::EmptyString => document.get_referrer_policy(),
511 policy => policy,
512 };
513
514 let mut ancestor = window.GetParent();
523 while let Some(a) = ancestor {
524 if let Some(ancestor_url) = a.document().map(|d| d.url()) {
525 if ancestor_url.scheme() == url.scheme() &&
526 ancestor_url.username() == url.username() &&
527 ancestor_url.password() == url.password() &&
528 ancestor_url.host() == url.host() &&
529 ancestor_url.port() == url.port() &&
530 ancestor_url.path() == url.path() &&
531 ancestor_url.query() == url.query()
532 {
533 return;
534 }
535 }
536 ancestor = a.parent().map(DomRoot::from_ref);
537 }
538
539 let (creator_pipeline_id, about_base_url) = if url.matches_about_blank() {
540 (Some(window.pipeline_id()), Some(document.base_url()))
541 } else {
542 (None, document.about_base_url())
543 };
544
545 let propagate_encoding_to_child_document = url.origin().same_origin(window.origin());
546 let mut load_data = LoadData::new(
547 LoadOrigin::Script(document.origin().snapshot()),
548 url,
549 about_base_url,
550 creator_pipeline_id,
551 window.as_global_scope().get_referrer(),
552 referrer_policy,
553 Some(window.as_global_scope().is_secure_context()),
554 Some(document.insecure_requests_policy()),
555 document.has_trustworthy_ancestor_or_current_origin(),
556 self.sandboxing_flag_set(),
557 );
558 load_data.destination = Destination::IFrame;
559 load_data.policy_container = Some(window.as_global_scope().policy_container());
560 if propagate_encoding_to_child_document {
561 load_data.container_document_encoding = Some(document.encoding());
562 }
563
564 let pipeline_id = self.pipeline_id();
565 let is_about_blank =
568 pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
569
570 let history_handling = if is_about_blank {
571 NavigationHistoryBehavior::Replace
572 } else {
573 NavigationHistoryBehavior::Push
574 };
575
576 let target_snapshot_params = snapshot_self(self);
577 self.navigate_or_reload_child_browsing_context(
578 load_data,
579 history_handling,
580 mode,
581 target_snapshot_params,
582 cx,
583 );
584 }
585
586 fn create_nested_browsing_context(&self, cx: &mut js::context::JSContext) {
594 let url = ServoUrl::parse("about:blank").unwrap();
595 let document = self.owner_document();
596 let window = self.owner_window();
597 let pipeline_id = Some(window.pipeline_id());
598 let mut load_data = LoadData::new(
599 LoadOrigin::Script(document.origin().snapshot()),
600 url,
601 Some(document.base_url()),
602 pipeline_id,
603 window.as_global_scope().get_referrer(),
604 document.get_referrer_policy(),
605 Some(window.as_global_scope().is_secure_context()),
606 Some(document.insecure_requests_policy()),
607 document.has_trustworthy_ancestor_or_current_origin(),
608 self.sandboxing_flag_set(),
609 );
610 load_data.destination = Destination::IFrame;
611 load_data.policy_container = Some(window.as_global_scope().policy_container());
612
613 let browsing_context_id = BrowsingContextId::new();
614 let webview_id = window.window_proxy().webview_id();
615 self.pipeline_id.set(None);
616 self.pending_pipeline_id.set(None);
617 self.webview_id.set(Some(webview_id));
618 self.browsing_context_id.set(Some(browsing_context_id));
619 self.start_new_pipeline(
620 load_data,
621 PipelineType::InitialAboutBlank,
622 NavigationHistoryBehavior::Push,
623 ProcessingMode::FirstTime,
624 snapshot_self(self),
625 cx,
626 );
627 }
628
629 fn destroy_nested_browsing_context(&self) {
630 self.pipeline_id.set(None);
631 self.pending_pipeline_id.set(None);
632 self.about_blank_pipeline_id.set(None);
633 self.webview_id.set(None);
634 if let Some(browsing_context_id) = self.browsing_context_id.take() {
635 self.script_window_proxies.remove(browsing_context_id)
636 }
637 }
638
639 pub(crate) fn update_pipeline_id(
640 &self,
641 new_pipeline_id: PipelineId,
642 reason: UpdatePipelineIdReason,
643 cx: &mut js::context::JSContext,
644 ) {
645 if !self.is_initial_blank_document() {
650 self.pending_navigation.set(false);
651 }
652 if self.pending_pipeline_id.get() != Some(new_pipeline_id) &&
653 reason == UpdatePipelineIdReason::Navigation
654 {
655 return;
656 }
657
658 self.pipeline_id.set(Some(new_pipeline_id));
659
660 if reason == UpdatePipelineIdReason::Traversal {
663 let blocker = &self.load_blocker;
664 LoadBlocker::terminate(blocker, cx);
665 }
666
667 self.upcast::<Node>().dirty(NodeDamage::Other);
668 }
669
670 fn new_inherited(
671 local_name: LocalName,
672 prefix: Option<Prefix>,
673 document: &Document,
674 ) -> HTMLIFrameElement {
675 HTMLIFrameElement {
676 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
677 browsing_context_id: Cell::new(None),
678 webview_id: Cell::new(None),
679 pipeline_id: Cell::new(None),
680 pending_pipeline_id: Cell::new(None),
681 about_blank_pipeline_id: Cell::new(None),
682 sandbox: Default::default(),
683 sandboxing_flag_set: Cell::new(None),
684 load_blocker: DomRefCell::new(None),
685 throttled: Cell::new(false),
686 script_window_proxies: ScriptThread::window_proxies(),
687 current_navigation_was_lazy_loaded: Default::default(),
688 lazy_load_resumption_steps: Default::default(),
689 pending_navigation: Default::default(),
690 already_fired_synchronous_load_event: Default::default(),
691 }
692 }
693
694 pub(crate) fn new(
695 cx: &mut js::context::JSContext,
696 local_name: LocalName,
697 prefix: Option<Prefix>,
698 document: &Document,
699 proto: Option<HandleObject>,
700 ) -> DomRoot<HTMLIFrameElement> {
701 Node::reflect_node_with_proto(
702 cx,
703 Box::new(HTMLIFrameElement::new_inherited(
704 local_name, prefix, document,
705 )),
706 document,
707 proto,
708 )
709 }
710
711 #[inline]
712 pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
713 self.pipeline_id.get()
714 }
715
716 #[inline]
717 pub(crate) fn browsing_context_id(&self) -> Option<BrowsingContextId> {
718 self.browsing_context_id.get()
719 }
720
721 #[inline]
722 pub(crate) fn webview_id(&self) -> Option<WebViewId> {
723 self.webview_id.get()
724 }
725
726 #[inline]
727 pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
728 self.sandboxing_flag_set
729 .get()
730 .unwrap_or_else(SandboxingFlagSet::empty)
731 }
732
733 pub(crate) fn set_throttled(&self, throttled: bool) {
734 if self.throttled.get() != throttled {
735 self.throttled.set(throttled);
736 }
737 }
738
739 pub(crate) fn note_pending_navigation(&self) {
743 self.pending_navigation.set(true);
744 }
745
746 pub(crate) fn iframe_load_event_steps(
748 &self,
749 loaded_pipeline: PipelineId,
750 cx: &mut js::context::JSContext,
751 ) {
752 if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
755 return;
756 }
757
758 let should_fire_event = if self.is_initial_blank_document() {
787 !self.pending_navigation.get() &&
791 !self.upcast::<Element>().has_attribute(&local_name!("src"))
792 } else {
793 !self.pending_navigation.get()
796 };
797
798 let should_fire_event =
801 !self.already_fired_synchronous_load_event.replace(false) && should_fire_event;
802 if should_fire_event {
803 self.run_iframe_load_event_steps(cx);
804 } else {
805 debug!(
806 "suppressing load event for iframe, loaded {:?}",
807 loaded_pipeline
808 );
809 }
810 }
811
812 pub(crate) fn run_iframe_load_event_steps(&self, cx: &mut JSContext) {
814 self.upcast::<EventTarget>()
822 .fire_event(atom!("load"), CanGc::from_cx(cx));
823
824 let blocker = &self.load_blocker;
825 LoadBlocker::terminate(blocker, cx);
826
827 }
829
830 fn parse_sandbox_attribute(&self) {
834 let attribute = self
835 .upcast::<Element>()
836 .get_attribute(&local_name!("sandbox"));
837 self.sandboxing_flag_set
838 .set(attribute.map(|attribute_value| {
839 let tokens: Vec<_> = attribute_value
840 .value()
841 .as_tokens()
842 .iter()
843 .map(|atom| atom.to_string().to_ascii_lowercase())
844 .collect();
845 parse_a_sandboxing_directive(&tokens)
846 }));
847 }
848
849 pub(crate) fn destroy_document_and_its_descendants(&self, cx: &mut js::context::JSContext) {
851 let Some(pipeline_id) = self.pipeline_id.get() else {
852 return;
853 };
854 if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
856 exited_document.destroy_document_and_its_descendants(cx);
857 }
858 self.destroy_nested_browsing_context();
859 }
860
861 fn destroy_child_navigable(&self, cx: &mut js::context::JSContext) {
863 let blocker = &self.load_blocker;
864 LoadBlocker::terminate(blocker, cx);
865
866 let Some(browsing_context_id) = self.browsing_context_id() else {
868 return;
870 };
871 let pipeline_id = self.pipeline_id.get();
874
875 self.destroy_nested_browsing_context();
883
884 let (sender, receiver) =
889 ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
890 let msg = ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, sender);
891 self.owner_window()
892 .as_global_scope()
893 .script_to_constellation_chan()
894 .send(msg)
895 .unwrap();
896 let _exited_pipeline_ids = receiver.recv().unwrap();
897 let Some(pipeline_id) = pipeline_id else {
898 return;
899 };
900 if let Some(exited_document) = ScriptThread::find_document(pipeline_id) {
901 exited_document.destroy_document_and_its_descendants(cx);
902 }
903
904 }
919}
920
921pub(crate) trait HTMLIFrameElementLayoutMethods {
922 fn pipeline_id(self) -> Option<PipelineId>;
923 fn browsing_context_id(self) -> Option<BrowsingContextId>;
924 fn get_width(self) -> LengthOrPercentageOrAuto;
925 fn get_height(self) -> LengthOrPercentageOrAuto;
926}
927
928impl HTMLIFrameElementLayoutMethods for LayoutDom<'_, HTMLIFrameElement> {
929 #[inline]
930 fn pipeline_id(self) -> Option<PipelineId> {
931 (self.unsafe_get()).pipeline_id.get()
932 }
933
934 #[inline]
935 fn browsing_context_id(self) -> Option<BrowsingContextId> {
936 (self.unsafe_get()).browsing_context_id.get()
937 }
938
939 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 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(
971 &self,
972 cx: &mut js::context::JSContext,
973 value: TrustedHTMLOrString,
974 ) -> Fallible<()> {
975 let element = self.upcast::<Element>();
979 let value = TrustedHTML::get_trusted_type_compliant_string(
980 cx,
981 &element.owner_global(),
982 value,
983 "HTMLIFrameElement srcdoc",
984 )?;
985 element.set_attribute(
987 &local_name!("srcdoc"),
988 AttrValue::String(value.str().to_owned()),
989 CanGc::from_cx(cx),
990 );
991 Ok(())
992 }
993
994 fn Sandbox(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMTokenList> {
1000 self.sandbox.or_init(|| {
1001 DOMTokenList::new(
1002 self.upcast::<Element>(),
1003 &local_name!("sandbox"),
1004 Some(vec![
1005 Atom::from("allow-downloads"),
1006 Atom::from("allow-forms"),
1007 Atom::from("allow-modals"),
1008 Atom::from("allow-orientation-lock"),
1009 Atom::from("allow-pointer-lock"),
1010 Atom::from("allow-popups"),
1011 Atom::from("allow-popups-to-escape-sandbox"),
1012 Atom::from("allow-presentation"),
1013 Atom::from("allow-same-origin"),
1014 Atom::from("allow-scripts"),
1015 Atom::from("allow-top-navigation"),
1016 Atom::from("allow-top-navigation-by-user-activation"),
1017 Atom::from("allow-top-navigation-to-custom-protocols"),
1018 ]),
1019 CanGc::from_cx(cx),
1020 )
1021 })
1022 }
1023
1024 fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> {
1026 self.browsing_context_id
1027 .get()
1028 .and_then(|id| self.script_window_proxies.find_window_proxy(id))
1029 }
1030
1031 fn GetContentDocument(&self) -> Option<DomRoot<Document>> {
1033 let pipeline_id = self.pipeline_id.get()?;
1035
1036 let document = ScriptThread::find_document(pipeline_id)?;
1040 if !self
1042 .owner_document()
1043 .origin()
1044 .same_origin_domain(&document.origin())
1045 {
1046 return None;
1047 }
1048 Some(document)
1050 }
1051
1052 fn ReferrerPolicy(&self) -> DOMString {
1054 reflect_referrer_policy_attribute(self.upcast::<Element>())
1055 }
1056
1057 make_setter!(SetReferrerPolicy, "referrerpolicy");
1059
1060 make_bool_getter!(AllowFullscreen, "allowfullscreen");
1062 make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
1064
1065 make_getter!(Width, "width");
1067 make_dimension_setter!(SetWidth, "width");
1069
1070 make_getter!(Height, "height");
1072 make_dimension_setter!(SetHeight, "height");
1074
1075 make_getter!(FrameBorder, "frameborder");
1077 make_setter!(SetFrameBorder, "frameborder");
1079
1080 make_atomic_setter!(SetName, "name");
1084
1085 make_getter!(Name, "name");
1089
1090 make_enumerated_getter!(
1093 Loading,
1094 "loading",
1095 "lazy" | "eager",
1096 missing => "eager",
1099 invalid => "eager"
1100 );
1101
1102 make_setter!(SetLoading, "loading");
1104
1105 make_url_getter!(LongDesc, "longdesc");
1107
1108 make_url_setter!(SetLongDesc, "longdesc");
1110}
1111
1112impl VirtualMethods for HTMLIFrameElement {
1113 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1114 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1115 }
1116
1117 fn attribute_mutated(
1118 &self,
1119 cx: &mut js::context::JSContext,
1120 attr: &Attr,
1121 mutation: AttributeMutation,
1122 ) {
1123 self.super_type()
1124 .unwrap()
1125 .attribute_mutated(cx, attr, mutation);
1126 match *attr.local_name() {
1127 local_name!("sandbox") if self.browsing_context_id.get().is_some() => {
1138 self.parse_sandbox_attribute();
1139 },
1140 local_name!("srcdoc") => {
1141 if self.upcast::<Node>().is_connected_with_browsing_context() {
1152 debug!("iframe srcdoc modified while in browsing context.");
1153 self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
1154 }
1155 },
1156 local_name!("src") => {
1157 if self.upcast::<Node>().is_connected_with_browsing_context() {
1166 debug!("iframe src set while in browsing context.");
1167 self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, cx);
1168 }
1169 },
1170 local_name!("loading") => {
1171 if !mutation.is_removal() && &**attr.value() == "lazy" {
1174 return;
1175 }
1176
1177 let previous_resumption_steps = self
1180 .lazy_load_resumption_steps
1181 .replace(LazyLoadResumptionSteps::None);
1182 match previous_resumption_steps {
1183 LazyLoadResumptionSteps::None => (),
1185 LazyLoadResumptionSteps::SrcDoc => {
1186 self.navigate_to_the_srcdoc_resource(ProcessingMode::NotFirstTime, cx);
1188 },
1189 }
1190 },
1191 _ => {},
1192 }
1193 }
1194
1195 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
1196 match attr.local_name() {
1197 &local_name!("width") | &local_name!("height") => true,
1198 _ => self
1199 .super_type()
1200 .unwrap()
1201 .attribute_affects_presentational_hints(attr),
1202 }
1203 }
1204
1205 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
1206 match *name {
1207 local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()),
1208 local_name!("width") => AttrValue::from_dimension(value.into()),
1209 local_name!("height") => AttrValue::from_dimension(value.into()),
1210 _ => self
1211 .super_type()
1212 .unwrap()
1213 .parse_plain_attribute(name, value),
1214 }
1215 }
1216
1217 fn post_connection_steps(&self, cx: &mut JSContext) {
1219 if let Some(s) = self.super_type() {
1220 s.post_connection_steps(cx);
1221 }
1222
1223 if !self.upcast::<Node>().is_connected_with_browsing_context() {
1227 return;
1228 }
1229
1230 debug!("<iframe> running post connection steps");
1231
1232 self.create_nested_browsing_context(cx);
1234
1235 self.parse_sandbox_attribute();
1238
1239 self.process_the_iframe_attributes(ProcessingMode::FirstTime, cx);
1241 }
1242
1243 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
1244 if let Some(s) = self.super_type() {
1245 s.bind_to_tree(cx, context);
1246 }
1247 self.owner_document().invalidate_iframes_collection();
1248 }
1249
1250 #[expect(unsafe_code)]
1252 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
1253 self.super_type().unwrap().unbind_from_tree(context, can_gc);
1254
1255 let mut cx = unsafe { temp_cx() };
1257
1258 self.destroy_child_navigable(&mut cx);
1260
1261 self.owner_document().invalidate_iframes_collection();
1262 }
1263}
1264
1265pub(crate) struct IframeContext<'a> {
1268 element: &'a HTMLIFrameElement,
1270 url: ServoUrl,
1272}
1273
1274impl<'a> IframeContext<'a> {
1275 pub fn new(element: &'a HTMLIFrameElement) -> Self {
1277 Self {
1278 element,
1279 url: element
1280 .shared_attribute_processing_steps_for_iframe_and_frame_elements(
1281 ProcessingMode::NotFirstTime,
1282 )
1283 .expect("Must always have a URL when navigating"),
1284 }
1285 }
1286}
1287
1288impl<'a> ResourceTimingListener for IframeContext<'a> {
1289 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1290 (
1291 InitiatorType::LocalName("iframe".to_string()),
1292 self.url.clone(),
1293 )
1294 }
1295
1296 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1297 self.element.upcast::<Node>().owner_doc().global()
1298 }
1299}
1300
1301fn snapshot_self(iframe: &HTMLIFrameElement) -> TargetSnapshotParams {
1302 let child_navigable = iframe.GetContentWindow();
1303 TargetSnapshotParams {
1304 sandboxing_flags: determine_creation_sandboxing_flags(
1305 child_navigable.as_deref(),
1306 Some(iframe.upcast()),
1307 ),
1308 iframe_element_referrer_policy: determine_iframe_element_referrer_policy(Some(
1309 iframe.upcast(),
1310 )),
1311 }
1312}