Skip to main content

script/
navigation.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
5//! The listener that encapsulates all state for an in-progress document request.
6//! Any redirects that are encountered are followed. Whenever a non-redirect
7//! response is received, it is forwarded to the appropriate script thread.
8
9use std::cell::Cell;
10use std::rc::Rc;
11
12use content_security_policy::sandboxing_directive::SandboxingFlagSet;
13use crossbeam_channel::Sender;
14use embedder_traits::user_contents::UserContentManagerId;
15use embedder_traits::{ViewportDetails, WebDriverLoadStatus};
16use http::header;
17use js::context::JSContext;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::fetch::fetch_controller::FetchController;
20use net_traits::request::{
21    CredentialsMode, InsecureRequestsPolicy, Origin, PreloadedResources, RedirectMode,
22    RequestBuilder, RequestClient, RequestMode,
23};
24use net_traits::response::ResponseInit;
25use net_traits::{
26    BoxedFetchCallback, CoreResourceThread, DOCUMENT_ACCEPT_HEADER_VALUE, FetchResponseMsg,
27    Metadata, ReferrerPolicy, fetch_async, set_default_accept_language,
28};
29use script_bindings::inheritance::Castable;
30use script_traits::{DocumentActivity, NewPipelineInfo, WebViewState};
31use servo_base::cross_process_instant::CrossProcessInstant;
32use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
33use servo_constellation_traits::{
34    LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationMessage,
35    TargetSnapshotParams,
36};
37use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
38use url::Position;
39
40use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
41use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
42use crate::dom::bindings::refcounted::Trusted;
43use crate::dom::document::AbortReason;
44use crate::dom::element::Element;
45use crate::dom::html::htmliframeelement::HTMLIFrameElement;
46use crate::dom::node::node::NodeTraits;
47use crate::dom::window::Window;
48use crate::dom::windowproxy::WindowProxy;
49use crate::event_loop::script_thread::ScriptThread;
50use crate::messaging::MainThreadScriptMsg;
51
52#[derive(Clone)]
53pub struct NavigationListener {
54    request_builder: RequestBuilder,
55    main_thread_sender: Sender<MainThreadScriptMsg>,
56    // Whether or not results are sent to the main thread. After a redirect results are no longer sent,
57    // as the main thread has already started a new request.
58    send_results_to_main_thread: Cell<bool>,
59}
60
61impl NavigationListener {
62    pub(crate) fn into_callback(self) -> BoxedFetchCallback {
63        Box::new(move |response_msg| self.notify_fetch(response_msg))
64    }
65
66    pub fn new(
67        request_builder: RequestBuilder,
68        main_thread_sender: Sender<MainThreadScriptMsg>,
69    ) -> NavigationListener {
70        NavigationListener {
71            request_builder,
72            main_thread_sender,
73            send_results_to_main_thread: Cell::new(true),
74        }
75    }
76
77    pub fn initiate_fetch(
78        self,
79        core_resource_thread: &CoreResourceThread,
80        response_init: Option<ResponseInit>,
81    ) {
82        fetch_async(
83            core_resource_thread,
84            self.request_builder.clone(),
85            response_init,
86            self.into_callback(),
87        );
88    }
89
90    fn notify_fetch(&self, message: FetchResponseMsg) {
91        // If we've already asked the main thread to redirect the response, then stop sending results
92        // for this fetch. The main thread has already replaced it.
93        if !self.send_results_to_main_thread.get() {
94            return;
95        }
96
97        // If this is a redirect, don't send any more message after this one.
98        if Self::http_redirect_metadata(&message).is_some() {
99            self.send_results_to_main_thread.set(false);
100        }
101
102        let pipeline_id = self
103            .request_builder
104            .pipeline_id
105            .expect("Navigation should always have an associated Pipeline");
106        let result = self
107            .main_thread_sender
108            .send(MainThreadScriptMsg::NavigationResponse {
109                pipeline_id,
110                message: Box::new(message),
111            });
112
113        if let Err(error) = result {
114            warn!(
115                "Failed to send network message to pipeline {:?}: {error:?}",
116                pipeline_id
117            );
118        }
119    }
120
121    pub(crate) fn http_redirect_metadata(message: &FetchResponseMsg) -> Option<&Metadata> {
122        let FetchResponseMsg::ProcessResponse(_, Ok(metadata)) = message else {
123            return None;
124        };
125
126        // Don't allow redirects for non HTTP(S) URLs.
127        let metadata = metadata.metadata();
128        if !matches!(
129            metadata.location_url,
130            Some(Ok(ref location_url)) if matches!(location_url.scheme(), "http" | "https")
131        ) {
132            return None;
133        }
134
135        Some(metadata)
136    }
137}
138
139/// A document load that is in the process of fetching the requested resource. Contains
140/// data that will need to be present when the document and frame tree entry are created,
141/// but is only easily available at initiation of the load and on a push basis (so some
142/// data will be updated according to future resize events, viewport changes, etc.)
143#[derive(JSTraceable)]
144pub(crate) struct InProgressLoad {
145    /// The pipeline which requested this load.
146    #[no_trace]
147    pub(crate) pipeline_id: PipelineId,
148    /// The browsing context being loaded into.
149    #[no_trace]
150    pub(crate) browsing_context_id: BrowsingContextId,
151    /// The shared state for the `WebView` of this [`InProgressLoad`].
152    #[no_trace]
153    pub(crate) webview_state: Rc<WebViewState>,
154    /// The parent pipeline and frame type associated with this load, if any.
155    #[no_trace]
156    pub(crate) parent_info: Option<PipelineId>,
157    /// The opener, if this is an auxiliary.
158    #[no_trace]
159    pub(crate) opener: Option<BrowsingContextId>,
160    /// The current window size associated with this pipeline.
161    #[no_trace]
162    pub(crate) viewport_details: ViewportDetails,
163    /// The activity level of the document (inactive, active or fully active).
164    #[no_trace]
165    pub(crate) activity: DocumentActivity,
166    /// Window is throttled, running timers at a heavily limited rate.
167    pub(crate) throttled: bool,
168    /// Timestamp reporting the time when the browser started this load.
169    #[no_trace]
170    pub(crate) navigation_start: CrossProcessInstant,
171    /// For cancelling the fetch
172    #[no_trace]
173    pub(crate) controller: FetchController,
174    /// The [`LoadData`] associated with this load.
175    #[no_trace]
176    pub(crate) load_data: LoadData,
177    /// A list of URL to keep track of all the redirects that have happened during
178    /// this load.
179    #[no_trace]
180    pub(crate) url_list: Vec<UrlWithBlobClaim>,
181    #[no_trace]
182    /// The [`UserContentManagerId`] associated with this load's `WebView`.
183    pub(crate) user_content_manager_id: Option<UserContentManagerId>,
184    /// The [`TargetSnapshotParams`] to use when creating this document.
185    #[no_trace]
186    pub(crate) target_snapshot_params: TargetSnapshotParams,
187    /// Name of this iframe, if any
188    pub(crate) frame_name: Option<String>,
189}
190
191impl InProgressLoad {
192    /// Create a new InProgressLoad object.
193    pub(crate) fn new(
194        new_pipeline_info: NewPipelineInfo,
195        webview_state: Rc<WebViewState>,
196    ) -> InProgressLoad {
197        let url = new_pipeline_info.load_data.url.clone();
198
199        InProgressLoad {
200            pipeline_id: new_pipeline_info.new_pipeline_id,
201            browsing_context_id: new_pipeline_info.browsing_context_id,
202            webview_state,
203            parent_info: new_pipeline_info.parent_info,
204            opener: new_pipeline_info.opener,
205            viewport_details: new_pipeline_info.viewport_details,
206            activity: DocumentActivity::FullyActive,
207            throttled: false,
208            navigation_start: CrossProcessInstant::now(),
209            controller: Default::default(),
210            load_data: new_pipeline_info.load_data,
211            url_list: vec![UrlWithBlobClaim::from_url_without_having_claimed_blob(url)],
212            user_content_manager_id: new_pipeline_info.user_content_manager_id,
213            target_snapshot_params: new_pipeline_info.target_snapshot_params,
214            frame_name: new_pipeline_info.frame_name,
215        }
216    }
217
218    pub(crate) fn webview_id(&self) -> WebViewId {
219        self.webview_state.id
220    }
221
222    pub(crate) fn request_builder(&mut self) -> RequestBuilder {
223        let client_origin = match self.load_data.load_origin {
224            LoadOrigin::Script(ref initiator_origin) => initiator_origin.immutable().clone(),
225            _ => ImmutableOrigin::new_opaque(),
226        };
227
228        let id = self.pipeline_id;
229        let webview_id = self.webview_state.id;
230
231        let insecure_requests_policy = self
232            .load_data
233            .inherited_insecure_requests_policy
234            .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade);
235
236        let request_client = RequestClient {
237            preloaded_resources: PreloadedResources::default(),
238            policy_container: self.load_data.policy_container.clone().unwrap_or_default(),
239            origin: Origin::Origin(client_origin),
240            is_nested_browsing_context: self.parent_info.is_some(),
241            insecure_requests_policy,
242            has_trustworthy_ancestor_origin: self.load_data.has_trustworthy_ancestor_origin,
243            network_partition_key: None,
244        };
245
246        let mut request_builder = RequestBuilder::new(
247            Some(webview_id),
248            UrlWithBlobClaim::from_url_without_having_claimed_blob(self.load_data.url.clone()),
249            self.load_data.referrer.clone(),
250        )
251        .method(self.load_data.method.clone())
252        .destination(self.load_data.destination)
253        .mode(RequestMode::Navigate)
254        .credentials_mode(CredentialsMode::Include)
255        .use_url_credentials(true)
256        .pipeline_id(Some(id))
257        .referrer_policy(self.load_data.referrer_policy)
258        .policy_container(self.load_data.policy_container.clone().unwrap_or_default())
259        .headers(self.load_data.headers.clone())
260        .body(self.load_data.data.clone())
261        .redirect_mode(RedirectMode::Manual)
262        .crash(self.load_data.crash.clone())
263        .client(request_client)
264        .url_list(self.url_list.clone());
265
266        request_builder.reload_navigation = self.load_data.reload_navigation;
267        request_builder.history_navigation = self.load_data.history_navigation;
268
269        if !request_builder.headers.contains_key(header::ACCEPT) {
270            request_builder
271                .headers
272                .insert(header::ACCEPT, DOCUMENT_ACCEPT_HEADER_VALUE);
273        }
274        set_default_accept_language(&mut request_builder.headers);
275
276        request_builder
277    }
278}
279
280/// <https://html.spec.whatwg.org/multipage/#determining-the-origin>
281pub(crate) fn determine_the_origin(
282    url: Option<&ServoUrl>,
283    sandbox_flags: SandboxingFlagSet,
284    source_origin: Option<MutableOrigin>,
285) -> MutableOrigin {
286    // Step 1. If sandboxFlags has its sandboxed origin browsing context flag set, then return a new opaque origin.
287    let is_sandboxed =
288        sandbox_flags.contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG);
289    if is_sandboxed {
290        return MutableOrigin::new(ImmutableOrigin::new_opaque());
291    }
292
293    // Step 2. If url is null, then return a new opaque origin.
294    let Some(url) = url else {
295        return MutableOrigin::new(ImmutableOrigin::new_opaque());
296    };
297
298    // Step 3. If url is about:srcdoc, then:
299    if url.as_str() == "about:srcdoc" {
300        // Step 3.1 Assert: sourceOrigin is non-null.
301        let source_origin =
302            source_origin.expect("Can't have a null source origin for about:srcdoc");
303        // Step 3.2 Return sourceOrigin
304        return source_origin;
305    }
306
307    // Step 4. If url matches about:blank and sourceOrigin is non-null, then return sourceOrigin.
308    if url.as_str() == "about:blank" &&
309        let Some(source_origin) = source_origin
310    {
311        return source_origin;
312    }
313
314    // Step 5. Return url's origin.
315    MutableOrigin::new(url.origin())
316}
317
318/// <https://html.spec.whatwg.org/multipage/#navigate-fragid>
319fn navigate_to_fragment(
320    cx: &mut JSContext,
321    window: &Window,
322    url: &ServoUrl,
323    history_handling: NavigationHistoryBehavior,
324) {
325    let doc = window.Document();
326    // Step 1. Let navigation be navigable's active window's navigation API.
327    // TODO
328    // Step 2. Let destinationNavigationAPIState be navigable's active session history entry's navigation API state.
329    // TODO
330    // Step 3. If navigationAPIState is not null, then set destinationNavigationAPIState to navigationAPIState.
331    // TODO
332
333    // Step 4. Let continue be the result of firing a push/replace/reload navigate event
334    // at navigation with navigationType set to historyHandling, isSameDocument set to true,
335    // userInvolvement set to userInvolvement, sourceElement set to sourceElement,
336    // destinationURL set to url, and navigationAPIState set to destinationNavigationAPIState.
337    // TODO
338    // Step 5. If continue is false, then return.
339    // TODO
340
341    // Step 6. Let historyEntry be a new session history entry, with
342    // Step 7. Let entryToReplace be navigable's active session history entry if historyHandling is "replace", otherwise null.
343    // Step 8. Let history be navigable's active document's history object.
344    // Step 9. Let scriptHistoryIndex be history's index.
345    // Step 10. Let scriptHistoryLength be history's length.
346    // Step 11. If historyHandling is "push", then:
347    // Step 13. Set navigable's active session history entry to historyEntry.
348    window.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment(
349        url.clone(),
350        history_handling,
351    ));
352    // Step 12. Set navigable's active document's URL to url.
353    let old_url = doc.url();
354    doc.set_url(url.clone());
355    // Step 14. Update document for history step application given navigable's active document,
356    // historyEntry, true, scriptHistoryIndex, scriptHistoryLength, and historyHandling.
357    doc.update_document_for_history_step_application(&old_url, url);
358    // Step 15. Scroll to the fragment given navigable's active document.
359    let Some(fragment) = url.fragment() else {
360        unreachable!("Must always have a fragment");
361    };
362    doc.scroll_to_the_fragment(cx, fragment);
363    // Step 16. Let traversable be navigable's traversable navigable.
364    // TODO
365    // Step 17. Append the following session history synchronous navigation steps involving navigable to traversable:
366    // TODO
367}
368
369/// <https://html.spec.whatwg.org/multipage/#navigate>
370pub(crate) fn navigate(
371    cx: &mut JSContext,
372    window: &Window,
373    history_handling: NavigationHistoryBehavior,
374    force_reload: bool,
375    mut load_data: LoadData,
376) {
377    let document = window.Document();
378
379    // <https://html.spec.whatwg.org/multipage/#process-a-navigate-fetch>
380    if force_reload {
381        // Step 7. If entry's document state's reload pending is true, then set request's reload-navigation flag.
382        load_data.reload_navigation = true;
383    }
384
385    // Step 3. Let initiatorOriginSnapshot be sourceDocument's origin.
386    let initiator_origin_snapshot = &load_data.load_origin;
387
388    // TODO: Important re security. See https://github.com/servo/servo/issues/23373
389    // Step 5. check that the source browsing-context is "allowed to navigate" this window.
390
391    // Step 4 and 5
392    let pipeline_id = window.pipeline_id();
393    let window_proxy = window.window_proxy();
394    if let Some(active) = window_proxy.currently_active() &&
395        pipeline_id == active &&
396        document.is_prompting_or_unloading()
397    {
398        return;
399    }
400
401    // Step 12. If historyHandling is "auto", then:
402    let history_handling = if history_handling == NavigationHistoryBehavior::Auto {
403        // Step 12.1. If url equals navigable's active document's URL, and
404        // initiatorOriginSnapshot is same origin with targetNavigable's active document's
405        // origin, then set historyHandling to "replace".
406        //
407        // Note: `targetNavigable` is not actually defined in the spec, "active document" is
408        // assumed to be the correct reference based on WPT results
409        if let LoadOrigin::Script(initiator_origin) = initiator_origin_snapshot {
410            if load_data.url == document.url() && initiator_origin.same_origin(&*document.origin())
411            {
412                NavigationHistoryBehavior::Replace
413            } else {
414                // Step 12.2. Otherwise, set historyHandling to "push".
415                NavigationHistoryBehavior::Push
416            }
417        } else {
418            // Step 12.2. Otherwise, set historyHandling to "push".
419            NavigationHistoryBehavior::Push
420        }
421    } else {
422        history_handling
423    };
424
425    // Step 13. If the navigation must be a replace given url and navigable's active
426    // document, then set historyHandling to "replace".
427    //
428    // Inlines implementation of https://html.spec.whatwg.org/multipage/#the-navigation-must-be-a-replace
429    let history_handling =
430        if load_data.url.scheme() == "javascript" || document.is_initial_about_blank() {
431            NavigationHistoryBehavior::Replace
432        } else {
433            history_handling
434        };
435
436    // Step 14. If all of the following are true:
437    // > documentResource is null;
438    // > response is null;
439    if !force_reload
440        // > url equals navigable's active session history entry's URL with exclude fragments set to true; and
441        && load_data.url.as_url()[..Position::AfterQuery] ==
442            document.url().as_url()[..Position::AfterQuery]
443        // > url's fragment is non-null,
444        && load_data.url.fragment().is_some()
445    {
446        // Step 14.1. Navigate to a fragment given navigable, url, historyHandling,
447        // userInvolvement, sourceElement, navigationAPIState, and navigationId.
448        let webdriver_sender = window.webdriver_load_status_sender();
449        if let Some(ref sender) = webdriver_sender {
450            let _ = sender.send(WebDriverLoadStatus::NavigationStart);
451        }
452        navigate_to_fragment(cx, window, &load_data.url, history_handling);
453        // Step 14.2. Return.
454        if let Some(sender) = webdriver_sender {
455            let _ = sender.send(WebDriverLoadStatus::NavigationStop);
456        }
457        return;
458    }
459
460    // Step 15. If navigable's parent is non-null, then set navigable's is delaying load events to true.
461    let window_proxy = window.window_proxy();
462    if window_proxy.parent().is_some() {
463        window_proxy.start_delaying_load_events_mode();
464    }
465
466    // Step 16. Let targetSnapshotParams be the result of snapshotting target
467    // snapshot params given navigable.
468    let target_snapshot_params = snapshot_target_snapshot_params(&window_proxy);
469
470    // Step 17. Invoke WebDriver BiDi navigation started with navigable
471    // and a new WebDriver BiDi navigation status whose id is navigationId,
472    // status is "pending", and url is url.
473    // TODO
474    if let Some(sender) = window.webdriver_load_status_sender() {
475        let _ = sender.send(WebDriverLoadStatus::NavigationStart);
476    }
477
478    // Step 18. If navigable's ongoing navigation is "traversal", then:
479    // TODO
480    // Step 19. Set the ongoing navigation for navigable to navigationId.
481    // TODO
482
483    // Step 20. If url's scheme is "javascript", then:
484    if load_data.url.scheme() == "javascript" {
485        // Step 20.1. Queue a global task on the navigation and traversal task source given
486        // navigable's active window to navigate to a javascript: URL given navigable, url,
487        // historyHandling, sourceSnapshotParams, initiatorOriginSnapshot, userInvolvement,
488        // cspNavigationType, initialInsertion, and navigationId.
489
490        let Some(initiator_pipeline_id) = load_data.creator_pipeline_id else {
491            unreachable!("javascript: URL navigations must have a creator pipeline");
492        };
493        let Some(initiator_window) = ScriptThread::find_window(initiator_pipeline_id) else {
494            warn!("Can't find global for navigation initiator");
495            return;
496        };
497
498        let target_window = Trusted::new(window);
499        let mut load_data = load_data;
500        let initiator_window = Trusted::new(&*initiator_window);
501        let task = task!(navigate_javascript: move |cx| {
502            // Important re security. See https://github.com/servo/servo/issues/23373
503            let target_window = target_window.root();
504            let initiator_window = initiator_window.root();
505            if ScriptThread::navigate_to_javascript_url(cx, initiator_window.upcast(), target_window.upcast(), &mut load_data, None, None) {
506                target_window
507                    .as_global_scope()
508                    .script_to_constellation_chan()
509                    .send(ScriptToConstellationMessage::LoadUrl(load_data, history_handling, target_snapshot_params))
510                    .unwrap();
511            } else {
512                // Note: not in the spec, but required to avoid timeouts in
513                // tests that navigate to javascript: URLs that don't result
514                // in documents: https://github.com/whatwg/html/issues/12773
515                let window_proxy = target_window.window_proxy();
516                if window_proxy.parent().is_some() {
517                    window_proxy.stop_delaying_load_events_mode();
518                }
519            }
520        });
521        window
522            .as_global_scope()
523            .task_manager()
524            .navigation_and_traversal_task_source()
525            .queue(task);
526        // Step 20.2. Return.
527        return;
528    }
529
530    // Step 23. If sourceDocument is navigable's container document, then reserve deferred
531    // fetch quota for navigable's container given url's origin.
532    // TODO: Implement this.
533
534    // Step 24. In parallel, run these steps:
535    //
536    // TODO: in parallel
537
538    // Step 24.1. Let unloadPromptCanceled be the result of checking if unloading
539    // is canceled for navigable's active document's inclusive descendant navigables.
540    let unload_prompt_canceled = document.check_if_unloading_is_cancelled(cx, false);
541    // Step 24.2. If unloadPromptCanceled is not "continue",
542    // or navigable's ongoing navigation is no longer navigationId:
543    //
544    // TODO: Check for ongoing navigation
545    if !unload_prompt_canceled {
546        // Step 24.2.1. Invoke WebDriver BiDi navigation failed with navigable
547        // and a new WebDriver BiDi navigation status whose id is navigationId,
548        // status is "canceled", and url is url.
549        // TODO
550        // Step 24.2.2. Abort these steps.
551        return;
552    }
553
554    // Step 24.4. Queue a global task on the navigation and traversal task source given
555    // navigable's active window to abort a document and its descendants given navigable's
556    // active document.
557    let trusted_document = Trusted::new(&*document);
558    window
559        .task_manager()
560        .navigation_and_traversal_task_source()
561        .queue(task!(abort_a_document_and_its_descendants: move |cx| {
562            trusted_document.root().abort_a_document_and_its_descendants(cx, AbortReason::Navigate);
563        }));
564
565    // Step 24.9. Attempt to populate the history entry's document for historyEntry,
566    // given navigable, "navigate", sourceSnapshotParams, targetSnapshotParams,
567    // userInvolvement, navigationId, navigationParams, cspNavigationType,
568    // with allowPOST set to true and completionSteps set to the following step:
569    window.send_to_constellation(ScriptToConstellationMessage::LoadUrl(
570        load_data,
571        history_handling,
572        target_snapshot_params,
573    ));
574}
575
576/// <https://html.spec.whatwg.org/multipage/#determining-the-creation-sandboxing-flags>
577pub(crate) fn determine_creation_sandboxing_flags(
578    browsing_context: Option<&WindowProxy>,
579    element: Option<&Element>,
580) -> SandboxingFlagSet {
581    // To determine the creation sandboxing flags for a browsing context
582    // browsing context, given null or an element embedder, return the union
583    // of the flags that are present in the following sandboxing flag sets:
584    match element {
585        // If embedder is null, then: the flags set on browsing context's
586        // popup sandboxing flag set.
587        None => browsing_context
588            .and_then(|browsing_context| browsing_context.document())
589            .map(|document| document.active_sandboxing_flag_set())
590            .unwrap_or(SandboxingFlagSet::empty()),
591        Some(element) => {
592            // If embedder is an element, then: the flags set on embedder's
593            // iframe sandboxing flag set.
594            // If embedder is an element, then: the flags set on embedder's
595            // node document's active sandboxing flag set.
596            element
597                .downcast::<HTMLIFrameElement>()
598                .map(|iframe| iframe.sandboxing_flag_set())
599                .unwrap_or(SandboxingFlagSet::empty())
600                .union(element.owner_document().active_sandboxing_flag_set())
601        },
602    }
603}
604
605/// <https://html.spec.whatwg.org/multipage/#determining-the-iframe-element-referrer-policy>
606pub(crate) fn determine_iframe_element_referrer_policy(
607    element: Option<&Element>,
608) -> ReferrerPolicy {
609    // Step 1. If embedder is an iframe element, then return embedder's referrerpolicy
610    // attribute's state's corresponding keyword.
611    element
612        .and_then(|element| element.downcast::<HTMLIFrameElement>())
613        .map(|iframe| {
614            let token = iframe.ReferrerPolicy();
615            ReferrerPolicy::from(&*token.str())
616        })
617        // Step 2. Return the empty string.
618        .unwrap_or(ReferrerPolicy::EmptyString)
619}
620
621/// <https://html.spec.whatwg.org/multipage/#snapshotting-target-snapshot-params>
622pub(crate) fn snapshot_target_snapshot_params(navigable: &WindowProxy) -> TargetSnapshotParams {
623    // TODO(jdm): This doesn't work for cross-origin parent frames.
624    let container = navigable.frame_element();
625    // the result of determining the creation sandboxing flags given targetNavigable's
626    // active browsing context and targetNavigable's container
627    let sandboxing_flags = determine_creation_sandboxing_flags(Some(navigable), container);
628    // the result of determining the iframe element referrer policy given
629    // targetNavigable's container
630    let iframe_element_referrer_policy = determine_iframe_element_referrer_policy(container);
631    TargetSnapshotParams {
632        sandboxing_flags,
633        iframe_element_referrer_policy,
634    }
635}