servo: Merge #15354 - Redirect document loads manually (from cynicaldevil:manual-redirect); r=jdm
authorNikhil Shagrithaya <nikhilshagri@gmail.com>
Fri, 02 Jun 2017 14:49:21 -0700
changeset 410240 eb1d5848152b5f0cb2eeff48bce5996410a3597b
parent 410239 ea498127969f442dba5cd81ac554bca5f9490d99
child 410241 4f5b93b0571a23c8314233534b06a1aeb476fb1e
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs15354, 14596
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #15354 - Redirect document loads manually (from cynicaldevil:manual-redirect); r=jdm <!-- Please describe your changes on the following line: --> --- <!-- 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 #14596 . r? @jdm I ran some tests at random from the `navigating-across-documents` folder, and they are passing. <!-- 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: eac4f407e2bfa0055a698ca438972566f0814e43
servo/Cargo.lock
servo/components/constellation/Cargo.toml
servo/components/constellation/constellation.rs
servo/components/constellation/lib.rs
servo/components/constellation/network_listener.rs
servo/components/net/http_loader.rs
servo/components/net/lib.rs
servo/components/net/resource_thread.rs
servo/components/net_traits/lib.rs
servo/components/net_traits/request.rs
servo/components/net_traits/response.rs
servo/components/script/dom/servoparser/mod.rs
servo/components/script/script_thread.rs
servo/components/script_traits/lib.rs
servo/components/script_traits/script_msg.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -484,16 +484,17 @@ dependencies = [
  "clipboard 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "compositing 0.0.1",
  "debugger 0.0.1",
  "devtools_traits 0.0.1",
  "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "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",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net 0.0.1",
  "net_traits 0.0.1",
  "offscreen_gl_context 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/constellation/Cargo.toml
+++ b/servo/components/constellation/Cargo.toml
@@ -16,16 +16,17 @@ canvas = {path = "../canvas"}
 clipboard = "0.3"
 canvas_traits = {path = "../canvas_traits"}
 compositing = {path = "../compositing"}
 debugger = {path = "../debugger"}
 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"}
 log = "0.3.5"
 msg = {path = "../msg"}
 net = {path = "../net"}
 net_traits = {path = "../net_traits"}
 offscreen_gl_context = "0.8"
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -86,19 +86,21 @@ use ipc_channel::{Error as IpcError};
 use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
 use ipc_channel::router::ROUTER;
 use itertools::Itertools;
 use layout_traits::LayoutThreadFactory;
 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;
 use profile_traits::time;
 use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
 use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext};
 use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData};
 use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg};
@@ -154,16 +156,22 @@ pub struct Constellation<Message, LTF, S
     /// An IPC channel for layout threads to send messages to the constellation.
     /// This is the layout threads' view of `layout_receiver`.
     layout_sender: IpcSender<FromLayoutMsg>,
 
     /// A channel for the constellation to receive messages from layout threads.
     /// 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>,
 
     /// A channel (the implementation of which is port-specific) for the
     /// constellation to send messages to the compositor thread.
     compositor_proxy: Box<CompositorProxy>,
 
     /// Channels for the constellation to send messages to the public
@@ -497,26 +505,30 @@ impl<Message, LTF, STF> Constellation<Me
 
         thread::Builder::new().name("Constellation".to_owned()).spawn(move || {
             let (ipc_script_sender, ipc_script_receiver) = ipc::channel().expect("ipc channel failure");
             let script_receiver = route_ipc_receiver_to_new_mpsc_receiver_preserving_errors(ipc_script_receiver);
 
             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));
 
             let mut constellation: Constellation<Message, LTF, STF> = Constellation {
                 script_sender: ipc_script_sender,
                 layout_sender: ipc_layout_sender,
                 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,
                 bluetooth_thread: state.bluetooth_thread,
                 public_resource_threads: state.public_resource_threads,
                 private_resource_threads: state.private_resource_threads,
                 font_cache_thread: state.font_cache_thread,
                 swmanager_chan: None,
@@ -792,16 +804,17 @@ impl<Message, LTF, STF> Constellation<Me
 
     /// Handles loading pages, navigation, and granting access to the compositor
     #[allow(unsafe_code)]
     fn handle_request(&mut self) {
         enum Request {
             Script(FromScriptMsg),
             Compositor(FromCompositorMsg),
             Layout(FromLayoutMsg),
+            NetworkListener((PipelineId, FetchResponseMsg)),
             FromSWManager(SWManagerMsg),
         }
 
         // Get one incoming request.
         // This is one of the few places where the compositor is
         // allowed to panic. If one of the receiver.recv() calls
         // fails, it is because the matching sender has been
         // reclaimed, but this can't happen in normal execution
@@ -810,24 +823,29 @@ impl<Message, LTF, STF> Constellation<Me
         // which receiver.recv() fails is if some unsafe code
         // produces undefined behaviour, resulting in the destructor
         // being called. If this happens, there's not much we can do
         // other than panic.
         let request = {
             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() =>
                     msg.expect("Unexpected script channel panic in constellation").map(Request::Script),
                 msg = receiver_from_compositor.recv() =>
                     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)
             }
         };
 
         let request = match request {
             Ok(request) => request,
             Err(err) => {
@@ -844,22 +862,41 @@ impl<Message, LTF, STF> Constellation<Me
                 self.handle_request_from_compositor(message)
             },
             Request::Script(message) => {
                 self.handle_request_from_script(message);
             },
             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) => {
                 // store service worker manager for communicating with it.
                 self.swmanager_chan = Some(sw_sender);
             }
         }
     }
@@ -951,16 +988,20 @@ impl<Message, LTF, STF> Constellation<Me
         }
     }
 
     fn handle_request_from_script(&mut self, message: FromScriptMsg) {
         match message {
             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,
                        load_info.old_pipeline_id,
                        load_info.info.new_pipeline_id);
                 self.handle_script_loaded_url_in_iframe_msg(load_info);
             }
             FromScriptMsg::ScriptNewIFrame(load_info, layout_sender) => {
@@ -1479,16 +1520,28 @@ impl<Message, LTF, STF> Constellation<Me
             Some(parent) => parent.event_loop.send(msg),
             None => return warn!("Parent {} browsing context loaded after closure.", parent_id),
         };
         if let Err(e) = result {
             self.handle_send_error(parent_id, e);
         }
     }
 
+    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
     // page navigation.
     fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfoWithData) {
         let (load_data, window_size, is_private) = {
             let old_pipeline = load_info.old_pipeline_id
                 .and_then(|old_pipeline_id| self.pipelines.get(&old_pipeline_id));
--- a/servo/components/constellation/lib.rs
+++ b/servo/components/constellation/lib.rs
@@ -15,16 +15,17 @@ extern crate clipboard;
 extern crate compositing;
 extern crate debugger;
 extern crate devtools_traits;
 extern crate euclid;
 #[cfg(not(target_os = "windows"))]
 extern crate gaol;
 extern crate gfx;
 extern crate gfx_traits;
+extern crate hyper;
 extern crate ipc_channel;
 extern crate itertools;
 extern crate layout_traits;
 #[macro_use]
 extern crate log;
 extern crate msg;
 extern crate net;
 extern crate net_traits;
@@ -40,16 +41,17 @@ extern crate servo_remutex;
 extern crate servo_url;
 extern crate style_traits;
 extern crate webrender_traits;
 extern crate webvr_traits;
 
 mod browsingcontext;
 mod constellation;
 mod event_loop;
+mod network_listener;
 mod pipeline;
 #[cfg(not(target_os = "windows"))]
 mod sandboxing;
 mod timer_scheduler;
 
 pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState};
 pub use pipeline::UnprivilegedPipelineContent;
 #[cfg(not(target_os = "windows"))]
new file mode 100644
--- /dev/null
+++ b/servo/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);
+            }
+        }
+    }
+}
--- a/servo/components/net/http_loader.rs
+++ b/servo/components/net/http_loader.rs
@@ -669,24 +669,24 @@ pub fn http_fetch(request: &mut Request,
 
     // set back to default
     response.return_internal = true;
     // Step 7
     response
 }
 
 /// [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);
 
     // Step 2
     if !response.actual_response().headers.has::<Location>() {
         return response;
     }
 
@@ -744,18 +744,20 @@ fn http_redirect_fetch(request: &mut Req
     }
 
     // Step 11
     request.url_list.push(location_url);
 
     // 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> {
     match *url_origin {
         // TODO (servo/servo#15569) Set "Origin: null" when hyper supports it
         ImmutableOrigin::Opaque(_) => None,
         ImmutableOrigin::Tuple(ref scheme, ref host, ref port) =>
             Some(HyperOrigin::new(scheme.clone(), host.to_string(), Some(port.clone())))
@@ -1103,16 +1105,17 @@ fn http_network_fetch(request: &Request,
     }
 
     let mut response = Response::new(url.clone());
     response.status = Some(res.response.status);
     response.raw_status = Some((res.response.status_raw().0,
                                 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();
 
     // We're about to spawn a thread to be waited on here
     let (done_sender, done_receiver) = channel();
     *done_chan = Some((done_sender.clone(), done_receiver));
     let meta = match response.metadata().expect("Response metadata should exist at this stage") {
         FetchMetadata::Unfiltered(m) => m,
--- a/servo/components/net/lib.rs
+++ b/servo/components/net/lib.rs
@@ -47,17 +47,17 @@ mod blob_loader;
 mod chrome_loader;
 pub mod connector;
 pub mod cookie;
 pub mod cookie_storage;
 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;
 mod storage_thread;
 pub mod subresource_integrity;
 mod websocket_loader;
 /// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
 pub mod fetch {
--- a/servo/components/net/resource_thread.rs
+++ b/servo/components/net/resource_thread.rs
@@ -3,27 +3,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A thread that takes a URL and streams back the binary data.
 use connector::{create_http_connector, create_ssl_client};
 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};
 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};
 use serde_json;
 use servo_config::opts;
 use servo_config::resource_files::resources_dir_path;
 use servo_url::ServoUrl;
 use std::borrow::{Cow, ToOwned};
@@ -148,18 +150,20 @@ impl ResourceChannelManager {
     }
 
 
     /// Returns false if the thread should exit.
     fn process_msg(&mut self,
                    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) =>
                 self.resource_manager.set_cookie_for_url(&request, cookie.into_inner(), source, http_state),
             CoreResourceMsg::SetCookiesForUrl(request, cookies, source) => {
                 for cookie in cookies {
                     self.resource_manager.set_cookie_for_url(&request, cookie.into_inner(), source, http_state);
                 }
@@ -313,37 +317,51 @@ impl CoreResourceManager {
                           http_state: &Arc<HttpState>) {
         if let Some(cookie) = cookie::Cookie::new_wrapped(cookie, request, source) {
             let mut cookie_jar = http_state.cookie_jar.write().unwrap();
             cookie_jar.push(cookie, request, source)
         }
     }
 
     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();
         let ua = self.user_agent.clone();
         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?
             // todo service worker stuff
             let context = FetchContext {
                 state: http_state,
                 user_agent: ua,
                 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");
     }
 
     fn websocket_connect(&self,
                          connect: WebSocketCommunicate,
                          connect_data: WebSocketConnectData,
                          http_state: &Arc<HttpState>) {
         websocket_loader::init(connect, connect_data, http_state.clone());
--- a/servo/components/net_traits/lib.rs
+++ b/servo/components/net_traits/lib.rs
@@ -37,17 +37,17 @@ use hyper::Error as HyperError;
 use hyper::header::{ContentType, Headers, ReferrerPolicy as ReferrerPolicyHeader};
 use hyper::http::RawStatus;
 use hyper::mime::{Attr, Mime};
 use hyper_serde::Serde;
 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;
 
 pub mod blob_url_store;
 pub mod filemanager_thread;
 pub mod image_cache;
 pub mod net_error_list;
@@ -364,16 +364,18 @@ pub struct WebSocketConnectData {
     pub resource_url: ServoUrl,
     pub origin: String,
     pub protocols: Vec<String>,
 }
 
 #[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
     SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
     /// Store a set of cookies for a given originating URL
     SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
     /// Retrieve the stored cookies for a given URL
     GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
@@ -430,30 +432,34 @@ pub struct Metadata {
     /// HTTP Status
     pub status: Option<(u16, Vec<u8>)>,
 
     /// Is successful HTTPS connection
     pub https_state: HttpsState,
 
     /// Referrer Url
     pub referrer: Option<ServoUrl>,
+
+    /// Referrer Policy of the Request used to obtain Response
+    pub referrer_policy: Option<ReferrerPolicy>,
 }
 
 impl Metadata {
     /// Metadata with defaults for everything optional.
     pub fn default(url: ServoUrl) -> Self {
         Metadata {
             final_url: url,
             content_type: None,
             charset: None,
             headers: None,
             // https://fetch.spec.whatwg.org/#concept-response-status-message
             status: Some((200, b"OK".to_vec())),
             https_state: HttpsState::None,
             referrer: None,
+            referrer_policy: None,
         }
     }
 
     /// Extract the parts of a Mime that we care about.
     pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
         if self.headers.is_none() {
             self.headers = Some(Serde(Headers::new()));
         }
--- a/servo/components/net_traits/request.rs
+++ b/servo/components/net_traits/request.rs
@@ -55,17 +55,17 @@ pub enum Destination {
 /// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize, HeapSizeOf)]
 pub enum Origin {
     Client,
     Origin(ImmutableOrigin),
 }
 
 /// 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
     Client,
     ReferrerUrl(ServoUrl),
 }
 
 /// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
@@ -153,16 +153,18 @@ pub struct RequestInit {
     // doesn't have info about the client right now
     pub origin: ServoUrl,
     // XXXManishearth these should be part of the client object
     pub referrer_url: Option<ServoUrl>,
     pub referrer_policy: Option<ReferrerPolicy>,
     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 {
     fn default() -> RequestInit {
         RequestInit {
             method: Method::Get,
             url: ServoUrl::parse("about:blank").unwrap(),
             headers: Headers::new(),
@@ -177,16 +179,17 @@ impl Default for RequestInit {
             credentials_mode: CredentialsMode::Omit,
             use_url_credentials: false,
             origin: ServoUrl::parse("about:blank").unwrap(),
             referrer_url: None,
             referrer_policy: None,
             pipeline_id: None,
             redirect_mode: RedirectMode::Follow,
             integrity_metadata: "".to_owned(),
+            url_list: vec![],
         }
     }
 }
 
 /// A [Request](https://fetch.spec.whatwg.org/#concept-request) as defined by
 /// the Fetch spec.
 #[derive(Clone, HeapSizeOf)]
 pub struct Request {
@@ -285,17 +288,17 @@ impl Request {
             integrity_metadata: String::new(),
             url_list: vec![url],
             redirect_count: 0,
             response_tainting: ResponseTainting::Basic,
         }
     }
 
     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);
         req.method = init.method;
         req.headers = init.headers;
         req.unsafe_request = init.unsafe_request;
         req.body = init.body;
         req.type_ = init.type_;
@@ -309,16 +312,22 @@ impl Request {
         req.referrer = if let Some(url) = init.referrer_url {
             Referrer::ReferrerUrl(url)
         } else {
             Referrer::NoReferrer
         };
         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
     }
 
     /// https://fetch.spec.whatwg.org/#concept-request-url
     pub fn url(&self) -> ServoUrl {
         self.url_list.first().unwrap().clone()
     }
--- a/servo/components/net_traits/response.rs
+++ b/servo/components/net_traits/response.rs
@@ -1,15 +1,15 @@
 /* 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 [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;
 use servo_url::ServoUrl;
 use std::ascii::AsciiExt;
 use std::sync::{Arc, Mutex};
 
 /// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
@@ -69,16 +69,26 @@ pub enum HttpsState {
 }
 
 pub enum ResponseMsg {
     Chunk(Vec<u8>),
     Finished,
     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 {
     pub response_type: ResponseType,
     pub termination_reason: Option<TerminationReason>,
     url: Option<ServoUrl>,
     pub url_list: Vec<ServoUrl>,
     /// `None` can be considered a StatusCode of `0`.
@@ -92,50 +102,60 @@ pub struct Response {
     pub cache_state: CacheState,
     pub https_state: HttpsState,
     pub referrer: Option<ServoUrl>,
     /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
     /// is a filtered 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 {
     pub fn new(url: ServoUrl) -> Response {
         Response {
             response_type: ResponseType::Default,
             termination_reason: None,
             url: Some(url),
             url_list: vec![],
             status: Some(StatusCode::Ok),
             raw_status: Some((200, b"OK".to_vec())),
             headers: Headers::new(),
             body: Arc::new(Mutex::new(ResponseBody::Empty)),
             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),
             termination_reason: None,
             url: None,
             url_list: vec![],
             status: None,
             raw_status: None,
             headers: Headers::new(),
             body: Arc::new(Mutex::new(ResponseBody::Empty)),
             cache_state: CacheState::None,
             https_state: HttpsState::None,
             referrer: None,
+            referrer_policy: None,
             internal_response: None,
             return_internal: true,
         }
     }
 
     pub fn url(&self) -> Option<&ServoUrl> {
         self.url.as_ref()
     }
@@ -258,16 +278,17 @@ impl Response {
             metadata.set_content_type(match response.headers.get() {
                 Some(&ContentType(ref mime)) => Some(mime),
                 None => None,
             });
             metadata.headers = Some(Serde(response.headers.clone()));
             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
         };
 
         if let Some(error) = self.get_network_error() {
             return Err(error.clone());
         }
 
         let metadata = self.url.as_ref().map(|url| init_metadata(self, url));
--- a/servo/components/script/dom/servoparser/mod.rs
+++ b/servo/components/script/dom/servoparser/mod.rs
@@ -522,16 +522,17 @@ impl Tokenizer {
             Tokenizer::Html(_) => ProfilerCategory::ScriptParseHTML,
             Tokenizer::Xml(_) => ProfilerCategory::ScriptParseXML,
         }
     }
 }
 
 /// 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>>,
     /// Is this a synthesized document
     is_synthesized_document: bool,
     /// The pipeline associated with this document.
     id: PipelineId,
     /// The URL for this document.
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -55,36 +55,35 @@ use dom::transitionevent::TransitionEven
 use dom::uievent::UIEvent;
 use dom::window::{ReflowReason, Window};
 use dom::windowproxy::WindowProxy;
 use dom::worker::TrustedWorkerAddress;
 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;
 use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use js::glue::GetWindowProxyClass;
 use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks};
 use js::jsapi::{JSTracer, SetWindowProxyClass};
 use js::jsval::UndefinedValue;
 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};
 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};
@@ -100,17 +99,17 @@ use servo_url::{ImmutableOrigin, Mutable
 use std::cell::Cell;
 use std::collections::{hash_map, HashMap, HashSet};
 use std::default::Default;
 use std::ops::Deref;
 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;
 use style::context::ReflowGoal;
 use style::thread_state;
 use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource};
 use task_source::file_reading::FileReadingTaskSource;
 use task_source::history_traversal::HistoryTraversalTaskSource;
@@ -406,16 +405,18 @@ impl<'a> Iterator for DocumentsIter<'a> 
 pub struct ScriptThread {
     /// The documents for pipelines managed by this thread
     documents: DOMRefCell<Documents>,
     /// The window proxies known by this thread
     /// TODO: this map grows, but never shrinks. Issue #15258.
     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
     job_queue_map: Rc<JobQueue>,
     /// Image cache for this script thread.
     image_cache: Arc<ImageCache>,
     /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if
     /// there are many iframes.
@@ -569,17 +570,17 @@ impl ScriptThreadFactory for ScriptThrea
                 root.set(Some(&script_thread as *const _));
             });
 
             let mut failsafe = ScriptMemoryFailsafe::new(&script_thread);
 
             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(|| {
                 script_thread.start();
                 let _ = script_thread.content_process_shutdown_chan.send(());
             }, reporter_name, script_chan, CommonScriptMsg::CollectReports);
 
             // This must always be the very last operation performed before the thread completes
@@ -753,16 +754,17 @@ impl ScriptThread {
         let boxed_script_sender = MainThreadScriptChan(chan.clone()).clone();
 
         let (image_cache_channel, image_cache_port) = channel();
 
         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()),
 
             image_cache: state.image_cache.clone(),
             image_cache_channel: image_cache_channel,
             image_cache_port: image_cache_port,
 
             resource_threads: state.resource_threads,
@@ -1100,16 +1102,24 @@ impl ScriptThread {
             profile(profiler_cat, None, self.time_profiler_chan.clone(), f)
         } else {
             f()
         }
     }
 
     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) =>
                 self.handle_event(id, event),
             ConstellationControlMsg::ResizeInactive(id, new_size) =>
                 self.handle_resize_inactive_msg(id, new_size),
             ConstellationControlMsg::GetTitle(pipeline_id) =>
                 self.handle_get_title_msg(pipeline_id),
@@ -1391,17 +1401,17 @@ impl ScriptThread {
                                            parent_info,
                                            layout_chan,
                                            window_size,
                                            load_data.url.clone(),
                                            origin);
         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);
         }
     }
 
     fn collect_reports(&self, reports_chan: ReportsChan) {
         let mut path_seg = String::from("url(");
         let mut dom_tree_size = 0;
         let mut reports = vec![];
 
@@ -2220,55 +2230,77 @@ impl ScriptThread {
         }
 
         // https://html.spec.whatwg.org/multipage/#event-loop-processing-model
         // Step 7.7 - evaluate media queries and report changes
         // Since we have resized, we need to re-evaluate MQLs
         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) {
         let id = incomplete.pipeline_id;
 
         self.incomplete_loads.borrow_mut().push(incomplete);
 
         let url = ServoUrl::parse("about:blank").unwrap();
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -50,17 +50,17 @@ use euclid::size::TypedSize2D;
 use gfx_traits::Epoch;
 use heapsize::HeapSizeOf;
 use hyper::header::Headers;
 use hyper::method::Method;
 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;
 use net_traits::storage_thread::StorageType;
 use profile_traits::mem;
 use profile_traits::time as profile_time;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use servo_url::ImmutableOrigin;
@@ -227,16 +227,19 @@ pub enum UpdatePipelineIdReason {
     Navigation,
     /// The pipeline id is being updated due to a history traversal.
     Traversal,
 }
 
 /// 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.
     Resize(PipelineId, WindowSizeData, WindowSizeType),
     /// Notifies script that window has been resized but to not take immediate action.
     ResizeInactive(PipelineId, WindowSizeData),
     /// Notifies the script that a pipeline should be closed.
     ExitPipeline(PipelineId, DiscardBrowsingContext),
@@ -299,16 +302,17 @@ pub enum ConstellationControlMsg {
     /// Notifies the script thread of WebVR events.
     WebVREvents(PipelineId, Vec<WebVREvent>)
 }
 
 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",
             ExitPipeline(..) => "ExitPipeline",
             ExitScriptThread => "ExitScriptThread",
             SendEvent(..) => "SendEvent",
             Viewport(..) => "Viewport",
             SetScrollState(..) => "SetScrollState",
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -15,16 +15,17 @@ use WorkerScriptLoadOrigin;
 use canvas_traits::CanvasMsg;
 use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
 use euclid::point::Point2D;
 use euclid::size::{Size2D, TypedSize2D};
 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;
 use servo_url::ServoUrl;
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use webrender_traits::ClipId;
@@ -62,16 +63,19 @@ pub enum LogEntry {
     Error(String),
     /// warning, with a reason
     Warn(String),
 }
 
 /// 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>),
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(PipelineId, AnimationState),
     /// Requests that a new 2D canvas thread be created. (This is done in the constellation because
     /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.)
     CreateCanvasPaintThread(Size2D<i32>, IpcSender<IpcSender<CanvasMsg>>),