Auto merge of #15354 - cynicaldevil:manual-redirect, r=jdm

Redirect document loads manually

<!-- 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. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15354)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-06-02 14:49:21 -07:00 committed by GitHub
commit eac4f407e2
17 changed files with 376 additions and 52 deletions

View file

@ -21,6 +21,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"}

View file

@ -91,9 +91,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;
@ -159,6 +161,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>,
@ -502,6 +510,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));
@ -512,6 +522,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,
@ -797,6 +809,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
Script(FromScriptMsg),
Compositor(FromCompositorMsg),
Layout(FromLayoutMsg),
NetworkListener((PipelineId, FetchResponseMsg)),
FromSWManager(SWManagerMsg),
}
@ -815,6 +828,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() =>
@ -823,6 +837,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)
}
@ -849,12 +867,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) => {
@ -956,6 +993,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,
@ -1484,6 +1525,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

View file

@ -20,6 +20,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;
@ -45,6 +46,7 @@ extern crate webvr_traits;
mod browsingcontext;
mod constellation;
mod event_loop;
mod network_listener;
mod pipeline;
#[cfg(not(target_os = "windows"))]
mod sandboxing;

View file

@ -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);
}
}
}
}