aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikhil Shagrithaya <nikhilshagri@gmail.com>2017-02-01 13:45:35 +0530
committerNikhil Shagrithaya <nikhilshagri@gmail.com>2017-05-31 17:28:53 +0530
commit541baafe1c644f5276fe417a4d7e3696d3b22e68 (patch)
tree50a6a4e450dc54b0a943e2527742bc3e768a732d
parent779edd7c4aaf900f12fab378a378b0fc52d4c628 (diff)
downloadservo-541baafe1c644f5276fe417a4d7e3696d3b22e68.tar.gz
servo-541baafe1c644f5276fe417a4d7e3696d3b22e68.zip
Redirect document loads manually
-rw-r--r--Cargo.lock1
-rw-r--r--components/constellation/Cargo.toml1
-rw-r--r--components/constellation/constellation.rs55
-rw-r--r--components/constellation/lib.rs2
-rw-r--r--components/constellation/network_listener.rs133
-rw-r--r--components/net/http_loader.rs21
-rw-r--r--components/net/lib.rs2
-rw-r--r--components/net/resource_thread.rs32
-rw-r--r--components/net_traits/lib.rs8
-rw-r--r--components/net_traits/request.rs13
-rw-r--r--components/net_traits/response.rs23
-rw-r--r--components/script/dom/servoparser/mod.rs1
-rw-r--r--components/script/script_thread.rs90
-rw-r--r--components/script_traits/lib.rs6
-rw-r--r--components/script_traits/script_msg.rs4
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json10
-rw-r--r--tests/wpt/mozilla/tests/mozilla/multiple_redirects.html26
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>