Commit 4a3e899c authored by Daniel Johnson's avatar Daniel Johnson
Browse files

servo: Merge #17083 - "javascript:" urls: execute in correct global scope...

servo: Merge #17083 - "javascript:" urls: execute in correct global scope (from danielj41:javascript-url-global-3); r=jdm

<!-- Please describe your changes on the following line: -->

#### Summary

This pull request makes `javascript:` urls execute in the correct global scope.

#### Example

```html
<script> var x = 4; </script>

<!-- this branch: logs "4" -->
<!-- master: undefined variable error -->
<a href="javascript:console.log(x)">link</a>
```

#### Questions

I'm new to servo and rust, so I'm unsure about these changes. In particular:
  * What's the appropriate place to evaluate the js?
    * I moved it to `handle_navigate`, but I'm not sure if this will catch all occurrences of `javascript:` urls. I also don't know if this will execute in the correct thread and the correct window.
  * What should I do with the result of the js evaluation?
    * I just ignored it. The previous behavior displayed it as the content of a new page load.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #15147, #16718

<!-- Either: -->
- [x] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

Source-Repo: https://github.com/servo/servo
Source-Revision: 40c8a6389afdad8fdffdf4a5616627f9a51b1822

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : da92cf37bf7b879cc6766a5bd816c943e8b18b17
parent 4219e42f
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -1067,6 +1067,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
                debug!("constellation got URL load message from script");
                self.handle_load_url_msg(source_top_ctx_id, source_pipeline_id, load_data, replace);
            }
            FromScriptMsg::AbortLoadUrl => {
                debug!("constellation got abort URL load message from script");
                self.handle_abort_load_url_msg(source_pipeline_id);
            }
            // A page loaded has completed all parsing, script, and reflow messages have been sent.
            FromScriptMsg::LoadComplete => {
                debug!("constellation got load complete message");
@@ -1891,6 +1895,18 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
        }
    }

    fn handle_abort_load_url_msg(&mut self, new_pipeline_id: PipelineId) {
        let pending_index = self.pending_changes.iter().rposition(|change| {
            change.new_pipeline_id == new_pipeline_id
        });

        // If it is found, remove it from the pending changes.
        if let Some(pending_index) = pending_index {
            self.pending_changes.remove(pending_index);
            self.close_pipeline(new_pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
        }
    }

    fn handle_load_start_msg(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId,
                             pipeline_id: PipelineId) {
        if self.pipelines.get(&pipeline_id).and_then(|p| p.parent_info).is_none() {
+18 −3
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ use msg::constellation_msg::{FrameType, BrowsingContextId, PipelineId, TopLevelB
use net_traits::response::HttpsState;
use script_layout_interface::message::ReflowQueryType;
use script_thread::{ScriptThread, Runnable};
use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, LoadData, UpdatePipelineIdReason};
use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, UpdatePipelineIdReason};
use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptMsg};
use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
use servo_atoms::Atom;
@@ -114,7 +114,7 @@ impl HTMLIFrameElement {
    }

    pub fn navigate_or_reload_child_browsing_context(&self,
                                                     load_data: Option<LoadData>,
                                                     mut load_data: Option<LoadData>,
                                                     nav_type: NavigationType,
                                                     replace: bool) {
        let sandboxed = if self.is_sandboxed() {
@@ -140,12 +140,27 @@ impl HTMLIFrameElement {
        // document; the new navigation will continue blocking it.
        LoadBlocker::terminate(&mut load_blocker);

        if let Some(ref mut load_data) = load_data {
            let is_javascript = load_data.url.scheme() == "javascript";
            if is_javascript {
                let window_proxy = self.GetContentWindow();
                if let Some(window_proxy) = window_proxy {
                    ScriptThread::eval_js_url(&window_proxy.global(), load_data);
                }
            }
        }

        //TODO(#9592): Deal with the case where an iframe is being reloaded so url is None.
        //      The iframe should always have access to the nested context's active
        //      document URL through the browsing context.
        if let Some(ref load_data) = load_data {
            match load_data.js_eval_result {
                Some(JsEvalResult::NoContent) => (),
                _ => {
                    *load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone())));
                }
            };
        }

        let window = window_from_node(self);
        let old_pipeline_id = self.pipeline_id();
+78 −44
Original line number Diff line number Diff line
@@ -89,8 +89,8 @@ use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx};
use script_traits::{CompositorEvent, ConstellationControlMsg, PaintMetricType};
use script_traits::{DocumentActivity, DiscardBrowsingContext, EventResult};
use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent};
use script_traits::{NewLayoutInfo, ScriptToConstellationChan, ScriptMsg, UpdatePipelineIdReason};
use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, MouseButton, MouseEventType};
use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptToConstellationChan, ScriptMsg, UpdatePipelineIdReason};
use script_traits::{ScriptThreadFactory, TimerEvent, TimerSchedulerMsg, TimerSource};
use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
@@ -122,6 +122,7 @@ use task_source::performance_timeline::PerformanceTimelineTaskSource;
use task_source::user_interaction::UserInteractionTaskSource;
use time::{get_time, precise_time_ns, Tm};
use url::Position;
use url::percent_encoding::percent_decode;
use webdriver_handlers;
use webvr_traits::{WebVREvent, WebVRMsg};

@@ -1509,7 +1510,7 @@ impl ScriptThread {
                                           load_data.url.clone(),
                                           origin);
        if load_data.url.as_str() == "about:blank" {
            self.start_page_load_about_blank(new_load);
            self.start_page_load_about_blank(new_load, load_data.js_eval_result);
        } else {
            self.pre_page_load(new_load, load_data);
        }
@@ -1679,6 +1680,18 @@ impl ScriptThread {
        // the pipeline exited before the page load completed.
        match idx {
            Some(idx) => {
                // https://html.spec.whatwg.org/multipage/#process-a-navigate-response
                // 2. If response's status is 204 or 205, then abort these steps.
                match metadata {
                    Some(Metadata { status: Some((204 ... 205, _)), .. }) => {
                        self.script_sender
                            .send((id.clone(), ScriptMsg::AbortLoadUrl))
                            .unwrap();
                        return None;
                    },
                    _ => ()
                };

                let load = self.incomplete_loads.borrow_mut().remove(idx);
                metadata.map(|meta| self.load(meta, load))
            }
@@ -2118,39 +2131,7 @@ impl ScriptThread {
        // Notify devtools that a new script global exists.
        self.notify_devtools(document.Title(), final_url.clone(), (incomplete.pipeline_id, None));

        let is_javascript = incomplete.url.scheme() == "javascript";
        let parse_input = if is_javascript {
            use url::percent_encoding::percent_decode;

            // Turn javascript: URL into JS code to eval, according to the steps in
            // https://html.spec.whatwg.org/multipage/#javascript-protocol

            // This slice of the URL’s serialization is equivalent to (5.) to (7.):
            // Start with the scheme data of the parsed URL;
            // append question mark and query component, if any;
            // append number sign and fragment component if any.
            let encoded = &incomplete.url[Position::BeforePath..];

            // Percent-decode (8.) and UTF-8 decode (9.)
            let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();

            // Script source is ready to be evaluated (11.)
            unsafe {
                let _ac = JSAutoCompartment::new(self.get_cx(), window.reflector().get_jsobject().get());
                rooted!(in(self.get_cx()) let mut jsval = UndefinedValue());
                window.upcast::<GlobalScope>().evaluate_js_on_global_with_result(
                    &script_source, jsval.handle_mut());
                let strval = DOMString::from_jsval(self.get_cx(),
                                                   jsval.handle(),
                                                   StringificationBehavior::Empty);
                match strval {
                    Ok(ConversionResult::Success(s)) => s,
                    _ => DOMString::new(),
                }
            }
        } else {
            DOMString::new()
        };
        let parse_input = DOMString::new();

        document.set_https_state(metadata.https_state);

@@ -2329,8 +2310,16 @@ impl ScriptThread {
    /// for the given pipeline (specifically the "navigate" algorithm).
    fn handle_navigate(&self, parent_pipeline_id: PipelineId,
                              browsing_context_id: Option<BrowsingContextId>,
                              load_data: LoadData,
                              mut load_data: LoadData,
                              replace: bool) {
        let is_javascript = load_data.url.scheme() == "javascript";
        if is_javascript {
            let window = self.documents.borrow().find_window(parent_pipeline_id);
            if let Some(window) = window {
                ScriptThread::eval_js_url(window.upcast::<GlobalScope>(), &mut load_data);
            }
        }

        match browsing_context_id {
            Some(browsing_context_id) => {
                let iframe = self.documents.borrow().find_iframe(parent_pipeline_id, browsing_context_id);
@@ -2346,6 +2335,43 @@ impl ScriptThread {
        }
    }

    pub fn eval_js_url(global_scope: &GlobalScope, load_data: &mut LoadData) {
        // Turn javascript: URL into JS code to eval, according to the steps in
        // https://html.spec.whatwg.org/multipage/#javascript-protocol

        // This slice of the URL’s serialization is equivalent to (5.) to (7.):
        // Start with the scheme data of the parsed URL;
        // append question mark and query component, if any;
        // append number sign and fragment component if any.
        let encoded = &load_data.url.clone()[Position::BeforePath..];

        // Percent-decode (8.) and UTF-8 decode (9.)
        let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();

        // Script source is ready to be evaluated (11.)
        let _ac = JSAutoCompartment::new(global_scope.get_cx(), global_scope.reflector().get_jsobject().get());
        rooted!(in(global_scope.get_cx()) let mut jsval = UndefinedValue());
        global_scope.evaluate_js_on_global_with_result(&script_source, jsval.handle_mut());

        load_data.js_eval_result = if jsval.get().is_string() {
            unsafe {
                let strval = DOMString::from_jsval(global_scope.get_cx(),
                                                   jsval.handle(),
                                                   StringificationBehavior::Empty);
                match strval {
                    Ok(ConversionResult::Success(s)) => {
                        Some(JsEvalResult::Ok(String::from(s).as_bytes().to_vec()))
                    },
                    _ => None,
                }
            }
        } else {
            Some(JsEvalResult::NoContent)
        };

        load_data.url = ServoUrl::parse("about:blank").unwrap();
    }

    fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData, size_type: WindowSizeType) {
        let document = match { self.documents.borrow().find_document(pipeline_id) } {
            Some(document) => document,
@@ -2377,7 +2403,7 @@ impl ScriptThread {
    /// argument until a notification is received that the fetch is complete.
    fn pre_page_load(&self, incomplete: InProgressLoad, load_data: LoadData) {
        let id = incomplete.pipeline_id.clone();
        let mut req_init = RequestInit {
        let req_init = RequestInit {
            url: load_data.url.clone(),
            method: load_data.method,
            destination: Destination::Document,
@@ -2393,10 +2419,6 @@ impl ScriptThread {
            .. RequestInit::default()
        };

        if req_init.url.scheme() == "javascript" {
            req_init.url = ServoUrl::parse("about:blank").unwrap();
        }

        let context = ParserContext::new(id, load_data.url);
        self.incomplete_parser_contexts.borrow_mut().push((id, context));

@@ -2436,7 +2458,7 @@ impl ScriptThread {

    /// Synchronously fetch `about:blank`. Stores the `InProgressLoad`
    /// argument until a notification is received that the fetch is complete.
    fn start_page_load_about_blank(&self, incomplete: InProgressLoad) {
    fn start_page_load_about_blank(&self, incomplete: InProgressLoad, js_eval_result: Option<JsEvalResult>) {
        let id = incomplete.pipeline_id;

        self.incomplete_loads.borrow_mut().push(incomplete);
@@ -2446,8 +2468,20 @@ impl ScriptThread {

        let mut meta = Metadata::default(url);
        meta.set_content_type(Some(&mime!(Text / Html)));

        // If this page load is the result of a javascript scheme url, map
        // the evaluation result into a response.
        let chunk = match js_eval_result {
            Some(JsEvalResult::Ok(content)) => content,
            Some(JsEvalResult::NoContent) => {
                meta.status = Some((204, b"No Content".to_vec()));
                vec![]
            },
            None => vec![]
        };

        context.process_response(Ok(FetchMetadata::Unfiltered(meta)));
        context.process_response_chunk(vec![]);
        context.process_response_chunk(chunk);
        context.process_response_eof(Ok(()));
    }

+13 −0
Original line number Diff line number Diff line
@@ -146,12 +146,24 @@ pub struct LoadData {
    pub headers: Headers,
    /// The data.
    pub data: Option<Vec<u8>>,
    /// The result of evaluating a javascript scheme url.
    pub js_eval_result: Option<JsEvalResult>,
    /// The referrer policy.
    pub referrer_policy: Option<ReferrerPolicy>,
    /// The referrer URL.
    pub referrer_url: Option<ServoUrl>,
}

/// The result of evaluating a javascript scheme url.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum JsEvalResult {
    /// The js evaluation had a non-string result, 204 status code.
    /// https://html.spec.whatwg.org/multipage/#navigate 12.11
    NoContent,
    /// The js evaluation had a string result.
    Ok(Vec<u8>)
}

impl LoadData {
    /// Create a new `LoadData` object.
    pub fn new(url: ServoUrl,
@@ -165,6 +177,7 @@ impl LoadData {
            method: Method::Get,
            headers: Headers::new(),
            data: None,
            js_eval_result: None,
            referrer_policy: referrer_policy,
            referrer_url: referrer_url,
        }
+2 −0
Original line number Diff line number Diff line
@@ -99,6 +99,8 @@ pub enum ScriptMsg {
    /// A new load has been requested, with an option to replace the current entry once loaded
    /// instead of adding a new entry.
    LoadUrl(LoadData, bool),
    /// Abort loading after sending a LoadUrl message.
    AbortLoadUrl,
    /// Post a message to the currently active window of a given browsing context.
    PostMessage(BrowsingContextId, Option<ImmutableOrigin>, Vec<u8>),
    /// Dispatch a mozbrowser event to the parent of a mozbrowser iframe.