diff options
author | Nikhil Shagrithaya <nikhilshagri@gmail.com> | 2017-02-01 13:45:35 +0530 |
---|---|---|
committer | Nikhil Shagrithaya <nikhilshagri@gmail.com> | 2017-05-31 17:28:53 +0530 |
commit | 541baafe1c644f5276fe417a4d7e3696d3b22e68 (patch) | |
tree | 50a6a4e450dc54b0a943e2527742bc3e768a732d | |
parent | 779edd7c4aaf900f12fab378a378b0fc52d4c628 (diff) | |
download | servo-541baafe1c644f5276fe417a4d7e3696d3b22e68.tar.gz servo-541baafe1c644f5276fe417a4d7e3696d3b22e68.zip |
Redirect document loads manually
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | components/constellation/Cargo.toml | 1 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 55 | ||||
-rw-r--r-- | components/constellation/lib.rs | 2 | ||||
-rw-r--r-- | components/constellation/network_listener.rs | 133 | ||||
-rw-r--r-- | components/net/http_loader.rs | 21 | ||||
-rw-r--r-- | components/net/lib.rs | 2 | ||||
-rw-r--r-- | components/net/resource_thread.rs | 32 | ||||
-rw-r--r-- | components/net_traits/lib.rs | 8 | ||||
-rw-r--r-- | components/net_traits/request.rs | 13 | ||||
-rw-r--r-- | components/net_traits/response.rs | 23 | ||||
-rw-r--r-- | components/script/dom/servoparser/mod.rs | 1 | ||||
-rw-r--r-- | components/script/script_thread.rs | 90 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 6 | ||||
-rw-r--r-- | components/script_traits/script_msg.rs | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 10 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/multiple_redirects.html | 26 |
17 files changed, 376 insertions, 52 deletions
diff --git a/Cargo.lock b/Cargo.lock index f5205c658c0..3b825232334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,7 @@ dependencies = [ "gaol 0.0.1 (git+https://github.com/servo/gaol)", "gfx 0.0.1", "gfx_traits 0.0.1", + "hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", "layout_traits 0.0.1", diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index e6b8bfebeb3..5e0fc12950e 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -20,6 +20,7 @@ devtools_traits = {path = "../devtools_traits"} euclid = "0.11" gfx = {path = "../gfx"} gfx_traits = {path = "../gfx_traits"} +hyper = "0.10" ipc-channel = "0.7" itertools = "0.5" layout_traits = {path = "../layout_traits"} diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 07ace83d561..d8de0b7ddb7 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -90,9 +90,11 @@ use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord}; use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, FrameType, PipelineId}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection}; -use net_traits::{self, IpcSend, ResourceThreads}; +use net_traits::{self, IpcSend, FetchResponseMsg, ResourceThreads}; use net_traits::pub_domains::reg_host; +use net_traits::request::RequestInit; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; +use network_listener::NetworkListener; use offscreen_gl_context::{GLContextAttributes, GLLimits}; use pipeline::{InitialPipelineState, Pipeline}; use profile_traits::mem; @@ -158,6 +160,12 @@ pub struct Constellation<Message, LTF, STF> { /// This is the constellation's view of `layout_sender`. layout_receiver: Receiver<Result<FromLayoutMsg, IpcError>>, + /// A channel for network listener to send messages to the constellation. + network_listener_sender: Sender<(PipelineId, FetchResponseMsg)>, + + /// A channel for the constellation to receive messages from network listener. + network_listener_receiver: Receiver<(PipelineId, FetchResponseMsg)>, + /// A channel for the constellation to receive messages from the compositor thread. compositor_receiver: Receiver<FromCompositorMsg>, @@ -498,6 +506,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let (ipc_layout_sender, ipc_layout_receiver) = ipc::channel().expect("ipc channel failure"); let layout_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(ipc_layout_receiver); + let (network_listener_sender, network_listener_receiver) = channel(); + let swmanager_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(swmanager_receiver); PipelineNamespace::install(PipelineNamespaceId(0)); @@ -508,6 +518,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> script_receiver: script_receiver, compositor_receiver: compositor_receiver, layout_receiver: layout_receiver, + network_listener_sender: network_listener_sender, + network_listener_receiver: network_listener_receiver, compositor_proxy: state.compositor_proxy, debugger_chan: state.debugger_chan, devtools_chan: state.devtools_chan, @@ -782,6 +794,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> Script(FromScriptMsg), Compositor(FromCompositorMsg), Layout(FromLayoutMsg), + NetworkListener((PipelineId, FetchResponseMsg)), FromSWManager(SWManagerMsg), } @@ -800,6 +813,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let receiver_from_script = &self.script_receiver; let receiver_from_compositor = &self.compositor_receiver; let receiver_from_layout = &self.layout_receiver; + let receiver_from_network_listener = &self.network_listener_receiver; let receiver_from_swmanager = &self.swmanager_receiver; select! { msg = receiver_from_script.recv() => @@ -808,6 +822,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> Ok(Request::Compositor(msg.expect("Unexpected compositor channel panic in constellation"))), msg = receiver_from_layout.recv() => msg.expect("Unexpected layout channel panic in constellation").map(Request::Layout), + msg = receiver_from_network_listener.recv() => + Ok(Request::NetworkListener( + msg.expect("Unexpected network listener channel panic in constellation") + )), msg = receiver_from_swmanager.recv() => msg.expect("Unexpected panic channel panic in constellation").map(Request::FromSWManager) } @@ -834,12 +852,31 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> Request::Layout(message) => { self.handle_request_from_layout(message); }, + Request::NetworkListener(message) => { + self.handle_request_from_network_listener(message); + }, Request::FromSWManager(message) => { self.handle_request_from_swmanager(message); } } } + fn handle_request_from_network_listener(&mut self, message: (PipelineId, FetchResponseMsg)) { + let (id, message_) = message; + let result = match self.pipelines.get(&id) { + Some(pipeline) => { + let msg = ConstellationControlMsg::NavigationResponse(id, message_); + pipeline.event_loop.send(msg) + }, + None => { + return warn!("Pipeline {:?} got fetch data after closure!", id); + }, + }; + if let Err(e) = result { + self.handle_send_error(id, e); + } + } + fn handle_request_from_swmanager(&mut self, message: SWManagerMsg) { match message { SWManagerMsg::OwnSender(sw_sender) => { @@ -941,6 +978,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> FromScriptMsg::PipelineExited(pipeline_id) => { self.handle_pipeline_exited(pipeline_id); } + FromScriptMsg::InitiateNavigateRequest(req_init, pipeline_id) => { + debug!("constellation got initiate navigate request message"); + self.handle_navigate_request(req_init, pipeline_id); + } FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => { debug!("constellation got iframe URL load message {:?} {:?} {:?}", load_info.info.parent_pipeline_id, @@ -1454,6 +1495,18 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> } } + fn handle_navigate_request(&self, + req_init: RequestInit, + id: PipelineId) { + let listener = NetworkListener::new( + req_init, + id, + self.public_resource_threads.clone(), + self.network_listener_sender.clone()); + + listener.initiate_fetch(); + } + // The script thread associated with pipeline_id has loaded a URL in an iframe via script. This // will result in a new pipeline being spawned and a child being added to // the parent pipeline. This message is never the result of a diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index 6958a5b7c39..a3cbda4195f 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -19,6 +19,7 @@ extern crate euclid; extern crate gaol; extern crate gfx; extern crate gfx_traits; +extern crate hyper; extern crate ipc_channel; extern crate itertools; extern crate layout_traits; @@ -44,6 +45,7 @@ extern crate webvr_traits; mod browsingcontext; mod constellation; mod event_loop; +mod network_listener; mod pipeline; #[cfg(not(target_os = "windows"))] mod sandboxing; diff --git a/components/constellation/network_listener.rs b/components/constellation/network_listener.rs new file mode 100644 index 00000000000..239a6089f40 --- /dev/null +++ b/components/constellation/network_listener.rs @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The listener that encapsulates all state for an in-progress document request. +//! Any redirects that are encountered are followed. Whenever a non-redirect +//! response is received, it is forwarded to the appropriate script thread. + +use hyper::header::Location; +use ipc_channel::ipc; +use ipc_channel::router::ROUTER; +use msg::constellation_msg::PipelineId; +use net::http_loader::{set_default_accept, set_default_accept_language}; +use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseMsg}; +use net_traits::{IpcSend, NetworkError, ResourceThreads}; +use net_traits::request::{Destination, RequestInit, Type}; +use net_traits::response::ResponseInit; +use std::sync::mpsc::Sender; + +pub struct NetworkListener { + res_init: Option<ResponseInit>, + req_init: RequestInit, + pipeline_id: PipelineId, + resource_threads: ResourceThreads, + sender: Sender<(PipelineId, FetchResponseMsg)>, + should_send: bool, +} + +impl NetworkListener { + pub fn new(req_init: RequestInit, + pipeline_id: PipelineId, + resource_threads: ResourceThreads, + sender: Sender<(PipelineId, FetchResponseMsg)>) -> NetworkListener { + NetworkListener { + res_init: None, + req_init, + pipeline_id, + resource_threads, + sender, + should_send: false + } + } + + pub fn initiate_fetch(&self) { + let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!"); + + let mut listener = NetworkListener { + res_init: self.res_init.clone(), + req_init: self.req_init.clone(), + resource_threads: self.resource_threads.clone(), + sender: self.sender.clone(), + pipeline_id: self.pipeline_id.clone(), + should_send: false, + }; + + let msg = match self.res_init { + Some(ref res_init_) => CoreResourceMsg::FetchRedirect( + self.req_init.clone(), + res_init_.clone(), + ipc_sender), + None => { + set_default_accept(Type::None, Destination::Document, &mut listener.req_init.headers); + set_default_accept_language(&mut listener.req_init.headers); + + CoreResourceMsg::Fetch( + listener.req_init.clone(), + ipc_sender) + } + }; + + ROUTER.add_route(ipc_receiver.to_opaque(), box move |message| { + let msg = message.to(); + match msg { + Ok(FetchResponseMsg::ProcessResponse(res)) => listener.check_redirect(res), + Ok(msg_) => listener.send(msg_), + Err(e) => warn!("Error while receiving network listener message: {}", e), + }; + }); + + if let Err(e) = self.resource_threads.sender().send(msg) { + warn!("Resource thread unavailable ({})", e); + } + } + + fn check_redirect(&mut self, + message: Result<(FetchMetadata), NetworkError>) { + match message { + Ok(res_metadata) => { + let metadata = match res_metadata { + FetchMetadata::Filtered { ref unsafe_, .. } => unsafe_, + FetchMetadata::Unfiltered(ref m) => m, + }; + + match metadata.headers { + Some(ref headers) if headers.has::<Location>() => { + if self.req_init.url_list.is_empty() { + self.req_init.url_list.push(self.req_init.url.clone()); + } + self.req_init.url_list.push(metadata.final_url.clone()); + + self.req_init.referrer_url = metadata.referrer.clone(); + self.req_init.referrer_policy = metadata.referrer_policy; + + self.res_init = Some(ResponseInit { + url: metadata.final_url.clone(), + headers: headers.clone().into_inner(), + referrer: metadata.referrer.clone(), + }); + + self.initiate_fetch(); + }, + _ => { + // Response should be processed by script thread. + self.should_send = true; + self.send(FetchResponseMsg::ProcessResponse(Ok(res_metadata.clone()))); + } + }; + }, + Err(e) => { + self.should_send = true; + self.send(FetchResponseMsg::ProcessResponse(Err(e))) + } + }; + } + + fn send(&mut self, msg: FetchResponseMsg) { + if self.should_send { + if let Err(e) = self.sender.send((self.pipeline_id, msg)) { + warn!("Failed to forward network message to pipeline {}: {:?}", self.pipeline_id, e); + } + } + } +} diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index b3b0f1d2da8..90f0708c234 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -674,14 +674,14 @@ pub fn http_fetch(request: &mut Request, } /// [HTTP redirect fetch](https://fetch.spec.whatwg.org#http-redirect-fetch) -fn http_redirect_fetch(request: &mut Request, - cache: &mut CorsCache, - response: Response, - cors_flag: bool, - target: Target, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { +pub fn http_redirect_fetch(request: &mut Request, + cache: &mut CorsCache, + response: Response, + cors_flag: bool, + target: Target, + done_chan: &mut DoneChannel, + context: &FetchContext) + -> Response { // Step 1 assert!(response.return_internal); @@ -749,8 +749,10 @@ fn http_redirect_fetch(request: &mut Request, // Step 12 // TODO implement referrer policy + let recursive_flag = request.redirect_mode != RedirectMode::Manual; + // Step 13 - main_fetch(request, cache, cors_flag, true, target, done_chan, context) + main_fetch(request, cache, cors_flag, recursive_flag, target, done_chan, context) } fn try_immutable_origin_to_hyper_origin(url_origin: &ImmutableOrigin) -> Option<HyperOrigin> { @@ -1108,6 +1110,7 @@ fn http_network_fetch(request: &Request, res.response.status_raw().1.as_bytes().to_vec())); response.headers = res.response.headers.clone(); response.referrer = request.referrer.to_url().cloned(); + response.referrer_policy = request.referrer_policy.clone(); let res_body = response.body.clone(); diff --git a/components/net/lib.rs b/components/net/lib.rs index 1aa9e632c32..8f15def3948 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -52,7 +52,7 @@ mod data_loader; pub mod filemanager_thread; mod hosts; pub mod hsts; -mod http_loader; +pub mod http_loader; pub mod image_cache; pub mod mime_classifier; pub mod resource_thread; diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 0ad4841d99c..0b1ce39747f 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -8,10 +8,11 @@ use cookie; use cookie_rs; use cookie_storage::CookieStorage; use devtools_traits::DevtoolsControlMsg; +use fetch::cors_cache::CorsCache; use fetch::methods::{FetchContext, fetch}; use filemanager_thread::{FileManager, TFDProvider}; use hsts::HstsList; -use http_loader::HttpState; +use http_loader::{HttpState, http_redirect_fetch}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; use net_traits::{CookieSource, CoreResourceThread}; @@ -19,6 +20,7 @@ use net_traits::{CoreResourceMsg, FetchResponseMsg}; use net_traits::{CustomResponseMediator, ResourceId}; use net_traits::{ResourceThreads, WebSocketCommunicate, WebSocketConnectData}; use net_traits::request::{Request, RequestInit}; +use net_traits::response::{Response, ResponseInit}; use net_traits::storage_thread::StorageThreadMsg; use profile_traits::time::ProfilerChan; use serde::{Deserialize, Serialize}; @@ -153,8 +155,10 @@ impl ResourceChannelManager { msg: CoreResourceMsg, http_state: &Arc<HttpState>) -> bool { match msg { - CoreResourceMsg::Fetch(init, sender) => - self.resource_manager.fetch(init, sender, http_state), + CoreResourceMsg::Fetch(req_init, sender) => + self.resource_manager.fetch(req_init, None, sender, http_state), + CoreResourceMsg::FetchRedirect(req_init, res_init, sender) => + self.resource_manager.fetch(req_init, Some(res_init), sender, http_state), CoreResourceMsg::WebsocketConnect(connect, connect_data) => self.resource_manager.websocket_connect(connect, connect_data, http_state), CoreResourceMsg::SetCookieForUrl(request, cookie, source) => @@ -318,7 +322,8 @@ impl CoreResourceManager { } fn fetch(&self, - init: RequestInit, + req_init: RequestInit, + res_init_: Option<ResponseInit>, mut sender: IpcSender<FetchResponseMsg>, http_state: &Arc<HttpState>) { let http_state = http_state.clone(); @@ -326,8 +331,8 @@ impl CoreResourceManager { let dc = self.devtools_chan.clone(); let filemanager = self.filemanager.clone(); - thread::Builder::new().name(format!("fetch thread for {}", init.url)).spawn(move || { - let mut request = Request::from_init(init); + thread::Builder::new().name(format!("fetch thread for {}", req_init.url)).spawn(move || { + let mut request = Request::from_init(req_init); // XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed) // todo load context / mimesniff in fetch // todo referrer policy? @@ -338,7 +343,20 @@ impl CoreResourceManager { devtools_chan: dc, filemanager: filemanager, }; - fetch(&mut request, &mut sender, &context); + + match res_init_ { + Some(res_init) => { + let response = Response::from_init(res_init); + http_redirect_fetch(&mut request, + &mut CorsCache::new(), + response, + true, + &mut sender, + &mut None, + &context); + }, + None => fetch(&mut request, &mut sender, &context), + }; }).expect("Thread spawning failed"); } diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index cbe8e2aad42..e1403386d54 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -42,7 +42,7 @@ use ipc_channel::Error as IpcError; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use request::{Request, RequestInit}; -use response::{HttpsState, Response}; +use response::{HttpsState, Response, ResponseInit}; use servo_url::ServoUrl; use std::error::Error; use storage_thread::StorageThreadMsg; @@ -369,6 +369,8 @@ pub struct WebSocketConnectData { #[derive(Deserialize, Serialize)] pub enum CoreResourceMsg { Fetch(RequestInit, IpcSender<FetchResponseMsg>), + /// Initiate a fetch in response to processing a redirection + FetchRedirect(RequestInit, ResponseInit, IpcSender<FetchResponseMsg>), /// Try to make a websocket connection to a URL. WebsocketConnect(WebSocketCommunicate, WebSocketConnectData), /// Store a cookie for a given originating URL @@ -435,6 +437,9 @@ pub struct Metadata { /// Referrer Url pub referrer: Option<ServoUrl>, + + /// Referrer Policy of the Request used to obtain Response + pub referrer_policy: Option<ReferrerPolicy>, } impl Metadata { @@ -449,6 +454,7 @@ impl Metadata { status: Some((200, b"OK".to_vec())), https_state: HttpsState::None, referrer: None, + referrer_policy: None, } } diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs index a882ce424ba..74f58d6f0c1 100644 --- a/components/net_traits/request.rs +++ b/components/net_traits/request.rs @@ -60,7 +60,7 @@ pub enum Origin { } /// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer) -#[derive(Clone, PartialEq, HeapSizeOf)] +#[derive(Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)] pub enum Referrer { NoReferrer, /// Default referrer if nothing is specified @@ -158,6 +158,8 @@ pub struct RequestInit { pub pipeline_id: Option<PipelineId>, pub redirect_mode: RedirectMode, pub integrity_metadata: String, + // to keep track of redirects + pub url_list: Vec<ServoUrl>, } impl Default for RequestInit { @@ -182,6 +184,7 @@ impl Default for RequestInit { pipeline_id: None, redirect_mode: RedirectMode::Follow, integrity_metadata: "".to_owned(), + url_list: vec![], } } } @@ -290,7 +293,7 @@ impl Request { } pub fn from_init(init: RequestInit) -> Request { - let mut req = Request::new(init.url, + let mut req = Request::new(init.url.clone(), Some(Origin::Origin(init.origin.origin())), false, init.pipeline_id); @@ -314,6 +317,12 @@ impl Request { req.referrer_policy = init.referrer_policy; req.pipeline_id = init.pipeline_id; req.redirect_mode = init.redirect_mode; + let mut url_list = init.url_list; + if url_list.is_empty() { + url_list.push(init.url); + } + req.redirect_count = url_list.len() as u32 - 1; + req.url_list = url_list; req.integrity_metadata = init.integrity_metadata; req } diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index 7d0bfe57000..74525a85f07 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -4,7 +4,7 @@ //! The [Response](https://fetch.spec.whatwg.org/#responses) object //! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) -use {FetchMetadata, FilteredMetadata, Metadata, NetworkError}; +use {FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy}; use hyper::header::{AccessControlExposeHeaders, ContentType, Headers}; use hyper::status::StatusCode; use hyper_serde::Serde; @@ -74,6 +74,16 @@ pub enum ResponseMsg { Errored, } +#[derive(Serialize, Deserialize, Clone, HeapSizeOf)] +pub struct ResponseInit { + pub url: ServoUrl, + #[serde(deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize")] + #[ignore_heap_size_of = "Defined in hyper"] + pub headers: Headers, + pub referrer: Option<ServoUrl>, +} + /// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec #[derive(Debug, Clone, HeapSizeOf)] pub struct Response { @@ -97,6 +107,7 @@ pub struct Response { pub internal_response: Option<Box<Response>>, /// whether or not to try to return the internal_response when asked for actual_response pub return_internal: bool, + pub referrer_policy: Option<ReferrerPolicy>, } impl Response { @@ -113,11 +124,19 @@ impl Response { cache_state: CacheState::None, https_state: HttpsState::None, referrer: None, + referrer_policy: None, internal_response: None, return_internal: true, } } + pub fn from_init(init: ResponseInit) -> Response { + let mut res = Response::new(init.url); + res.headers = init.headers; + res.referrer = init.referrer; + res + } + pub fn network_error(e: NetworkError) -> Response { Response { response_type: ResponseType::Error(e), @@ -131,6 +150,7 @@ impl Response { cache_state: CacheState::None, https_state: HttpsState::None, referrer: None, + referrer_policy: None, internal_response: None, return_internal: true, } @@ -263,6 +283,7 @@ impl Response { metadata.status = response.raw_status.clone(); metadata.https_state = response.https_state; metadata.referrer = response.referrer.clone(); + metadata.referrer_policy = response.referrer_policy.clone(); metadata }; diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index af017d58958..254c178f8f6 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -527,6 +527,7 @@ impl Tokenizer { /// The context required for asynchronously fetching a document /// and parsing it progressively. +#[derive(JSTraceable)] pub struct ParserContext { /// The parser that initiated the request. parser: Option<Trusted<ServoParser>>, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 405616fedec..8dabb29c81b 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -60,7 +60,7 @@ use dom::worklet::WorkletThreadPool; use dom::workletglobalscope::WorkletGlobalScopeInit; use euclid::Rect; use euclid::point::Point2D; -use hyper::header::{ContentType, HttpDate, LastModified, Headers}; +use hyper::header::{ContentType, HttpDate, Headers, LastModified}; use hyper::header::ReferrerPolicy as ReferrerPolicyHeader; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper_serde::Serde; @@ -74,12 +74,11 @@ use js::rust::Runtime; use mem::heap_size_of_self_and_children; use microtask::{MicrotaskQueue, Microtask}; use msg::constellation_msg::{BrowsingContextId, FrameType, PipelineId, PipelineNamespace, TopLevelBrowsingContextId}; -use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; -use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; +use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg}; +use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; use net_traits::image_cache::{ImageCache, PendingImageResponse}; -use net_traits::request::{CredentialsMode, Destination, RequestInit}; +use net_traits::request::{CredentialsMode, Destination, RedirectMode, RequestInit}; use net_traits::storage_thread::StorageType; -use network_listener::NetworkListener; use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan}; use profile_traits::time::{self, ProfilerCategory, profile}; use script_layout_interface::message::{self, NewLayoutThreadInfo, ReflowQueryType}; @@ -105,7 +104,7 @@ use std::option::Option; use std::ptr; use std::rc::Rc; use std::result::Result; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Select, Sender, channel}; use std::thread; @@ -411,6 +410,8 @@ pub struct ScriptThread { window_proxies: DOMRefCell<HashMap<BrowsingContextId, JS<WindowProxy>>>, /// A list of data pertaining to loads that have not yet received a network response incomplete_loads: DOMRefCell<Vec<InProgressLoad>>, + /// A vector containing parser contexts which have not yet been fully processed + incomplete_parser_contexts: DOMRefCell<Vec<(PipelineId, ParserContext)>>, /// A map to store service worker registrations for a given origin registration_map: DOMRefCell<HashMap<ServoUrl, JS<ServiceWorkerRegistration>>>, /// A job queue for Service Workers keyed by their scope url @@ -574,7 +575,7 @@ impl ScriptThreadFactory for ScriptThread { let origin = MutableOrigin::new(load_data.url.origin()); let new_load = InProgressLoad::new(id, browsing_context_id, top_level_browsing_context_id, parent_info, layout_chan, window_size, load_data.url.clone(), origin); - script_thread.start_page_load(new_load, load_data); + script_thread.pre_page_load(new_load, load_data); let reporter_name = format!("script-reporter-{}", id); mem_profiler_chan.run_with_memory_reporting(|| { @@ -758,6 +759,7 @@ impl ScriptThread { documents: DOMRefCell::new(Documents::new()), window_proxies: DOMRefCell::new(HashMap::new()), incomplete_loads: DOMRefCell::new(vec!()), + incomplete_parser_contexts: DOMRefCell::new(vec!()), registration_map: DOMRefCell::new(HashMap::new()), job_queue_map: Rc::new(JobQueue::new()), @@ -1105,6 +1107,14 @@ impl ScriptThread { fn handle_msg_from_constellation(&self, msg: ConstellationControlMsg) { match msg { + ConstellationControlMsg::NavigationResponse(id, fetch_data) => { + match fetch_data { + FetchResponseMsg::ProcessResponse(metadata) => self.handle_fetch_metadata(id, metadata), + FetchResponseMsg::ProcessResponseChunk(chunk) => self.handle_fetch_chunk(id, chunk), + FetchResponseMsg::ProcessResponseEOF(eof) => self.handle_fetch_eof(id, eof), + _ => unreachable!(), + }; + }, ConstellationControlMsg::Navigate(parent_pipeline_id, browsing_context_id, load_data, replace) => self.handle_navigate(parent_pipeline_id, Some(browsing_context_id), load_data, replace), ConstellationControlMsg::SendEvent(id, event) => @@ -1396,7 +1406,7 @@ impl ScriptThread { if load_data.url.as_str() == "about:blank" { self.start_page_load_about_blank(new_load); } else { - self.start_page_load(new_load, load_data); + self.pre_page_load(new_load, load_data); } } @@ -2225,45 +2235,67 @@ impl ScriptThread { window.evaluate_media_queries_and_report_changes(); } - /// Initiate a non-blocking fetch for a specified resource. Stores the InProgressLoad + /// Instructs the constellation to fetch the document that will be loaded. Stores the InProgressLoad /// argument until a notification is received that the fetch is complete. - fn start_page_load(&self, incomplete: InProgressLoad, mut load_data: LoadData) { + fn pre_page_load(&self, incomplete: InProgressLoad, load_data: LoadData) { let id = incomplete.pipeline_id.clone(); - - let context = Arc::new(Mutex::new(ParserContext::new(id, load_data.url.clone()))); - let (action_sender, action_receiver) = ipc::channel().unwrap(); - let listener = NetworkListener { - context: context, - task_source: self.networking_task_source.clone(), - wrapper: None, - }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); - - if load_data.url.scheme() == "javascript" { - load_data.url = ServoUrl::parse("about:blank").unwrap(); - } - - let request = RequestInit { + let mut req_init = RequestInit { url: load_data.url.clone(), method: load_data.method, destination: Destination::Document, credentials_mode: CredentialsMode::Include, use_url_credentials: true, - origin: load_data.url, + origin: load_data.url.clone(), pipeline_id: Some(id), referrer_url: load_data.referrer_url, referrer_policy: load_data.referrer_policy, headers: load_data.headers, body: load_data.data, + redirect_mode: RedirectMode::Manual, .. RequestInit::default() }; - self.resource_threads.send(CoreResourceMsg::Fetch(request, action_sender)).unwrap(); + 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)); + + self.constellation_chan.send(ConstellationMsg::InitiateNavigateRequest(req_init, id)).unwrap(); self.incomplete_loads.borrow_mut().push(incomplete); } + fn handle_fetch_metadata(&self, id: PipelineId, fetch_metadata: Result<FetchMetadata, NetworkError>) { + match fetch_metadata { + Ok(_) => {}, + Err(ref e) => warn!("Network error: {:?}", e), + }; + let mut incomplete_parser_contexts = self.incomplete_parser_contexts.borrow_mut(); + let parser = incomplete_parser_contexts.iter_mut().find(|&&mut (pipeline_id, _)| pipeline_id == id); + if let Some(&mut (_, ref mut ctxt)) = parser { + ctxt.process_response(fetch_metadata); + } + } + + fn handle_fetch_chunk(&self, id: PipelineId, chunk: Vec<u8>) { + let mut incomplete_parser_contexts = self.incomplete_parser_contexts.borrow_mut(); + let parser = incomplete_parser_contexts.iter_mut().find(|&&mut (pipeline_id, _)| pipeline_id == id); + if let Some(&mut (_, ref mut ctxt)) = parser { + ctxt.process_response_chunk(chunk); + } + } + + fn handle_fetch_eof(&self, id: PipelineId, eof: Result<(), NetworkError>) { + let idx = self.incomplete_parser_contexts.borrow().iter().position(|&(pipeline_id, _)| { + pipeline_id == id + }); + if let Some(idx) = idx { + let (_, mut ctxt) = self.incomplete_parser_contexts.borrow_mut().remove(idx); + ctxt.process_response_eof(eof); + } + } + /// 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) { diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 51725abc3b8..ba92857efbc 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -55,7 +55,7 @@ use ipc_channel::ipc::{IpcReceiver, IpcSender}; use libc::c_void; use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, FrameType, Key, KeyModifiers, KeyState}; use msg::constellation_msg::{PipelineId, PipelineNamespaceId, TraversalDirection}; -use net_traits::{ReferrerPolicy, ResourceThreads}; +use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads}; use net_traits::image::base::Image; use net_traits::image_cache::ImageCache; use net_traits::response::HttpsState; @@ -232,6 +232,9 @@ pub enum UpdatePipelineIdReason { /// Messages sent from the constellation or layout to the script thread. #[derive(Deserialize, Serialize)] pub enum ConstellationControlMsg { + /// Sends the final response to script thread for fetching after all redirections + /// have been resolved + NavigationResponse(PipelineId, FetchResponseMsg), /// Gives a channel and ID to a layout thread, as well as the ID of that layout's parent AttachLayout(NewLayoutInfo), /// Window resized. Sends a DOM event eventually, but first we combine events. @@ -304,6 +307,7 @@ impl fmt::Debug for ConstellationControlMsg { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { use self::ConstellationControlMsg::*; let variant = match *self { + NavigationResponse(..) => "NavigationResponse", AttachLayout(..) => "AttachLayout", Resize(..) => "Resize", ResizeInactive(..) => "ResizeInactive", diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index 9bb7053daa0..ec6f85f5a61 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -20,6 +20,7 @@ use ipc_channel::ipc::IpcSender; use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, FrameType, PipelineId, TraversalDirection}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use net_traits::CoreResourceMsg; +use net_traits::request::RequestInit; use net_traits::storage_thread::StorageType; use offscreen_gl_context::{GLContextAttributes, GLLimits}; use servo_url::ImmutableOrigin; @@ -67,6 +68,9 @@ pub enum LogEntry { /// Messages from the script to the constellation. #[derive(Deserialize, Serialize)] pub enum ScriptMsg { + /// Requests are sent to constellation and fetches are checked manually + /// for cross-origin loads + InitiateNavigateRequest(RequestInit, PipelineId), /// Broadcast a storage event to every same-origin pipeline. /// The strings are key, old value and new value. BroadcastStorageEvent(PipelineId, StorageType, ServoUrl, Option<String>, Option<String>, Option<String>), diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 578db7bc716..eefedbe420e 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13352,6 +13352,12 @@ {} ] ], + "mozilla/multiple_redirects.html": [ + [ + "/_mozilla/mozilla/multiple_redirects.html", + {} + ] + ], "mozilla/navigator.html": [ [ "/_mozilla/mozilla/navigator.html", @@ -26021,6 +26027,10 @@ "f8f9adebe09f9473a52e5ec4f075540b10b32d7e", "testharness" ], + "mozilla/multiple_redirects.html": [ + "db0ffd3db5b23204b91f332915d938cfc85ec46c", + "testharness" + ], "mozilla/navigator.html": [ "939f453fecfb28a36cb93057382b56439b00b136", "testharness" diff --git a/tests/wpt/mozilla/tests/mozilla/multiple_redirects.html b/tests/wpt/mozilla/tests/mozilla/multiple_redirects.html new file mode 100644 index 00000000000..790d2933288 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/multiple_redirects.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>Multiple redirects</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<iframe src=""></iframe> +<script> +async_test(function(t) { + var iframe = document.getElementsByTagName("iframe")[0]; + var base_url = "/common/redirect.py?location=/common/dummy.xhtml"; + var second = "/common/redirect.py?location=" + base_url; + var third = "/common/redirect.py?location=" + second; + iframe.src = third; + iframe.onload = t.step_func(function() { + if(iframe.contentWindow.location.href === new URL('/common/dummy.xhtml', location.href).href) { + this.done(); + } + }); +}); +</script> +</body> +</html> |