Propagating the load errors from network loader

This commit is contained in:
Ravi Shankar 2016-01-01 00:01:24 +05:30 committed by Josh Matthews
parent 8d988f20c1
commit 5e6f32a59b
15 changed files with 208 additions and 152 deletions

View file

@ -183,15 +183,21 @@ impl FontCache {
ROUTER.add_route(data_receiver.to_opaque(), box move |message| { ROUTER.add_route(data_receiver.to_opaque(), box move |message| {
let response: ResponseAction = message.to().unwrap(); let response: ResponseAction = message.to().unwrap();
match response { match response {
ResponseAction::HeadersAvailable(metadata) => { ResponseAction::HeadersAvailable(meta_result) => {
let is_response_valid = let is_response_valid = match meta_result {
Ok(ref metadata) => {
metadata.content_type.as_ref().map_or(false, |content_type| { metadata.content_type.as_ref().map_or(false, |content_type| {
let mime = &content_type.0; let mime = &content_type.0;
is_supported_font_type(&mime.0, &mime.1) is_supported_font_type(&mime.0, &mime.1)
}); })
info!("{} font with MIME type {:?}", }
Err(_) => false,
};
info!("{} font with MIME type {}",
if is_response_valid { "Loading" } else { "Ignoring" }, if is_response_valid { "Loading" } else { "Ignoring" },
metadata.content_type); meta_result.map(|ref meta| format!("{:?}", meta.content_type))
.unwrap_or(format!("<Network Error>")));
*response_valid.lock().unwrap() = is_response_valid; *response_valid.lock().unwrap() = is_response_valid;
} }
ResponseAction::DataAvailable(new_bytes) => { ResponseAction::DataAvailable(new_bytes) => {

View file

@ -9,7 +9,7 @@ use hyper::mime::{Mime, SubLevel, TopLevel};
use mime_classifier::MIMEClassifier; use mime_classifier::MIMEClassifier;
use net_traits::ProgressMsg::Done; use net_traits::ProgressMsg::Done;
use net_traits::response::HttpsState; use net_traits::response::HttpsState;
use net_traits::{LoadConsumer, LoadData, Metadata}; use net_traits::{LoadConsumer, LoadData, Metadata, NetworkError};
use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt};
use std::sync::Arc; use std::sync::Arc;
use url::Url; use url::Url;
@ -49,7 +49,7 @@ pub fn factory(mut load_data: LoadData,
load_data.url = Url::from_file_path(&*path).unwrap(); load_data.url = Url::from_file_path(&*path).unwrap();
} }
_ => { _ => {
send_error(load_data.url, "Unknown about: URL.".to_owned(), start_chan); send_error(load_data.url, NetworkError::Internal("Unknown about: URL.".to_owned()), start_chan);
return return
} }
}; };

View file

@ -6,7 +6,7 @@ use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
use mime_classifier::MIMEClassifier; use mime_classifier::MIMEClassifier;
use net_traits::LoadConsumer; use net_traits::LoadConsumer;
use net_traits::ProgressMsg::{Payload, Done}; use net_traits::ProgressMsg::{Payload, Done};
use net_traits::{LoadData, Metadata}; use net_traits::{LoadData, Metadata, NetworkError};
use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt};
use rustc_serialize::base64::FromBase64; use rustc_serialize::base64::FromBase64;
use std::sync::Arc; use std::sync::Arc;
@ -110,7 +110,9 @@ pub fn load(load_data: LoadData,
let _ = chan.send(Done(Ok(()))); let _ = chan.send(Done(Ok(())));
} }
}, },
Err(DecodeError::InvalidDataUri) => send_error(url, "invalid data uri".to_owned(), start_chan), Err(DecodeError::InvalidDataUri) =>
Err(DecodeError::NonBase64DataUri) => send_error(url, "non-base64 data uri".to_owned(), start_chan), send_error(url, NetworkError::Internal("invalid data uri".to_owned()), start_chan),
Err(DecodeError::NonBase64DataUri) =>
send_error(url, NetworkError::Internal("non-base64 data uri".to_owned()), start_chan),
} }
} }

View file

@ -6,7 +6,7 @@ use about_loader;
use mime_classifier::MIMEClassifier; use mime_classifier::MIMEClassifier;
use mime_guess::guess_mime_type; use mime_guess::guess_mime_type;
use net_traits::ProgressMsg::{Done, Payload}; use net_traits::ProgressMsg::{Done, Payload};
use net_traits::{LoadConsumer, LoadData, Metadata}; use net_traits::{LoadConsumer, LoadData, Metadata, NetworkError};
use resource_thread::{CancellationListener, ProgressSender}; use resource_thread::{CancellationListener, ProgressSender};
use resource_thread::{send_error, start_sending_sniffed_opt}; use resource_thread::{send_error, start_sending_sniffed_opt};
use std::borrow::ToOwned; use std::borrow::ToOwned;
@ -50,7 +50,7 @@ fn read_all(reader: &mut File, progress_chan: &ProgressSender, cancel_listener:
ReadStatus::EOF => return Ok(LoadResult::Finished), ReadStatus::EOF => return Ok(LoadResult::Finished),
} }
} }
let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned()))));
Ok(LoadResult::Cancelled) Ok(LoadResult::Cancelled)
} }
@ -72,7 +72,7 @@ pub fn factory(load_data: LoadData,
let file_path = match load_data.url.to_file_path() { let file_path = match load_data.url.to_file_path() {
Ok(file_path) => file_path, Ok(file_path) => file_path,
Err(_) => { Err(_) => {
send_error(load_data.url, "Could not parse path".to_owned(), senders); send_error(load_data.url, NetworkError::Internal("Could not parse path".to_owned()), senders);
return; return;
}, },
}; };
@ -92,7 +92,7 @@ pub fn factory(load_data: LoadData,
if cancel_listener.is_cancelled() { if cancel_listener.is_cancelled() {
if let Ok(progress_chan) = get_progress_chan(load_data, file_path, if let Ok(progress_chan) = get_progress_chan(load_data, file_path,
senders, classifier, &[]) { senders, classifier, &[]) {
let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned()))));
} }
return; return;
} }
@ -116,7 +116,7 @@ pub fn factory(load_data: LoadData,
} }
} }
Err(e) => { Err(e) => {
send_error(load_data.url, e, senders); send_error(load_data.url, NetworkError::Internal(e), senders);
} }
} }
}); });

View file

@ -8,7 +8,6 @@ use cookie;
use cookie_storage::CookieStorage; use cookie_storage::CookieStorage;
use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest};
use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent};
use file_loader;
use flate2::read::{DeflateDecoder, GzDecoder}; use flate2::read::{DeflateDecoder, GzDecoder};
use hsts::{HstsEntry, HstsList, secure_url}; use hsts::{HstsEntry, HstsList, secure_url};
use hyper::Error as HttpError; use hyper::Error as HttpError;
@ -28,7 +27,8 @@ use msg::constellation_msg::{PipelineId};
use net_traits::ProgressMsg::{Done, Payload}; use net_traits::ProgressMsg::{Done, Payload};
use net_traits::hosts::replace_hosts; use net_traits::hosts::replace_hosts;
use net_traits::response::HttpsState; use net_traits::response::HttpsState;
use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadContext, LoadData, Metadata}; use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadContext, LoadData};
use net_traits::{Metadata, NetworkError};
use openssl::ssl::error::{SslError, OpensslError}; use openssl::ssl::error::{SslError, OpensslError};
use openssl::ssl::{SSL_OP_NO_SSLV2, SSL_OP_NO_SSLV3, SSL_VERIFY_PEER, SslContext, SslMethod}; use openssl::ssl::{SSL_OP_NO_SSLV2, SSL_OP_NO_SSLV3, SSL_VERIFY_PEER, SslContext, SslMethod};
use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt, AuthCacheEntry}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt, AuthCacheEntry};
@ -158,32 +158,15 @@ fn load_for_consumer(load_data: LoadData,
match load(&load_data, &ui_provider, &http_state, match load(&load_data, &ui_provider, &http_state,
devtools_chan, &factory, devtools_chan, &factory,
user_agent, &cancel_listener) { user_agent, &cancel_listener) {
Err(LoadError::UnsupportedScheme(url)) => { Err(error) => {
let s = format!("{} request, but we don't support that scheme", &*url.scheme); match error.error {
send_error(url, s, start_chan) LoadErrorType::ConnectionAborted => unreachable!(),
LoadErrorType::Ssl => send_error(error.url.clone(),
NetworkError::SslValidation(error.url),
start_chan),
_ => send_error(error.url, NetworkError::Internal(error.reason), start_chan)
} }
Err(LoadError::Connection(url, e)) => {
send_error(url, e, start_chan)
} }
Err(LoadError::MaxRedirects(url, _)) => {
send_error(url, "too many redirects".to_owned(), start_chan)
}
Err(LoadError::Cors(url, msg)) |
Err(LoadError::Cancelled(url, msg)) |
Err(LoadError::InvalidRedirect(url, msg)) |
Err(LoadError::Decoding(url, msg)) => {
send_error(url, msg, start_chan)
}
Err(LoadError::Ssl(url, msg)) => {
info!("ssl validation error {}, '{}'", url.serialize(), msg);
let mut image = resources_dir_path();
image.push("badcert.html");
let load_data = LoadData::new(load_data.context, Url::from_file_path(&*image).unwrap(), None);
file_loader::factory(load_data, start_chan, classifier, cancel_listener)
}
Err(LoadError::ConnectionAborted(_)) => unreachable!(),
Ok(mut load_response) => { Ok(mut load_response) => {
let metadata = load_response.metadata.clone(); let metadata = load_response.metadata.clone();
send_data(load_data.context, &mut load_response, start_chan, metadata, classifier, &cancel_listener) send_data(load_data.context, &mut load_response, start_chan, metadata, classifier, &cancel_listener)
@ -268,20 +251,15 @@ impl HttpRequestFactory for NetworkHttpRequestFactory {
let error: &(Error + Send + 'static) = &**error; let error: &(Error + Send + 'static) = &**error;
if let Some(&SslError::OpenSslErrors(ref errors)) = error.downcast_ref::<SslError>() { if let Some(&SslError::OpenSslErrors(ref errors)) = error.downcast_ref::<SslError>() {
if errors.iter().any(is_cert_verify_error) { if errors.iter().any(is_cert_verify_error) {
return Err( let msg = format!("ssl error: {:?} {:?}", error.description(), error.cause());
LoadError::Ssl(url, format!("ssl error: {:?} {:?}", return Err(LoadError::new(url, LoadErrorType::Ssl, msg));
error.description(),
error.cause())));
} }
} }
} }
let mut request = match connection { let mut request = match connection {
Ok(req) => req, Ok(req) => req,
Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned())),
Err(e) => {
return Err(LoadError::Connection(url, e.description().to_owned()))
}
}; };
*request.headers_mut() = headers; *request.headers_mut() = headers;
@ -306,21 +284,23 @@ impl HttpRequest for WrappedHttpRequest {
let url = self.request.url.clone(); let url = self.request.url.clone();
let mut request_writer = match self.request.start() { let mut request_writer = match self.request.start() {
Ok(streaming) => streaming, Ok(streaming) => streaming,
Err(e) => return Err(LoadError::Connection(url, e.description().to_owned())) Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned())),
}; };
if let Some(ref data) = *body { if let Some(ref data) = *body {
if let Err(e) = request_writer.write_all(&data) { if let Err(e) = request_writer.write_all(&data) {
return Err(LoadError::Connection(url, e.description().to_owned())) return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned()))
} }
} }
let response = match request_writer.send() { let response = match request_writer.send() {
Ok(w) => w, Ok(w) => w,
Err(HttpError::Io(ref io_error)) if io_error.kind() == io::ErrorKind::ConnectionAborted => { Err(HttpError::Io(ref io_error)) if io_error.kind() == io::ErrorKind::ConnectionAborted => {
return Err(LoadError::ConnectionAborted(io_error.description().to_owned())); return Err(LoadError::new(url, LoadErrorType::ConnectionAborted,
io_error.description().to_owned()));
}, },
Err(e) => return Err(LoadError::Connection(url, e.description().to_owned())) Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection,
e.description().to_owned())),
}; };
Ok(WrappedHttpResponse { response: response }) Ok(WrappedHttpResponse { response: response })
@ -328,16 +308,33 @@ impl HttpRequest for WrappedHttpRequest {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError { pub struct LoadError {
UnsupportedScheme(Url), url: Url,
Connection(Url, String), error: LoadErrorType,
Cors(Url, String), reason: String,
Ssl(Url, String), }
InvalidRedirect(Url, String),
Decoding(Url, String), impl LoadError {
MaxRedirects(Url, u32), // u32 indicates number of redirects that occurred fn new(url: Url, error: LoadErrorType, reason: String) -> LoadError {
ConnectionAborted(String), LoadError {
Cancelled(Url, String), url: url,
error: error,
reason: reason,
}
}
}
#[derive(Debug)]
pub enum LoadErrorType {
Cancelled,
Connection,
ConnectionAborted,
Cors,
Decoding,
InvalidRedirect,
MaxRedirects(u32), // u32 indicates number of redirects that occurred
Ssl,
UnsupportedScheme,
} }
fn set_default_accept_encoding(headers: &mut Headers) { fn set_default_accept_encoding(headers: &mut Headers) {
@ -456,12 +453,8 @@ impl<R: HttpResponse> StreamedResponse<R> {
Some(Encoding::Gzip) => { Some(Encoding::Gzip) => {
let result = GzDecoder::new(response); let result = GzDecoder::new(response);
match result { match result {
Ok(response_decoding) => { Ok(response_decoding) => Ok(StreamedResponse::new(m, Decoder::Gzip(response_decoding))),
Ok(StreamedResponse::new(m, Decoder::Gzip(response_decoding))) Err(err) => Err(LoadError::new(m.final_url, LoadErrorType::Decoding, err.to_string())),
}
Err(err) => {
Err(LoadError::Decoding(m.final_url, err.to_string()))
}
} }
} }
Some(Encoding::Deflate) => { Some(Encoding::Deflate) => {
@ -670,7 +663,7 @@ pub fn obtain_response<A>(request_factory: &HttpRequestFactory<R=A>,
headers.clone())); headers.clone()));
if cancel_listener.is_cancelled() { if cancel_listener.is_cancelled() {
return Err(LoadError::Cancelled(connection_url.clone(), "load cancelled".to_owned())); return Err(LoadError::new(connection_url.clone(), LoadErrorType::Cancelled, "load cancelled".to_owned()));
} }
let maybe_response = req.send(request_body); let maybe_response = req.send(request_body);
@ -685,11 +678,14 @@ pub fn obtain_response<A>(request_factory: &HttpRequestFactory<R=A>,
response = match maybe_response { response = match maybe_response {
Ok(r) => r, Ok(r) => r,
Err(LoadError::ConnectionAborted(reason)) => { Err(e) => {
debug!("connection aborted ({:?}), possibly stale, trying new connection", reason); if let LoadErrorType::ConnectionAborted = e.error {
debug!("connection aborted ({:?}), possibly stale, trying new connection", e.reason);
continue; continue;
} else {
return Err(e)
} }
Err(e) => return Err(e), },
}; };
// if no ConnectionAborted, break the loop // if no ConnectionAborted, break the loop
@ -736,7 +732,7 @@ pub fn load<A, B>(load_data: &LoadData,
let mut new_auth_header: Option<Authorization<Basic>> = None; let mut new_auth_header: Option<Authorization<Basic>> = None;
if cancel_listener.is_cancelled() { if cancel_listener.is_cancelled() {
return Err(LoadError::Cancelled(doc_url, "load cancelled".to_owned())); return Err(LoadError::new(doc_url, LoadErrorType::Cancelled, "load cancelled".to_owned()));
} }
// If the URL is a view-source scheme then the scheme data contains the // If the URL is a view-source scheme then the scheme data contains the
@ -758,15 +754,17 @@ pub fn load<A, B>(load_data: &LoadData,
} }
if iters > max_redirects { if iters > max_redirects {
return Err(LoadError::MaxRedirects(doc_url, iters - 1)); return Err(LoadError::new(doc_url, LoadErrorType::MaxRedirects(iters - 1),
"too many redirects".to_owned()));
} }
if &*doc_url.scheme != "http" && &*doc_url.scheme != "https" { if &*doc_url.scheme != "http" && &*doc_url.scheme != "https" {
return Err(LoadError::UnsupportedScheme(doc_url)); let s = format!("{} request, but we don't support that scheme", &*doc_url.scheme);
return Err(LoadError::new(doc_url, LoadErrorType::UnsupportedScheme, s));
} }
if cancel_listener.is_cancelled() { if cancel_listener.is_cancelled() {
return Err(LoadError::Cancelled(doc_url, "load cancelled".to_owned())); return Err(LoadError::new(doc_url, LoadErrorType::Cancelled, "load cancelled".to_owned()));
} }
info!("requesting {}", doc_url.serialize()); info!("requesting {}", doc_url.serialize());
@ -832,9 +830,8 @@ pub fn load<A, B>(load_data: &LoadData,
// CORS (https://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10) // CORS (https://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10)
if let Some(ref c) = load_data.cors { if let Some(ref c) = load_data.cors {
if c.preflight { if c.preflight {
return Err( return Err(LoadError::new(doc_url,
LoadError::Cors( LoadErrorType::Cors,
doc_url,
"Preflight fetch inconsistent with main fetch".to_owned())); "Preflight fetch inconsistent with main fetch".to_owned()));
} else { } else {
// XXXManishearth There are some CORS-related steps here, // XXXManishearth There are some CORS-related steps here,
@ -844,9 +841,7 @@ pub fn load<A, B>(load_data: &LoadData,
let new_doc_url = match doc_url.join(&new_url) { let new_doc_url = match doc_url.join(&new_url) {
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => return Err(LoadError::new(doc_url, LoadErrorType::InvalidRedirect, e.to_string())),
return Err(LoadError::InvalidRedirect(doc_url, e.to_string()));
}
}; };
// According to https://tools.ietf.org/html/rfc7231#section-6.4.2, // According to https://tools.ietf.org/html/rfc7231#section-6.4.2,
@ -858,7 +853,7 @@ pub fn load<A, B>(load_data: &LoadData,
} }
if redirected_to.contains(&new_doc_url) { if redirected_to.contains(&new_doc_url) {
return Err(LoadError::InvalidRedirect(doc_url, "redirect loop".to_owned())); return Err(LoadError::new(doc_url, LoadErrorType::InvalidRedirect, "redirect loop".to_owned()));
} }
info!("redirecting to {}", new_doc_url); info!("redirecting to {}", new_doc_url);
@ -921,7 +916,7 @@ fn send_data<R: Read>(context: LoadContext,
loop { loop {
if cancel_listener.is_cancelled() { if cancel_listener.is_cancelled() {
let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned()))));
return; return;
} }

View file

@ -10,7 +10,7 @@ use net_traits::image_cache_thread::ImageResponder;
use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheCommand, ImageCacheThread, ImageState}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheCommand, ImageCacheThread, ImageState};
use net_traits::image_cache_thread::{ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, UsePlaceholder}; use net_traits::image_cache_thread::{ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, UsePlaceholder};
use net_traits::{AsyncResponseTarget, ControlMsg, LoadConsumer, LoadData, ResourceThread}; use net_traits::{AsyncResponseTarget, ControlMsg, LoadConsumer, LoadData, ResourceThread};
use net_traits::{ResponseAction, LoadContext}; use net_traits::{ResponseAction, LoadContext, NetworkError};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
@ -44,7 +44,7 @@ struct PendingLoad {
metadata: Option<ImageMetadata>, metadata: Option<ImageMetadata>,
// Once loading is complete, the result of the operation. // Once loading is complete, the result of the operation.
result: Option<Result<(), String>>, result: Option<Result<(), NetworkError>>,
listeners: Vec<ImageListener>, listeners: Vec<ImageListener>,
// The url being loaded. Do not forget that this may be several Mb // The url being loaded. Do not forget that this may be several Mb

View file

@ -21,7 +21,7 @@ use net_traits::LoadContext;
use net_traits::ProgressMsg::Done; use net_traits::ProgressMsg::Done;
use net_traits::{AsyncResponseTarget, Metadata, ProgressMsg, ResourceThread, ResponseAction}; use net_traits::{AsyncResponseTarget, Metadata, ProgressMsg, ResourceThread, ResponseAction};
use net_traits::{ControlMsg, CookieSource, LoadConsumer, LoadData, LoadResponse, ResourceId}; use net_traits::{ControlMsg, CookieSource, LoadConsumer, LoadData, LoadResponse, ResourceId};
use net_traits::{WebSocketCommunicate, WebSocketConnectData}; use net_traits::{NetworkError, WebSocketCommunicate, WebSocketConnectData};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::boxed::FnBox; use std::boxed::FnBox;
use std::cell::Cell; use std::cell::Cell;
@ -55,11 +55,11 @@ impl ProgressSender {
} }
} }
pub fn send_error(url: Url, err: String, start_chan: LoadConsumer) { pub fn send_error(url: Url, err: NetworkError, start_chan: LoadConsumer) {
let mut metadata: Metadata = Metadata::default(url); let mut metadata: Metadata = Metadata::default(url);
metadata.status = None; metadata.status = None;
if let Ok(p) = start_sending_opt(start_chan, metadata) { if let Ok(p) = start_sending_opt(start_chan, metadata, Some(err.clone())) {
p.send(Done(Err(err))).unwrap(); p.send(Done(Err(err))).unwrap();
} }
} }
@ -107,11 +107,14 @@ pub fn start_sending_sniffed_opt(start_chan: LoadConsumer, mut metadata: Metadat
metadata.content_type = Some(ContentType(Mime(mime_tp, mime_sb, vec![]))); metadata.content_type = Some(ContentType(Mime(mime_tp, mime_sb, vec![])));
} }
start_sending_opt(start_chan, metadata) start_sending_opt(start_chan, metadata, None)
} }
/// For use by loaders in responding to a Load message. /// For use by loaders in responding to a Load message.
fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result<ProgressSender, ()> { /// It takes an optional NetworkError, so that we can extract the SSL Validation errors
/// and take it to the HTML parser
fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata,
network_error: Option<NetworkError>) -> Result<ProgressSender, ()> {
match start_chan { match start_chan {
LoadConsumer::Channel(start_chan) => { LoadConsumer::Channel(start_chan) => {
let (progress_chan, progress_port) = ipc::channel().unwrap(); let (progress_chan, progress_port) = ipc::channel().unwrap();
@ -125,7 +128,13 @@ fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result<Pro
} }
} }
LoadConsumer::Listener(target) => { LoadConsumer::Listener(target) => {
target.invoke_with_listener(ResponseAction::HeadersAvailable(metadata)); match network_error {
Some(NetworkError::SslValidation(url)) => {
let error = NetworkError::SslValidation(url);
target.invoke_with_listener(ResponseAction::HeadersAvailable(Err(error)));
}
_ => target.invoke_with_listener(ResponseAction::HeadersAvailable(Ok(metadata))),
}
Ok(ProgressSender::Listener(target)) Ok(ProgressSender::Listener(target))
} }
} }
@ -339,7 +348,7 @@ impl ResourceManager {
"about" => from_factory(about_loader::factory), "about" => from_factory(about_loader::factory),
_ => { _ => {
debug!("resource_thread: no loader for scheme {}", load_data.url.scheme); debug!("resource_thread: no loader for scheme {}", load_data.url.scheme);
send_error(load_data.url, "no loader for scheme".to_owned(), consumer); send_error(load_data.url, NetworkError::Internal("no loader for scheme".to_owned()), consumer);
return return
} }
}; };

View file

@ -113,13 +113,13 @@ pub trait AsyncFetchListener {
/// A listener for asynchronous network events. Cancelling the underlying request is unsupported. /// A listener for asynchronous network events. Cancelling the underlying request is unsupported.
pub trait AsyncResponseListener { pub trait AsyncResponseListener {
/// The response headers for a request have been received. /// The response headers for a request have been received.
fn headers_available(&mut self, metadata: Metadata); fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>);
/// A portion of the response body has been received. This data is unavailable after /// A portion of the response body has been received. This data is unavailable after
/// this method returned, and must be stored accordingly. /// this method returned, and must be stored accordingly.
fn data_available(&mut self, payload: Vec<u8>); fn data_available(&mut self, payload: Vec<u8>);
/// The response is complete. If the provided status is an Err value, there is no guarantee /// The response is complete. If the provided status is an Err value, there is no guarantee
/// that the response body was completely read. /// that the response body was completely read.
fn response_complete(&mut self, status: Result<(), String>); fn response_complete(&mut self, status: Result<(), NetworkError>);
} }
/// Data for passing between threads/processes to indicate a particular action to /// Data for passing between threads/processes to indicate a particular action to
@ -127,11 +127,11 @@ pub trait AsyncResponseListener {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum ResponseAction { pub enum ResponseAction {
/// Invoke headers_available /// Invoke headers_available
HeadersAvailable(Metadata), HeadersAvailable(Result<Metadata, NetworkError>),
/// Invoke data_available /// Invoke data_available
DataAvailable(Vec<u8>), DataAvailable(Vec<u8>),
/// Invoke response_complete /// Invoke response_complete
ResponseComplete(Result<(), String>) ResponseComplete(Result<(), NetworkError>)
} }
impl ResponseAction { impl ResponseAction {
@ -376,7 +376,7 @@ pub enum ProgressMsg {
/// Binary data - there may be multiple of these /// Binary data - there may be multiple of these
Payload(Vec<u8>), Payload(Vec<u8>),
/// Indicates loading is complete, either successfully or not /// Indicates loading is complete, either successfully or not
Done(Result<(), String>) Done(Result<(), NetworkError>),
} }
/// Convenience function for synchronously loading a whole resource. /// Convenience function for synchronously loading a whole resource.
@ -384,7 +384,7 @@ pub fn load_whole_resource(context: LoadContext,
resource_thread: &ResourceThread, resource_thread: &ResourceThread,
url: Url, url: Url,
pipeline_id: Option<PipelineId>) pipeline_id: Option<PipelineId>)
-> Result<(Metadata, Vec<u8>), String> { -> Result<(Metadata, Vec<u8>), NetworkError> {
let (start_chan, start_port) = ipc::channel().unwrap(); let (start_chan, start_port) = ipc::channel().unwrap();
resource_thread.send(ControlMsg::Load(LoadData::new(context, url, pipeline_id), resource_thread.send(ControlMsg::Load(LoadData::new(context, url, pipeline_id),
LoadConsumer::Channel(start_chan), None)).unwrap(); LoadConsumer::Channel(start_chan), None)).unwrap();
@ -413,3 +413,13 @@ pub enum ConstellationMsg {
/// Queries whether a pipeline or its ancestors are private /// Queries whether a pipeline or its ancestors are private
IsPrivate(PipelineId, Sender<bool>), IsPrivate(PipelineId, Sender<bool>),
} }
/// Network errors that have to be exported out of the loaders
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum NetworkError {
/// Could be any of the internal errors, like unsupported scheme, load
/// cancellation, connection errors, etc.
Internal(String),
/// SSL validation error that has to be handled in the HTML parser
SslValidation(Url),
}

View file

@ -18,7 +18,7 @@ use hyper::header::{HeaderView, Headers};
use hyper::method::Method; use hyper::method::Method;
use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::mime::{Mime, SubLevel, TopLevel};
use hyper::status::StatusClass::Success; use hyper::status::StatusClass::Success;
use net_traits::{AsyncResponseListener, Metadata, ResponseAction}; use net_traits::{AsyncResponseListener, Metadata, NetworkError, ResponseAction};
use network_listener::{NetworkListener, PreInvoke}; use network_listener::{NetworkListener, PreInvoke};
use script_runtime::ScriptChan; use script_runtime::ScriptChan;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
@ -124,13 +124,15 @@ impl CORSRequest {
// This is shoe-horning the CORSReponse stuff into the rest of the async network // This is shoe-horning the CORSReponse stuff into the rest of the async network
// framework right now. It would be worth redesigning http_fetch to do this properly. // framework right now. It would be worth redesigning http_fetch to do this properly.
impl AsyncResponseListener for CORSContext { impl AsyncResponseListener for CORSContext {
fn headers_available(&mut self, _metadata: Metadata) { fn headers_available(&mut self, _metadata: Result<Metadata, NetworkError>) {
} }
fn data_available(&mut self, _payload: Vec<u8>) { fn data_available(&mut self, _payload: Vec<u8>) {
} }
fn response_complete(&mut self, _status: Result<(), String>) { fn response_complete(&mut self, _status: Result<(), NetworkError>) {
let response = self.response.take().unwrap(); let response = self.response.take().unwrap();
self.listener.response_available(response); self.listener.response_available(response);
} }

View file

@ -57,11 +57,11 @@ use layout_interface::{LayoutChan, LayoutRPC};
use libc; use libc;
use msg::constellation_msg::ConstellationChan; use msg::constellation_msg::ConstellationChan;
use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData}; use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData};
use net_traits::Metadata;
use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
use net_traits::response::HttpsState; use net_traits::response::HttpsState;
use net_traits::storage_thread::StorageType; use net_traits::storage_thread::StorageType;
use net_traits::{Metadata, NetworkError};
use offscreen_gl_context::GLLimits; use offscreen_gl_context::GLLimits;
use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::mem::ProfilerChan as MemProfilerChan;
use profile_traits::time::ProfilerChan as TimeProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan;
@ -287,6 +287,7 @@ no_jsmanaged_fields!(Size2D<T>);
no_jsmanaged_fields!(Arc<T>); no_jsmanaged_fields!(Arc<T>);
no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheChan, ImageCacheThread); no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheChan, ImageCacheThread);
no_jsmanaged_fields!(Metadata); no_jsmanaged_fields!(Metadata);
no_jsmanaged_fields!(NetworkError);
no_jsmanaged_fields!(Atom, Namespace, QualName); no_jsmanaged_fields!(Atom, Namespace, QualName);
no_jsmanaged_fields!(Trusted<T: Reflectable>); no_jsmanaged_fields!(Trusted<T: Reflectable>);
no_jsmanaged_fields!(PropertyDeclarationBlock); no_jsmanaged_fields!(PropertyDeclarationBlock);

View file

@ -24,7 +24,7 @@ use ipc_channel::ipc;
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
use layout_interface::{LayoutChan, Msg}; use layout_interface::{LayoutChan, Msg};
use msg::constellation_msg::ConstellationChan; use msg::constellation_msg::ConstellationChan;
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError};
use network_listener::{NetworkListener, PreInvoke}; use network_listener::{NetworkListener, PreInvoke};
use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg}; use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg};
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
@ -271,8 +271,8 @@ struct StylesheetContext {
impl PreInvoke for StylesheetContext {} impl PreInvoke for StylesheetContext {}
impl AsyncResponseListener for StylesheetContext { impl AsyncResponseListener for StylesheetContext {
fn headers_available(&mut self, metadata: Metadata) { fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>) {
self.metadata = Some(metadata); self.metadata = metadata.ok();
} }
fn data_available(&mut self, payload: Vec<u8>) { fn data_available(&mut self, payload: Vec<u8>) {
@ -280,9 +280,12 @@ impl AsyncResponseListener for StylesheetContext {
self.data.append(&mut payload); self.data.append(&mut payload);
} }
fn response_complete(&mut self, _status: Result<(), String>) { fn response_complete(&mut self, _status: Result<(), NetworkError>) {
let data = mem::replace(&mut self.data, vec!()); let data = mem::replace(&mut self.data, vec!());
let metadata = self.metadata.take().unwrap(); let metadata = match self.metadata.take() {
Some(meta) => meta,
None => return,
};
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8 as EncodingRef; let environment_encoding = UTF_8 as EncodingRef;
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s); let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);

View file

@ -33,7 +33,7 @@ use ipc_channel::ipc;
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
use js::jsapi::RootedValue; use js::jsapi::RootedValue;
use js::jsval::UndefinedValue; use js::jsval::UndefinedValue;
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError};
use network_listener::{NetworkListener, PreInvoke}; use network_listener::{NetworkListener, PreInvoke};
use script_runtime::ScriptChan; use script_runtime::ScriptChan;
use script_thread::MainThreadScriptChan; use script_thread::MainThreadScriptChan;
@ -124,7 +124,7 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[
#[derive(HeapSizeOf, JSTraceable)] #[derive(HeapSizeOf, JSTraceable)]
pub enum ScriptOrigin { pub enum ScriptOrigin {
Internal(DOMString, Url), Internal(DOMString, Url),
External(Result<(Metadata, Vec<u8>), String>), External(Result<(Metadata, Vec<u8>), NetworkError>),
} }
/// The context required for asynchronously loading an external script source. /// The context required for asynchronously loading an external script source.
@ -138,23 +138,25 @@ struct ScriptContext {
/// The initial URL requested. /// The initial URL requested.
url: Url, url: Url,
/// Indicates whether the request failed, and why /// Indicates whether the request failed, and why
status: Result<(), String> status: Result<(), NetworkError>
} }
impl AsyncResponseListener for ScriptContext { impl AsyncResponseListener for ScriptContext {
fn headers_available(&mut self, metadata: Metadata) { fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>) {
let status_code = match metadata.status { self.metadata = metadata.ok();
Some(RawStatus(c, _)) => c,
_ => 0 let status_code = self.metadata.as_ref().and_then(|m| {
}; match m.status {
Some(RawStatus(c, _)) => Some(c),
_ => None,
}
}).unwrap_or(0);
self.status = match status_code { self.status = match status_code {
0 => Err("No http status code received".to_owned()), 0 => Err(NetworkError::Internal("No http status code received".to_owned())),
200...299 => Ok(()), // HTTP ok status codes 200...299 => Ok(()), // HTTP ok status codes
_ => Err(format!("HTTP error code {}", status_code)) _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code)))
}; };
self.metadata = Some(metadata);
} }
fn data_available(&mut self, payload: Vec<u8>) { fn data_available(&mut self, payload: Vec<u8>) {
@ -164,7 +166,7 @@ impl AsyncResponseListener for ScriptContext {
} }
} }
fn response_complete(&mut self, status: Result<(), String>) { fn response_complete(&mut self, status: Result<(), NetworkError>) {
let load = status.and(self.status.clone()).map(|_| { let load = status.and(self.status.clone()).map(|_| {
let data = mem::replace(&mut self.data, vec!()); let data = mem::replace(&mut self.data, vec!());
let metadata = self.metadata.take().unwrap(); let metadata = self.metadata.take().unwrap();
@ -398,7 +400,7 @@ impl HTMLScriptElement {
let (source, external, url) = match load { let (source, external, url) = match load {
// Step 2.a. // Step 2.a.
ScriptOrigin::External(Err(e)) => { ScriptOrigin::External(Err(e)) => {
error!("error loading script {}", e); error!("error loading script {:?}", e);
self.dispatch_error_event(); self.dispatch_error_event();
return; return;
} }

View file

@ -26,7 +26,7 @@ use hyper::header::ContentType;
use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::mime::{Mime, SubLevel, TopLevel};
use js::jsapi::JSTracer; use js::jsapi::JSTracer;
use msg::constellation_msg::{PipelineId, SubpageId}; use msg::constellation_msg::{PipelineId, SubpageId};
use net_traits::{AsyncResponseListener, Metadata}; use net_traits::{AsyncResponseListener, Metadata, NetworkError};
use network_listener::PreInvoke; use network_listener::PreInvoke;
use parse::Parser; use parse::Parser;
use script_runtime::ScriptChan; use script_runtime::ScriptChan;
@ -36,6 +36,7 @@ use std::cell::UnsafeCell;
use std::default::Default; use std::default::Default;
use std::ptr; use std::ptr;
use url::Url; use url::Url;
use util::resource_files::read_resource_file;
#[must_root] #[must_root]
#[derive(JSTraceable, HeapSizeOf)] #[derive(JSTraceable, HeapSizeOf)]
@ -239,12 +240,23 @@ impl ParserContext {
} }
impl AsyncResponseListener for ParserContext { impl AsyncResponseListener for ParserContext {
fn headers_available(&mut self, metadata: Metadata) { fn headers_available(&mut self, meta_result: Result<Metadata, NetworkError>) {
let content_type = metadata.content_type.clone(); let mut is_ssl_error = false;
let metadata = match meta_result {
let parser = ScriptThread::page_fetch_complete(self.id.clone(), self.subpage.clone(), Ok(meta) => Some(meta),
metadata); Err(NetworkError::SslValidation(url)) => {
let parser = match parser { is_ssl_error = true;
let mut meta = Metadata::default(url);
let mime: Option<Mime> = "text/html".parse().ok();
meta.set_content_type(mime.as_ref());
Some(meta)
},
Err(_) => None,
};
let content_type = metadata.clone().and_then(|meta| meta.content_type);
let parser = match ScriptThread::page_fetch_complete(self.id.clone(),
self.subpage.clone(),
metadata) {
Some(parser) => parser, Some(parser) => parser,
None => return, None => return,
}; };
@ -274,7 +286,15 @@ impl AsyncResponseListener for ParserContext {
parser.parse_sync(); parser.parse_sync();
parser.set_plaintext_state(); parser.set_plaintext_state();
}, },
Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => {}, // Handle text/html Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { // Handle text/html
if is_ssl_error {
self.is_synthesized_document = true;
let page_bytes = read_resource_file("badcert.html").unwrap();
let page = String::from_utf8(page_bytes).unwrap();
parser.pending_input().borrow_mut().push(page);
parser.parse_sync();
}
},
Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml
Some(ContentType(Mime(toplevel, sublevel, _))) => { Some(ContentType(Mime(toplevel, sublevel, _))) => {
if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" { if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" {
@ -308,7 +328,7 @@ impl AsyncResponseListener for ParserContext {
} }
} }
fn response_complete(&mut self, status: Result<(), String>) { fn response_complete(&mut self, status: Result<(), NetworkError>) {
let parser = match self.parser.as_ref() { let parser = match self.parser.as_ref() {
Some(parser) => parser.root(), Some(parser) => parser.root(),
None => return, None => return,
@ -316,7 +336,7 @@ impl AsyncResponseListener for ParserContext {
parser.r().document().finish_load(LoadType::PageSource(self.url.clone())); parser.r().document().finish_load(LoadType::PageSource(self.url.clone()));
if let Err(err) = status { if let Err(err) = status {
debug!("Failed to load page URL {}, error: {}", self.url.serialize(), err); debug!("Failed to load page URL {}, error: {:?}", self.url.serialize(), err);
// TODO(Savago): we should send a notification to callers #5463. // TODO(Savago): we should send a notification to callers #5463.
} }

View file

@ -45,7 +45,7 @@ use js::jsapi::JS_ClearPendingException;
use js::jsapi::{JSContext, JS_ParseJSON, RootedValue}; use js::jsapi::{JSContext, JS_ParseJSON, RootedValue};
use js::jsval::{JSVal, NullValue, UndefinedValue}; use js::jsval::{JSVal, NullValue, UndefinedValue};
use net_traits::ControlMsg::Load; use net_traits::ControlMsg::Load;
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError};
use net_traits::{LoadConsumer, LoadContext, LoadData, ResourceCORSData, ResourceThread}; use net_traits::{LoadConsumer, LoadContext, LoadData, ResourceCORSData, ResourceThread};
use network_listener::{NetworkListener, PreInvoke}; use network_listener::{NetworkListener, PreInvoke};
use parse::html::{ParseContext, parse_html}; use parse::html::{ParseContext, parse_html};
@ -254,7 +254,7 @@ impl XMLHttpRequest {
resource_thread: ResourceThread, resource_thread: ResourceThread,
load_data: LoadData) { load_data: LoadData) {
impl AsyncResponseListener for XHRContext { impl AsyncResponseListener for XHRContext {
fn headers_available(&mut self, metadata: Metadata) { fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>) {
let xhr = self.xhr.root(); let xhr = self.xhr.root();
let rv = xhr.process_headers_available(self.cors_request.clone(), let rv = xhr.process_headers_available(self.cors_request.clone(),
self.gen_id, self.gen_id,
@ -269,7 +269,7 @@ impl XMLHttpRequest {
self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone());
} }
fn response_complete(&mut self, status: Result<(), String>) { fn response_complete(&mut self, status: Result<(), NetworkError>) {
let rv = self.xhr.root().process_response_complete(self.gen_id, status); let rv = self.xhr.root().process_response_complete(self.gen_id, status);
*self.sync_status.borrow_mut() = Some(rv); *self.sync_status.borrow_mut() = Some(rv);
} }
@ -870,7 +870,15 @@ impl XMLHttpRequest {
} }
fn process_headers_available(&self, cors_request: Option<CORSRequest>, fn process_headers_available(&self, cors_request: Option<CORSRequest>,
gen_id: GenerationId, metadata: Metadata) -> Result<(), Error> { gen_id: GenerationId, metadata: Result<Metadata, NetworkError>)
-> Result<(), Error> {
let metadata = match metadata {
Ok(meta) => meta,
Err(_) => {
self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network));
return Err(Error::Network);
},
};
let bypass_cross_origin_check = { let bypass_cross_origin_check = {
// We want to be able to do cross-origin requests in browser.html. // We want to be able to do cross-origin requests in browser.html.
@ -904,9 +912,7 @@ impl XMLHttpRequest {
*self.response_url.borrow_mut() = metadata.final_url.serialize_no_fragment(); *self.response_url.borrow_mut() = metadata.final_url.serialize_no_fragment();
// XXXManishearth Clear cache entries in case of a network error // XXXManishearth Clear cache entries in case of a network error
self.process_partial_response(XHRProgress::HeadersReceived(gen_id, self.process_partial_response(XHRProgress::HeadersReceived(gen_id, metadata.headers, metadata.status));
metadata.headers,
metadata.status));
Ok(()) Ok(())
} }
@ -914,7 +920,7 @@ impl XMLHttpRequest {
self.process_partial_response(XHRProgress::Loading(gen_id, ByteString::new(payload))); self.process_partial_response(XHRProgress::Loading(gen_id, ByteString::new(payload)));
} }
fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), String>) fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), NetworkError>)
-> ErrorResult { -> ErrorResult {
match status { match status {
Ok(()) => { Ok(()) => {

View file

@ -492,7 +492,7 @@ pub unsafe extern "C" fn shadow_check_callback(_cx: *mut JSContext,
} }
impl ScriptThread { impl ScriptThread {
pub fn page_fetch_complete(id: PipelineId, subpage: Option<SubpageId>, metadata: Metadata) pub fn page_fetch_complete(id: PipelineId, subpage: Option<SubpageId>, metadata: Option<Metadata>)
-> Option<ParserRoot> { -> Option<ParserRoot> {
SCRIPT_THREAD_ROOT.with(|root| { SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.borrow().unwrap() }; let script_thread = unsafe { &*root.borrow().unwrap() };
@ -1280,7 +1280,7 @@ impl ScriptThread {
/// We have received notification that the response associated with a load has completed. /// We have received notification that the response associated with a load has completed.
/// Kick off the document and frame tree creation process using the result. /// Kick off the document and frame tree creation process using the result.
fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>, fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
metadata: Metadata) -> Option<ParserRoot> { metadata: Option<Metadata>) -> Option<ParserRoot> {
let idx = self.incomplete_loads.borrow().iter().position(|load| { let idx = self.incomplete_loads.borrow().iter().position(|load| {
load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
}); });
@ -1289,7 +1289,7 @@ impl ScriptThread {
match idx { match idx {
Some(idx) => { Some(idx) => {
let load = self.incomplete_loads.borrow_mut().remove(idx); let load = self.incomplete_loads.borrow_mut().remove(idx);
Some(self.load(metadata, load)) metadata.map(|meta| self.load(meta, load))
} }
None => { None => {
assert!(self.closed_pipelines.borrow().contains(&id)); assert!(self.closed_pipelines.borrow().contains(&id));