Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"javascript:" urls: execute in correct global scope #17083

Merged
merged 8 commits into from Sep 9, 2017
@@ -1055,6 +1055,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");
@@ -1874,6 +1878,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() {
@@ -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,11 +140,26 @@ 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 {
*load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone())));
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);
@@ -87,8 +87,8 @@ use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx};
use script_traits::{CompositorEvent, ConstellationControlMsg};
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};
@@ -119,6 +119,7 @@ use task_source::performance_timeline::{PerformanceTimelineTask, PerformanceTime
use task_source::user_interaction::{UserInteractionTask, 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};

@@ -1506,7 +1507,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);
}
@@ -1676,6 +1677,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))
}
@@ -2116,39 +2129,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);

@@ -2327,8 +2308,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);
@@ -2344,6 +2333,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,
@@ -2375,7 +2401,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,
@@ -2391,10 +2417,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));

@@ -2434,7 +2456,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);
@@ -2444,8 +2466,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(()));
}

@@ -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,
}
@@ -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.
{}
]
],
"html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html": [
[
"/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html",
{}
]
],
"html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html": [
[
"/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html",
"3842ac825b9fb33d0d95ef99f77c8c7d02a88e9a",
"support"
],
"html/browsers/browsing-the-web/navigating-across-documents/javascript-url-global-scope.html": [
"d678c54e2c20d5f240fd68790ea4e03512db2c8a",
"testharness"
],
"html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html": [
"1278f37be250f761f84bf352ebff8ed4c8a04e96",
"testharness"
@@ -1,6 +1,5 @@
[open-url-javascript-window-2.htm]
type: testharness
expected: TIMEOUT
[XMLHttpRequest: open() - resolving URLs (javascript: <iframe>; 2)]
expected: TIMEOUT
expected: FAIL

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -7,6 +7,3 @@
[Script script-svg]
expected: NOTRUN

[Script iframe-src]
expected: NOTRUN

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.