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