Cargoify servo

This commit is contained in:
Jack Moffitt 2014-08-28 09:34:23 -06:00
parent db2f642c32
commit c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions

27
components/net/Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "net"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "net"
path = "lib.rs"
[dependencies.util]
path = "../util"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.http]
git = "https://github.com/servo/rust-http"
branch = "servo"
[dependencies.png]
git = "https://github.com/servo/rust-png"
[dependencies.stb_image]
git = "https://github.com/servo/rust-stb-image"
[dependencies.url]
git = "https://github.com/servo/rust-url"

View file

@ -0,0 +1,154 @@
/* 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/. */
use std::str;
use resource_task::{Done, Payload, Metadata, LoadData, LoadResponse, LoaderTask, start_sending};
use serialize::base64::FromBase64;
use http::headers::test_utils::from_stream_with_str;
use http::headers::content_type::MediaType;
use url::{percent_decode, NonRelativeSchemeData};
pub fn factory() -> LoaderTask {
proc(url, start_chan) {
// NB: we don't spawn a new task.
// Hypothesis: data URLs are too small for parallel base64 etc. to be worth it.
// Should be tested at some point.
load(url, start_chan)
}
}
fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
let url = load_data.url;
assert!("data" == url.scheme.as_slice());
let mut metadata = Metadata::default(url.clone());
// Split out content type and data.
let mut scheme_data = match url.scheme_data {
NonRelativeSchemeData(scheme_data) => scheme_data,
_ => fail!("Expected a non-relative scheme URL.")
};
match url.query {
Some(query) => {
scheme_data.push_str("?");
scheme_data.push_str(query.as_slice());
},
None => ()
}
let parts: Vec<&str> = scheme_data.as_slice().splitn(',', 1).collect();
if parts.len() != 2 {
start_sending(start_chan, metadata).send(Done(Err("invalid data uri".to_string())));
return;
}
// ";base64" must come at the end of the content type, per RFC 2397.
// rust-http will fail to parse it because there's no =value part.
let mut is_base64 = false;
let mut ct_str = parts[0];
if ct_str.ends_with(";base64") {
is_base64 = true;
ct_str = ct_str.slice_to(ct_str.as_bytes().len() - 7);
}
// Parse the content type using rust-http.
// FIXME: this can go into an infinite loop! (rust-http #25)
let content_type: Option<MediaType> = from_stream_with_str(ct_str);
metadata.set_content_type(&content_type);
let progress_chan = start_sending(start_chan, metadata);
let bytes = percent_decode(parts[1].as_bytes());
if is_base64 {
// FIXME(#2909): Its unclear what to do with non-alphabet characters,
// but Acid 3 apparently depends on spaces being ignored.
let bytes = bytes.move_iter().filter(|&b| b != ' ' as u8).collect::<Vec<u8>>();
// FIXME(#2877): use bytes.as_slice().from_base64() when we upgrade to a Rust version
// that includes https://github.com/rust-lang/rust/pull/15810
let fake_utf8 = unsafe { str::raw::from_utf8(bytes.as_slice()) };
match fake_utf8.from_base64() {
Err(..) => {
progress_chan.send(Done(Err("non-base64 data uri".to_string())));
}
Ok(data) => {
progress_chan.send(Payload(data));
progress_chan.send(Done(Ok(())));
}
}
} else {
progress_chan.send(Payload(bytes));
progress_chan.send(Done(Ok(())));
}
}
#[cfg(test)]
fn assert_parse(url: &'static str,
content_type: Option<(String, String)>,
charset: Option<String>,
data: Option<Vec<u8>>) {
use std::comm;
use url::Url;
let (start_chan, start_port) = comm::channel();
load(LoadData::new(Url::parse(url).unwrap()), start_chan);
let response = start_port.recv();
assert_eq!(&response.metadata.content_type, &content_type);
assert_eq!(&response.metadata.charset, &charset);
let progress = response.progress_port.recv();
match data {
None => {
assert_eq!(progress, Done(Err("invalid data uri".to_string())));
}
Some(dat) => {
assert_eq!(progress, Payload(dat));
assert_eq!(response.progress_port.recv(), Done(Ok(())));
}
}
}
#[test]
fn empty_invalid() {
assert_parse("data:", None, None, None);
}
#[test]
fn plain() {
assert_parse("data:,hello%20world", None, None, Some(b"hello world".iter().map(|&x| x).collect()));
}
#[test]
fn plain_ct() {
assert_parse("data:text/plain,hello",
Some(("text".to_string(), "plain".to_string())), None, Some(b"hello".iter().map(|&x| x).collect()));
}
#[test]
fn plain_charset() {
assert_parse("data:text/plain;charset=latin1,hello",
Some(("text".to_string(), "plain".to_string())), Some("latin1".to_string()), Some(b"hello".iter().map(|&x| x).collect()));
}
#[test]
fn base64() {
assert_parse("data:;base64,C62+7w==", None, None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
}
#[test]
fn base64_ct() {
assert_parse("data:application/octet-stream;base64,C62+7w==",
Some(("application".to_string(), "octet-stream".to_string())), None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
}
#[test]
fn base64_charset() {
assert_parse("data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==",
Some(("text".to_string(), "plain".to_string())), Some("koi8-r".to_string()),
Some(vec!(0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4)));
}

View file

@ -0,0 +1,316 @@
/* 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/. */
//! An implementation of the [CORS preflight cache](http://fetch.spec.whatwg.org/#cors-preflight-cache)
//! For now this library is XHR-specific.
//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
//! the request mode should be and compare with the fetch spec
//! This library will eventually become the core of the Fetch crate
//! with CORSRequest being expanded into FetchRequest (etc)
use http::method::Method;
use std::ascii::StrAsciiExt;
use std::comm::{Sender, Receiver, channel};
use time;
use time::{now, Timespec};
use url::Url;
/// Union type for CORS cache entries
///
/// Each entry might pertain to a header or method
#[deriving(Clone)]
pub enum HeaderOrMethod {
HeaderData(String),
MethodData(Method)
}
impl HeaderOrMethod {
fn match_header(&self, header_name: &str) -> bool {
match *self {
HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name),
_ => false
}
}
fn match_method(&self, method: &Method) -> bool {
match *self {
MethodData(ref m) => m == method,
_ => false
}
}
}
/// An entry in the CORS cache
#[deriving(Clone)]
pub struct CORSCacheEntry {
pub origin: Url,
pub url: Url,
pub max_age: uint,
pub credentials: bool,
pub header_or_method: HeaderOrMethod,
created: Timespec
}
impl CORSCacheEntry {
fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
CORSCacheEntry {
origin: origin,
url: url,
max_age: max_age,
credentials: credentials,
header_or_method: header_or_method,
created: time::now().to_timespec()
}
}
}
/// Properties of Request required to cache match.
pub struct CacheRequestDetails {
pub origin: Url,
pub destination: Url,
pub credentials: bool
}
/// Trait for a generic CORS Cache
pub trait CORSCache {
/// [Clear the cache](http://fetch.spec.whatwg.org/#concept-cache-clear)
fn clear (&mut self, request: CacheRequestDetails);
/// Remove old entries
fn cleanup(&mut self);
/// Returns true if an entry with a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool;
/// Updates max age if an entry for a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
///
/// If not, it will insert an equivalent entry
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool;
/// Returns true if an entry with a [matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool;
/// Updates max age if an entry for [a matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
///
/// If not, it will insert an equivalent entry
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool;
/// Insert an entry
fn insert(&mut self, entry: CORSCacheEntry);
}
/// A simple, vector-based CORS Cache
#[deriving(Clone)]
#[unstable = "This might later be replaced with a HashMap-like entity, though that requires a separate Origin struct"]
pub struct BasicCORSCache(Vec<CORSCacheEntry>);
impl BasicCORSCache {
fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails, header_name: &str) -> Option<&'a mut CORSCacheEntry> {
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
e.origin.host() == request.origin.host() &&
e.origin.port() == request.origin.port() &&
e.url == request.destination &&
e.credentials == request.credentials &&
e.header_or_method.match_header(header_name));
entry
}
fn find_entry_by_method<'a>(&'a mut self, request: &CacheRequestDetails, method: Method) -> Option<&'a mut CORSCacheEntry> {
// we can take the method from CORSRequest itself
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
e.origin.host() == request.origin.host() &&
e.origin.port() == request.origin.port() &&
e.url == request.destination &&
e.credentials == request.credentials &&
e.header_or_method.match_method(&method));
entry
}
}
impl CORSCache for BasicCORSCache {
/// http://fetch.spec.whatwg.org/#concept-cache-clear
#[allow(dead_code)]
fn clear (&mut self, request: CacheRequestDetails) {
let BasicCORSCache(buf) = self.clone();
let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
*self = BasicCORSCache(new_buf);
}
// Remove old entries
fn cleanup(&mut self) {
let BasicCORSCache(buf) = self.clone();
let now = time::now().to_timespec();
let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect();
*self = BasicCORSCache(new_buf);
}
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
self.find_entry_by_header(&request, header_name).is_some()
}
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
Some(_) => true,
None => {
self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
request.credentials, HeaderData(header_name.to_string())));
false
}
}
}
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
self.find_entry_by_method(&request, method).is_some()
}
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
Some(_) => true,
None => {
self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
request.credentials, MethodData(method)));
false
}
}
}
fn insert(&mut self, entry: CORSCacheEntry) {
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
buf.push(entry);
}
}
/// Various messages that can be sent to a CORSCacheTask
pub enum CORSCacheTaskMsg {
Clear(CacheRequestDetails, Sender<()>),
Cleanup(Sender<()>),
MatchHeader(CacheRequestDetails, String, Sender<bool>),
MatchHeaderUpdate(CacheRequestDetails, String, uint, Sender<bool>),
MatchMethod(CacheRequestDetails, Method, Sender<bool>),
MatchMethodUpdate(CacheRequestDetails, Method, uint, Sender<bool>),
Insert(CORSCacheEntry, Sender<()>),
ExitMsg
}
/// A Sender to a CORSCacheTask
///
/// This can be used as a CORS Cache.
/// The methods on this type block until they can run, and it behaves similar to a mutex
pub type CORSCacheSender = Sender<CORSCacheTaskMsg>;
impl CORSCache for CORSCacheSender {
fn clear (&mut self, request: CacheRequestDetails) {
let (tx, rx) = channel();
self.send(Clear(request, tx));
let _ = rx.recv_opt();
}
fn cleanup(&mut self) {
let (tx, rx) = channel();
self.send(Cleanup(tx));
let _ = rx.recv_opt();
}
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
let (tx, rx) = channel();
self.send(MatchHeader(request, header_name.to_string(), tx));
rx.recv_opt().unwrap_or(false)
}
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
let (tx, rx) = channel();
self.send(MatchHeaderUpdate(request, header_name.to_string(), new_max_age, tx));
rx.recv_opt().unwrap_or(false)
}
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
let (tx, rx) = channel();
self.send(MatchMethod(request, method, tx));
rx.recv_opt().unwrap_or(false)
}
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
let (tx, rx) = channel();
self.send(MatchMethodUpdate(request, method, new_max_age, tx));
rx.recv_opt().unwrap_or(false)
}
fn insert(&mut self, entry: CORSCacheEntry) {
let (tx, rx) = channel();
self.send(Insert(entry, tx));
let _ = rx.recv_opt();
}
}
/// A simple task-based CORS Cache that can be sent messages
///
/// #Example
/// ```
/// let task = CORSCacheTask::new();
/// let builder = TaskBuilder::new().named("XHRTask");
/// let mut sender = task.get_sender();
/// builder.spawn(proc() { task.run() });
/// sender.insert(CORSCacheEntry::new(/* parameters here */));
/// ```
pub struct CORSCacheTask {
receiver: Receiver<CORSCacheTaskMsg>,
cache: BasicCORSCache,
sender: CORSCacheSender
}
impl CORSCacheTask {
pub fn new() -> CORSCacheTask {
let (tx, rx) = channel();
CORSCacheTask {
receiver: rx,
cache: BasicCORSCache(vec![]),
sender: tx
}
}
/// Provides a sender to the cache task
pub fn get_sender(&self) -> CORSCacheSender {
self.sender.clone()
}
/// Runs the cache task
/// This blocks the current task, so it is advised
/// to spawn a new task for this
/// Send ExitMsg to the associated Sender to exit
pub fn run(&mut self) {
loop {
match self.receiver.recv() {
Clear(request, tx) => {
self.cache.clear(request);
tx.send(());
},
Cleanup(tx) => {
self.cache.cleanup();
tx.send(());
},
MatchHeader(request, header, tx) => {
tx.send(self.cache.match_header(request, header.as_slice()));
},
MatchHeaderUpdate(request, header, new_max_age, tx) => {
tx.send(self.cache.match_header_and_update(request, header.as_slice(), new_max_age));
},
MatchMethod(request, method, tx) => {
tx.send(self.cache.match_method(request, method));
},
MatchMethodUpdate(request, method, new_max_age, tx) => {
tx.send(self.cache.match_method_and_update(request, method, new_max_age));
},
Insert(entry, tx) => {
self.cache.insert(entry);
tx.send(());
},
ExitMsg => break
}
}
}
}

View file

@ -0,0 +1,149 @@
/* 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/. */
use url::Url;
use http::method::{Get, Method};
use http::headers::request::HeaderCollection;
use fetch::cors_cache::CORSCache;
use fetch::response::Response;
/// A [request context](http://fetch.spec.whatwg.org/#concept-request-context)
pub enum Context {
Audio, Beacon, CSPreport, Download, Embed, Eventsource,
Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
ImageSet, Import, Internal, Location, Manifest, Object, Ping,
Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
Style, Track, Video, Worker, XMLHttpRequest, XSLT
}
/// A [request context frame type](http://fetch.spec.whatwg.org/#concept-request-context-frame-type)
pub enum ContextFrameType {
Auxiliary,
TopLevel,
Nested,
ContextNone
}
/// A [referer](http://fetch.spec.whatwg.org/#concept-request-referrer)
pub enum Referer {
RefererNone,
Client,
RefererUrl(Url)
}
/// A [request mode](http://fetch.spec.whatwg.org/#concept-request-mode)
pub enum RequestMode {
SameOrigin,
NoCORS,
CORSMode,
ForcedPreflightMode
}
/// Request [credentials mode](http://fetch.spec.whatwg.org/#concept-request-credentials-mode)
pub enum CredentialsMode {
Omit,
CredentialsSameOrigin,
Include
}
/// [Response tainting](http://fetch.spec.whatwg.org/#concept-request-response-tainting)
pub enum ResponseTainting {
Basic,
CORSTainting,
Opaque
}
/// A [Request](http://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
pub struct Request {
pub method: Method,
pub url: Url,
pub headers: HeaderCollection,
pub unsafe_request: bool,
pub body: Option<Vec<u8>>,
pub preserve_content_codings: bool,
// pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
// not the entire scope to avoid the libscript dependency
pub skip_service_worker: bool,
pub context: Context,
pub context_frame_type: ContextFrameType,
pub origin: Option<Url>,
pub force_origin_header: bool,
pub same_origin_data: bool,
pub referer: Referer,
pub authentication: bool,
pub sync: bool,
pub mode: RequestMode,
pub credentials_mode: CredentialsMode,
pub use_url_credentials: bool,
pub manual_redirect: bool,
pub redirect_count: uint,
pub response_tainting: ResponseTainting,
pub cache: Option<Box<CORSCache>>
}
impl Request {
pub fn new(url: Url, context: Context) -> Request {
Request {
method: Get,
url: url,
headers: HeaderCollection::new(),
unsafe_request: false,
body: None,
preserve_content_codings: false,
skip_service_worker: false,
context: context,
context_frame_type: ContextNone,
origin: None,
force_origin_header: false,
same_origin_data: false,
referer: Client,
authentication: false,
sync: false,
mode: NoCORS,
credentials_mode: Omit,
use_url_credentials: false,
manual_redirect: false,
redirect_count: 0,
response_tainting: Basic,
cache: None
}
}
/// [Basic fetch](http://fetch.spec.whatwg.org#basic-fetch)
pub fn basic_fetch(&mut self) -> Response {
match self.url.scheme.as_slice() {
"about" => match self.url.non_relative_scheme_data() {
Some(s) if s.as_slice() == "blank" => {
let mut response = Response::new();
let _ = response.headers.insert_raw("Content-Type".to_string(), b"text/html;charset=utf-8");
response
},
_ => Response::network_error()
},
"http" | "https" => {
self.http_fetch(false, false, false)
},
"blob" | "data" | "file" | "ftp" => {
// XXXManishearth handle these
fail!("Unimplemented scheme for Fetch")
},
_ => Response::network_error()
}
}
// [HTTP fetch](http://fetch.spec.whatwg.org#http-fetch)
pub fn http_fetch(&mut self, _cors_flag: bool, cors_preflight_flag: bool, _authentication_fetch_flag: bool) -> Response {
let response = Response::new();
// TODO: Service worker fetch
// Step 3
// Substep 1
self.skip_service_worker = true;
// Substep 2
if cors_preflight_flag {
// XXXManishearth stuff goes here
}
response
}
}

View file

@ -0,0 +1,144 @@
/* 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/. */
use url::Url;
use http::status::{Status, UnregisteredStatus};
use StatusOk = http::status::Ok;
use http::headers::HeaderEnum;
use http::headers::response::HeaderCollection;
use std::ascii::OwnedStrAsciiExt;
use std::comm::Receiver;
/// [Response type](http://fetch.spec.whatwg.org/#concept-response-type)
#[deriving(Clone, PartialEq)]
pub enum ResponseType {
Basic,
CORS,
Default,
Error,
Opaque
}
/// [Response termination reason](http://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[deriving(Clone)]
pub enum TerminationReason {
EndUserAbort,
Fatal,
Timeout
}
/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
#[deriving(Clone)]
pub enum ResponseBody {
Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
Receiving(Vec<u8>),
Done(Vec<u8>),
}
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
pub enum ResponseMsg {
Chunk(Vec<u8>),
Finished,
Errored
}
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
pub struct ResponseLoader {
response: Response,
chan: Receiver<ResponseMsg>
}
/// A [Response](http://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[deriving(Clone)]
pub struct Response {
pub response_type: ResponseType,
pub termination_reason: Option<TerminationReason>,
pub url: Option<Url>,
pub status: Status,
pub headers: HeaderCollection,
pub body: ResponseBody,
/// [Internal response](http://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response is a filtered response
pub internal_response: Option<Box<Response>>,
}
impl Response {
pub fn new() -> Response {
Response {
response_type: Default,
termination_reason: None,
url: None,
status: StatusOk,
headers: HeaderCollection::new(),
body: Empty,
internal_response: None
}
}
pub fn network_error() -> Response {
Response {
response_type: Error,
termination_reason: None,
url: None,
status: UnregisteredStatus(0, "".to_string()),
headers: HeaderCollection::new(),
body: Empty,
internal_response: None
}
}
pub fn is_network_error(&self) -> bool {
match self.response_type {
Error => true,
_ => false
}
}
/// Convert to a filtered response, of type `filter_type`.
/// Do not use with type Error or Default
pub fn to_filtered(self, filter_type: ResponseType) -> Response {
assert!(filter_type != Error);
assert!(filter_type != Default);
if self.is_network_error() {
return self;
}
let old_headers = self.headers.clone();
let mut response = self.clone();
response.internal_response = Some(box self);
match filter_type {
Default | Error => unreachable!(),
Basic => {
let mut headers = HeaderCollection::new();
for h in old_headers.iter() {
match h.header_name().into_ascii_lower().as_slice() {
"set-cookie" | "set-cookie2" => {},
_ => headers.insert(h)
}
}
response.headers = headers;
response.response_type = filter_type;
},
CORS => {
let mut headers = HeaderCollection::new();
for h in old_headers.iter() {
match h.header_name().into_ascii_lower().as_slice() {
"cache-control" | "content-language" |
"content-type" | "expires" | "last-modified" | "Pragma" => {},
// XXXManishearth handle Access-Control-Expose-Headers
_ => headers.insert(h)
}
}
response.headers = headers;
response.response_type = filter_type;
},
Opaque => {
response.headers = HeaderCollection::new();
response.status = UnregisteredStatus(0, "".to_string());
response.body = Empty;
}
}
response
}
}

View file

@ -0,0 +1,50 @@
/* 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/. */
use resource_task::{ProgressMsg, Metadata, Payload, Done, LoaderTask, start_sending};
use std::io;
use std::io::File;
use servo_util::task::spawn_named;
static READ_SIZE: uint = 8192;
fn read_all(reader: &mut io::Stream, progress_chan: &Sender<ProgressMsg>)
-> Result<(), String> {
loop {
let mut buf = vec!();
match reader.push_at_least(READ_SIZE, READ_SIZE, &mut buf) {
Ok(_) => progress_chan.send(Payload(buf)),
Err(e) => match e.kind {
io::EndOfFile => {
if buf.len() > 0 {
progress_chan.send(Payload(buf));
}
return Ok(());
}
_ => return Err(e.desc.to_string()),
}
}
}
}
pub fn factory() -> LoaderTask {
let f: LoaderTask = proc(load_data, start_chan) {
let url = load_data.url;
assert!("file" == url.scheme.as_slice());
let progress_chan = start_sending(start_chan, Metadata::default(url.clone()));
spawn_named("file_loader", proc() {
match File::open_mode(&Path::new(url.serialize_path().unwrap()), io::Open, io::Read) {
Ok(ref mut reader) => {
let res = read_all(reader as &mut io::Stream, &progress_chan);
progress_chan.send(Done(res));
}
Err(e) => {
progress_chan.send(Done(Err(e.desc.to_string())));
}
};
});
};
f
}

View file

@ -0,0 +1,167 @@
/* 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/. */
use resource_task::{Metadata, Payload, Done, LoadResponse, LoadData, LoaderTask, start_sending_opt};
use std::collections::hashmap::HashSet;
use http::client::{RequestWriter, NetworkStream};
use http::headers::HeaderEnum;
use std::io::Reader;
use servo_util::task::spawn_named;
use url::Url;
pub fn factory() -> LoaderTask {
let f: LoaderTask = proc(url, start_chan) {
spawn_named("http_loader", proc() load(url, start_chan))
};
f
}
fn send_error(url: Url, err: String, start_chan: Sender<LoadResponse>) {
match start_sending_opt(start_chan, Metadata::default(url)) {
Ok(p) => p.send(Done(Err(err))),
_ => {}
};
}
fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
// FIXME: At the time of writing this FIXME, servo didn't have any central
// location for configuration. If you're reading this and such a
// repository DOES exist, please update this constant to use it.
let max_redirects = 50u;
let mut iters = 0u;
let mut url = load_data.url.clone();
let mut redirected_to = HashSet::new();
// Loop to handle redirects.
loop {
iters = iters + 1;
if iters > max_redirects {
send_error(url, "too many redirects".to_string(), start_chan);
return;
}
if redirected_to.contains(&url) {
send_error(url, "redirect loop".to_string(), start_chan);
return;
}
redirected_to.insert(url.clone());
match url.scheme.as_slice() {
"http" | "https" => {}
_ => {
let s = format!("{:s} request, but we don't support that scheme", url.scheme);
send_error(url, s, start_chan);
return;
}
}
info!("requesting {:s}", url.serialize());
let request = RequestWriter::<NetworkStream>::new(load_data.method.clone(), url.clone());
let mut writer = match request {
Ok(w) => box w,
Err(e) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
};
// Preserve the `host` header set automatically by RequestWriter.
let host = writer.headers.host.clone();
writer.headers = box load_data.headers.clone();
writer.headers.host = host;
if writer.headers.accept_encoding.is_none() {
// We currently don't support HTTP Compression (FIXME #2587)
writer.headers.accept_encoding = Some(String::from_str("identity".as_slice()))
}
match load_data.data {
Some(ref data) => {
writer.headers.content_length = Some(data.len());
match writer.write(data.as_slice()) {
Err(e) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
_ => {}
}
},
_ => {}
}
let mut response = match writer.read_response() {
Ok(r) => r,
Err((_, e)) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
};
// Dump headers, but only do the iteration if info!() is enabled.
info!("got HTTP response {:s}, headers:", response.status.to_string());
info!("{:?}",
for header in response.headers.iter() {
info!(" - {:s}: {:s}", header.header_name(), header.header_value());
});
if 3 == (response.status.code() / 100) {
match response.headers.location {
Some(new_url) => {
// CORS (http://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10)
match load_data.cors {
Some(ref c) => {
if c.preflight {
// The preflight lied
send_error(url, "Preflight fetch inconsistent with main fetch".to_string(), start_chan);
return;
} else {
// XXXManishearth There are some CORS-related steps here,
// but they don't seem necessary until credentials are implemented
}
}
_ => {}
}
info!("redirecting to {:s}", new_url.serialize());
url = new_url;
continue;
}
None => ()
}
}
let mut metadata = Metadata::default(url);
metadata.set_content_type(&response.headers.content_type);
metadata.headers = Some(*response.headers.clone());
metadata.status = response.status.clone();
let progress_chan = match start_sending_opt(start_chan, metadata) {
Ok(p) => p,
_ => return
};
loop {
let mut buf = Vec::with_capacity(1024);
unsafe { buf.set_len(1024); }
match response.read(buf.as_mut_slice()) {
Ok(len) => {
unsafe { buf.set_len(len); }
if progress_chan.send_opt(Payload(buf)).is_err() {
// The send errors when the receiver is out of scope,
// which will happen if the fetch has timed out (or has been aborted)
// so we don't need to continue with the loading of the file here.
return;
}
}
Err(_) => {
let _ = progress_chan.send_opt(Done(Ok(())));
break;
}
}
}
// We didn't get redirected.
break;
}
}

View file

@ -0,0 +1,67 @@
/* 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/. */
use std::iter::range_step;
use stb_image = stb_image::image;
use png;
// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
pub type Image = png::Image;
static TEST_IMAGE: &'static [u8] = include_bin!("test.jpeg");
pub fn test_image_bin() -> Vec<u8> {
TEST_IMAGE.iter().map(|&x| x).collect()
}
// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
fn byte_swap(data: &mut [u8]) {
let length = data.len();
for i in range_step(0, length, 4) {
let r = data[i + 2];
data[i + 2] = data[i + 0];
data[i + 0] = r;
}
}
pub fn load_from_memory(buffer: &[u8]) -> Option<Image> {
if buffer.len() == 0 {
return None;
}
if png::is_png(buffer) {
match png::load_png_from_memory(buffer) {
Ok(mut png_image) => {
match png_image.pixels {
png::RGB8(ref mut data) | png::RGBA8(ref mut data) => {
byte_swap(data.as_mut_slice());
}
_ => {}
}
Some(png_image)
}
Err(_err) => None,
}
} else {
// For non-png images, we use stb_image
// Can't remember why we do this. Maybe it's what cairo wants
static FORCE_DEPTH: uint = 4;
match stb_image::load_from_memory_with_depth(buffer, FORCE_DEPTH, true) {
stb_image::ImageU8(mut image) => {
assert!(image.depth == 4);
byte_swap(image.data.as_mut_slice());
Some(png::Image {
width: image.width as u32,
height: image.height as u32,
pixels: png::RGBA8(image.data)
})
}
stb_image::ImageF32(_image) => fail!("HDR images not implemented"),
stb_image::Error(_) => None
}
}
}

View file

@ -0,0 +1,109 @@
/* 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/. */
use image::base::Image;
use image_cache_task::{ImageReady, ImageNotReady, ImageFailed};
use local_image_cache::LocalImageCache;
use geom::size::Size2D;
use std::mem;
use sync::{Arc, Mutex};
use url::Url;
// FIXME: Nasty coupling here This will be a problem if we want to factor out image handling from
// the network stack. This should probably be factored out into an interface and use dependency
// injection.
/// A struct to store image data. The image will be loaded once the first time it is requested,
/// and an Arc will be stored. Clones of this Arc are given out on demand.
#[deriving(Clone)]
pub struct ImageHolder {
url: Url,
image: Option<Arc<Box<Image>>>,
cached_size: Size2D<int>,
local_image_cache: Arc<Mutex<LocalImageCache>>,
}
impl ImageHolder {
pub fn new(url: Url, local_image_cache: Arc<Mutex<LocalImageCache>>) -> ImageHolder {
debug!("ImageHolder::new() {}", url.serialize());
let holder = ImageHolder {
url: url,
image: None,
cached_size: Size2D(0,0),
local_image_cache: local_image_cache.clone(),
};
// Tell the image cache we're going to be interested in this url
// FIXME: These two messages must be sent to prep an image for use
// but they are intended to be spread out in time. Ideally prefetch
// should be done as early as possible and decode only once we
// are sure that the image will be used.
{
let val = holder.local_image_cache.lock();
let mut local_image_cache = val;
local_image_cache.prefetch(&holder.url);
local_image_cache.decode(&holder.url);
}
holder
}
/// This version doesn't perform any computation, but may be stale w.r.t. newly-available image
/// data that determines size.
///
/// The intent is that the impure version is used during layout when dimensions are used for
/// computing layout.
pub fn size(&self) -> Size2D<int> {
self.cached_size
}
/// Query and update the current image size.
pub fn get_size(&mut self) -> Option<Size2D<int>> {
debug!("get_size() {}", self.url.serialize());
self.get_image().map(|img| {
self.cached_size = Size2D(img.width as int,
img.height as int);
self.cached_size.clone()
})
}
pub fn get_image_if_present(&self) -> Option<Arc<Box<Image>>> {
debug!("get_image_if_present() {}", self.url.serialize());
self.image.clone()
}
pub fn get_image(&mut self) -> Option<Arc<Box<Image>>> {
debug!("get_image() {}", self.url.serialize());
// If this is the first time we've called this function, load
// the image and store it for the future
if self.image.is_none() {
let port = {
let val = self.local_image_cache.lock();
let mut local_image_cache = val;
local_image_cache.get_image(&self.url)
};
match port.recv() {
ImageReady(image) => {
self.image = Some(image);
}
ImageNotReady => {
debug!("image not ready for {:s}", self.url.serialize());
}
ImageFailed => {
debug!("image decoding failed for {:s}", self.url.serialize());
}
}
}
// Clone isn't pure so we have to swap out the mutable image option
let image = mem::replace(&mut self.image, None);
let result = image.clone();
mem::replace(&mut self.image, image);
return result;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,993 @@
/* 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/. */
use image::base::{Image, load_from_memory};
use resource_task;
use resource_task::{LoadData, ResourceTask};
use std::comm::{channel, Receiver, Sender};
use std::collections::hashmap::HashMap;
use std::mem::replace;
use std::task::spawn;
use std::result;
use sync::{Arc, Mutex};
use serialize::{Encoder, Encodable};
use url::Url;
pub enum Msg {
/// Tell the cache that we may need a particular image soon. Must be posted
/// before Decode
Prefetch(Url),
/// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
Decode(Url),
/// Request an Image object for a URL. If the image is not is not immediately
/// available then ImageNotReady is returned.
GetImage(Url, Sender<ImageResponseMsg>),
/// Wait for an image to become available (or fail to load).
WaitForImage(Url, Sender<ImageResponseMsg>),
/// Clients must wait for a response before shutting down the ResourceTask
Exit(Sender<()>),
/// Used by the prefetch tasks to post back image binaries
StorePrefetchedImageData(Url, Result<Vec<u8>, ()>),
/// Used by the decoder tasks to post decoded images back to the cache
StoreImage(Url, Option<Arc<Box<Image>>>),
/// For testing
WaitForStore(Sender<()>),
/// For testing
WaitForStorePrefetched(Sender<()>),
}
#[deriving(Clone)]
pub enum ImageResponseMsg {
ImageReady(Arc<Box<Image>>),
ImageNotReady,
ImageFailed
}
impl PartialEq for ImageResponseMsg {
fn eq(&self, other: &ImageResponseMsg) -> bool {
match (self, other) {
(&ImageReady(..), &ImageReady(..)) => fail!("unimplemented comparison"),
(&ImageNotReady, &ImageNotReady) => true,
(&ImageFailed, &ImageFailed) => true,
(&ImageReady(..), _) | (&ImageNotReady, _) | (&ImageFailed, _) => false
}
}
}
#[deriving(Clone)]
pub struct ImageCacheTask {
chan: Sender<Msg>,
}
impl<E, S: Encoder<E>> Encodable<S, E> for ImageCacheTask {
fn encode(&self, _: &mut S) -> Result<(), E> {
Ok(())
}
}
type DecoderFactory = fn() -> proc(&[u8]) -> Option<Image>;
impl ImageCacheTask {
pub fn new(resource_task: ResourceTask) -> ImageCacheTask {
let (chan, port) = channel();
let chan_clone = chan.clone();
spawn(proc() {
let mut cache = ImageCache {
resource_task: resource_task,
port: port,
chan: chan_clone,
state_map: HashMap::new(),
wait_map: HashMap::new(),
need_exit: None
};
cache.run();
});
ImageCacheTask {
chan: chan,
}
}
pub fn new_sync(resource_task: ResourceTask) -> ImageCacheTask {
let (chan, port) = channel();
spawn(proc() {
let inner_cache = ImageCacheTask::new(resource_task);
loop {
let msg: Msg = port.recv();
match msg {
GetImage(url, response) => {
inner_cache.send(WaitForImage(url, response));
}
Exit(response) => {
inner_cache.send(Exit(response));
break;
}
msg => inner_cache.send(msg)
}
}
});
ImageCacheTask {
chan: chan,
}
}
}
struct ImageCache {
/// A handle to the resource task for fetching the image binaries
resource_task: ResourceTask,
/// The port on which we'll receive client requests
port: Receiver<Msg>,
/// A copy of the shared chan to give to child tasks
chan: Sender<Msg>,
/// The state of processsing an image for a URL
state_map: HashMap<Url, ImageState>,
/// List of clients waiting on a WaitForImage response
wait_map: HashMap<Url, Arc<Mutex<Vec<Sender<ImageResponseMsg>>>>>,
need_exit: Option<Sender<()>>,
}
#[deriving(Clone)]
enum ImageState {
Init,
Prefetching(AfterPrefetch),
Prefetched(Vec<u8>),
Decoding,
Decoded(Arc<Box<Image>>),
Failed
}
#[deriving(Clone)]
enum AfterPrefetch {
DoDecode,
DoNotDecode
}
impl ImageCache {
pub fn run(&mut self) {
let mut store_chan: Option<Sender<()>> = None;
let mut store_prefetched_chan: Option<Sender<()>> = None;
loop {
let msg = self.port.recv();
debug!("image_cache_task: received: {:?}", msg);
match msg {
Prefetch(url) => self.prefetch(url),
StorePrefetchedImageData(url, data) => {
store_prefetched_chan.map(|chan| {
chan.send(());
});
store_prefetched_chan = None;
self.store_prefetched_image_data(url, data);
}
Decode(url) => self.decode(url),
StoreImage(url, image) => {
store_chan.map(|chan| {
chan.send(());
});
store_chan = None;
self.store_image(url, image)
}
GetImage(url, response) => self.get_image(url, response),
WaitForImage(url, response) => {
self.wait_for_image(url, response)
}
WaitForStore(chan) => store_chan = Some(chan),
WaitForStorePrefetched(chan) => store_prefetched_chan = Some(chan),
Exit(response) => {
assert!(self.need_exit.is_none());
self.need_exit = Some(response);
}
}
let need_exit = replace(&mut self.need_exit, None);
match need_exit {
Some(response) => {
// Wait until we have no outstanding requests and subtasks
// before exiting
let mut can_exit = true;
for (_, state) in self.state_map.iter() {
match *state {
Prefetching(..) => can_exit = false,
Decoding => can_exit = false,
Init | Prefetched(..) | Decoded(..) | Failed => ()
}
}
if can_exit {
response.send(());
break;
} else {
self.need_exit = Some(response);
}
}
None => ()
}
}
}
fn get_state(&self, url: Url) -> ImageState {
match self.state_map.find(&url) {
Some(state) => state.clone(),
None => Init
}
}
fn set_state(&mut self, url: Url, state: ImageState) {
self.state_map.insert(url, state);
}
fn prefetch(&mut self, url: Url) {
match self.get_state(url.clone()) {
Init => {
let to_cache = self.chan.clone();
let resource_task = self.resource_task.clone();
let url_clone = url.clone();
spawn(proc() {
let url = url_clone;
debug!("image_cache_task: started fetch for {:s}", url.serialize());
let image = load_image_data(url.clone(), resource_task.clone());
let result = if image.is_ok() {
Ok(image.unwrap())
} else {
Err(())
};
to_cache.send(StorePrefetchedImageData(url.clone(), result));
debug!("image_cache_task: ended fetch for {:s}", url.serialize());
});
self.set_state(url, Prefetching(DoNotDecode));
}
Prefetching(..) | Prefetched(..) | Decoding | Decoded(..) | Failed => {
// We've already begun working on this image
}
}
}
fn store_prefetched_image_data(&mut self, url: Url, data: Result<Vec<u8>, ()>) {
match self.get_state(url.clone()) {
Prefetching(next_step) => {
match data {
Ok(data) => {
self.set_state(url.clone(), Prefetched(data));
match next_step {
DoDecode => self.decode(url),
_ => ()
}
}
Err(..) => {
self.set_state(url.clone(), Failed);
self.purge_waiters(url, || ImageFailed);
}
}
}
Init
| Prefetched(..)
| Decoding
| Decoded(..)
| Failed => {
fail!("wrong state for storing prefetched image")
}
}
}
fn decode(&mut self, url: Url) {
match self.get_state(url.clone()) {
Init => fail!("decoding image before prefetch"),
Prefetching(DoNotDecode) => {
// We don't have the data yet, queue up the decode
self.set_state(url, Prefetching(DoDecode))
}
Prefetching(DoDecode) => {
// We don't have the data yet, but the decode request is queued up
}
Prefetched(data) => {
let to_cache = self.chan.clone();
let url_clone = url.clone();
spawn(proc() {
let url = url_clone;
debug!("image_cache_task: started image decode for {:s}", url.serialize());
let image = load_from_memory(data.as_slice());
let image = if image.is_some() {
Some(Arc::new(box image.unwrap()))
} else {
None
};
to_cache.send(StoreImage(url.clone(), image));
debug!("image_cache_task: ended image decode for {:s}", url.serialize());
});
self.set_state(url, Decoding);
}
Decoding | Decoded(..) | Failed => {
// We've already begun decoding
}
}
}
fn store_image(&mut self, url: Url, image: Option<Arc<Box<Image>>>) {
match self.get_state(url.clone()) {
Decoding => {
match image {
Some(image) => {
self.set_state(url.clone(), Decoded(image.clone()));
self.purge_waiters(url, || ImageReady(image.clone()) );
}
None => {
self.set_state(url.clone(), Failed);
self.purge_waiters(url, || ImageFailed );
}
}
}
Init
| Prefetching(..)
| Prefetched(..)
| Decoded(..)
| Failed => {
fail!("incorrect state in store_image")
}
}
}
fn purge_waiters(&mut self, url: Url, f: || -> ImageResponseMsg) {
match self.wait_map.pop(&url) {
Some(waiters) => {
let mut items = waiters.lock();
for response in items.iter() {
response.send(f());
}
}
None => ()
}
}
fn get_image(&self, url: Url, response: Sender<ImageResponseMsg>) {
match self.get_state(url.clone()) {
Init => fail!("request for image before prefetch"),
Prefetching(DoDecode) => response.send(ImageNotReady),
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
Decoding => response.send(ImageNotReady),
Decoded(image) => response.send(ImageReady(image.clone())),
Failed => response.send(ImageFailed),
}
}
fn wait_for_image(&mut self, url: Url, response: Sender<ImageResponseMsg>) {
match self.get_state(url.clone()) {
Init => fail!("request for image before prefetch"),
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
Prefetching(DoDecode) | Decoding => {
// We don't have this image yet
if self.wait_map.contains_key(&url) {
let waiters = self.wait_map.find_mut(&url).unwrap();
let mut response = Some(response);
let mut items = waiters.lock();
items.push(response.take().unwrap());
} else {
let response = vec!(response);
let wrapped = Arc::new(Mutex::new(response));
self.wait_map.insert(url, wrapped);
}
}
Decoded(image) => {
response.send(ImageReady(image.clone()));
}
Failed => {
response.send(ImageFailed);
}
}
}
}
pub trait ImageCacheTaskClient {
fn exit(&self);
}
impl ImageCacheTaskClient for ImageCacheTask {
fn exit(&self) {
let (response_chan, response_port) = channel();
self.send(Exit(response_chan));
response_port.recv();
}
}
impl ImageCacheTask {
pub fn send(&self, msg: Msg) {
self.chan.send(msg);
}
#[cfg(test)]
fn wait_for_store(&self) -> Receiver<()> {
let (chan, port) = channel();
self.send(WaitForStore(chan));
port
}
#[cfg(test)]
fn wait_for_store_prefetched(&self) -> Receiver<()> {
let (chan, port) = channel();
self.send(WaitForStorePrefetched(chan));
port
}
}
fn load_image_data(url: Url, resource_task: ResourceTask) -> Result<Vec<u8>, ()> {
let (response_chan, response_port) = channel();
resource_task.send(resource_task::Load(LoadData::new(url), response_chan));
let mut image_data = vec!();
let progress_port = response_port.recv().progress_port;
loop {
match progress_port.recv() {
resource_task::Payload(data) => {
image_data.push_all(data.as_slice());
}
resource_task::Done(result::Ok(..)) => {
return Ok(image_data.move_iter().collect());
}
resource_task::Done(result::Err(..)) => {
return Err(());
}
}
}
}
pub fn spawn_listener<A: Send>(f: proc(Receiver<A>):Send) -> Sender<A> {
let (setup_chan, setup_port) = channel();
spawn(proc() {
let (chan, port) = channel();
setup_chan.send(chan);
f(port);
});
setup_port.recv()
}
#[cfg(test)]
mod tests {
use super::*;
use resource_task;
use resource_task::{ResourceTask, Metadata, start_sending};
use image::base::test_image_bin;
use std::comm;
use url::Url;
trait Closure {
fn invoke(&self, _response: Sender<resource_task::ProgressMsg>) { }
}
struct DoesNothing;
impl Closure for DoesNothing { }
struct JustSendOK {
url_requested_chan: Sender<()>,
}
impl Closure for JustSendOK {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
self.url_requested_chan.send(());
response.send(resource_task::Done(Ok(())));
}
}
struct SendTestImage;
impl Closure for SendTestImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Ok(())));
}
}
struct SendBogusImage;
impl Closure for SendBogusImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(vec!()));
response.send(resource_task::Done(Ok(())));
}
}
struct SendTestImageErr;
impl Closure for SendTestImageErr {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Err("".to_string())));
}
}
struct WaitSendTestImage {
wait_port: Receiver<()>,
}
impl Closure for WaitSendTestImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
// Don't send the data until after the client requests
// the image
self.wait_port.recv();
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Ok(())));
}
}
struct WaitSendTestImageErr {
wait_port: Receiver<()>,
}
impl Closure for WaitSendTestImageErr {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
// Don't send the data until after the client requests
// the image
self.wait_port.recv();
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Err("".to_string())));
}
}
fn mock_resource_task<T: Closure+Send>(on_load: Box<T>) -> ResourceTask {
spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
on_load.invoke(chan);
}
resource_task::Exit => break
}
}
})
}
#[test]
fn should_exit_on_request() {
let mock_resource_task = mock_resource_task(box DoesNothing);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
#[should_fail]
fn should_fail_if_unprefetched_image_is_requested() {
let mock_resource_task = mock_resource_task(box DoesNothing);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let (chan, port) = channel();
image_cache_task.send(GetImage(url, chan));
port.recv();
}
#[test]
fn should_request_url_from_resource_task_on_prefetch() {
let (url_requested_chan, url_requested) = channel();
let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url));
url_requested.recv();
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_not_request_url_from_resource_task_on_multiple_prefetches() {
let (url_requested_chan, url_requested) = comm::channel();
let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Prefetch(url));
url_requested.recv();
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
match url_requested.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
};
}
#[test]
fn should_return_image_not_ready_if_data_has_not_arrived() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImage{wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
assert!(response_port.recv() == ImageNotReady);
wait_chan.send(());
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_decoded_image_data_if_data_has_arrived() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_decoded_image_data_for_multiple_requests() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
for _ in range(0u32, 2u32) {
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url.clone(), response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_not_request_image_from_resource_task_if_image_is_already_available() {
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
chan.send(resource_task::Payload(test_image_bin()));
chan.send(resource_task::Done(Ok(())));
image_bin_sent_chan.send(());
}
resource_task::Exit => {
resource_task_exited_chan.send(());
break
}
}
}
});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
image_bin_sent.recv();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
resource_task_exited.recv();
// Our resource task should not have received another request for the image
// because it's already cached
match image_bin_sent.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
}
}
#[test]
fn should_not_request_image_from_resource_task_if_image_fetch_already_failed() {
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
chan.send(resource_task::Payload(test_image_bin()));
chan.send(resource_task::Done(Err("".to_string())));
image_bin_sent_chan.send(());
}
resource_task::Exit => {
resource_task_exited_chan.send(());
break
}
}
}
});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
image_bin_sent.recv();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
resource_task_exited.recv();
// Our resource task should not have received another request for the image
// because it's already cached
match image_bin_sent.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
}
}
#[test]
fn should_return_failed_if_image_bin_cannot_be_fetched() {
let mock_resource_task = mock_resource_task(box SendTestImageErr);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store_prefetched();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_failed_for_multiple_get_image_requests_if_image_bin_cannot_be_fetched() {
let mock_resource_task = mock_resource_task(box SendTestImageErr);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store_prefetched();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url.clone(), response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
// And ask again, we should get the same response
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_failed_if_image_decode_fails() {
let mock_resource_task = mock_resource_task(box SendBogusImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
// Make the request
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_on_wait_if_image_is_already_loaded() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
match response_port.recv() {
ImageReady(..) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_on_wait_if_image_is_not_yet_loaded() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImage {wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
wait_chan.send(());
match response_port.recv() {
ImageReady(..) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_failed_on_wait_if_image_fails_to_load() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImageErr{wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
wait_chan.send(());
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn sync_cache_should_wait_for_images() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new_sync(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
}

44
components/net/lib.rs Normal file
View file

@ -0,0 +1,44 @@
/* 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/. */
#![feature(default_type_params, globs, managed_boxes, phase)]
extern crate debug;
extern crate collections;
extern crate geom;
extern crate http;
extern crate png;
#[phase(plugin, link)]
extern crate log;
extern crate serialize;
extern crate servo_util = "util";
extern crate stb_image;
extern crate sync;
extern crate time;
extern crate url;
/// Image handling.
///
/// It may be surprising that this goes in the network crate as opposed to the graphics crate.
/// However, image handling is generally very integrated with the network stack (especially where
/// caching is involved) and as a result it must live in here.
pub mod image {
pub mod base;
pub mod holder;
}
pub mod file_loader;
pub mod http_loader;
pub mod data_loader;
pub mod image_cache_task;
pub mod local_image_cache;
pub mod resource_task;
/// An implementation of the [Fetch spec](http://fetch.spec.whatwg.org/)
pub mod fetch {
#![allow(dead_code)] // XXXManishearth this is only temporary until the Fetch mod starts being used
pub mod request;
pub mod response;
pub mod cors_cache;
}

View file

@ -0,0 +1,166 @@
/* 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/. */
/*!
An adapter for ImageCacheTask that does local caching to avoid
extra message traffic, it also avoids waiting on the same image
multiple times and thus triggering reflows multiple times.
*/
use image_cache_task::{Decode, GetImage, ImageCacheTask, ImageFailed, ImageNotReady, ImageReady};
use image_cache_task::{ImageResponseMsg, Prefetch, WaitForImage};
use std::comm::{Receiver, channel};
use std::collections::hashmap::HashMap;
use servo_util::task::spawn_named;
use url::Url;
pub trait ImageResponder {
fn respond(&self) -> proc(ImageResponseMsg):Send;
}
pub struct LocalImageCache {
image_cache_task: ImageCacheTask,
round_number: uint,
on_image_available: Option<Box<ImageResponder+Send>>,
state_map: HashMap<Url, ImageState>
}
impl LocalImageCache {
pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache {
LocalImageCache {
image_cache_task: image_cache_task,
round_number: 1,
on_image_available: None,
state_map: HashMap::new()
}
}
}
#[deriving(Clone)]
struct ImageState {
prefetched: bool,
decoded: bool,
last_request_round: uint,
last_response: ImageResponseMsg
}
impl LocalImageCache {
/// The local cache will only do a single remote request for a given
/// URL in each 'round'. Layout should call this each time it begins
pub fn next_round(&mut self, on_image_available: Box<ImageResponder+Send>) {
self.round_number += 1;
self.on_image_available = Some(on_image_available);
}
pub fn prefetch(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.prefetched {
return
}
state.prefetched = true;
}
self.image_cache_task.send(Prefetch((*url).clone()));
}
pub fn decode(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.decoded {
return
}
state.decoded = true;
}
self.image_cache_task.send(Decode((*url).clone()));
}
// FIXME: Should return a Future
pub fn get_image(&mut self, url: &Url) -> Receiver<ImageResponseMsg> {
{
let round_number = self.round_number;
let state = self.get_state(url);
// Save the previous round number for comparison
let last_round = state.last_request_round;
// Set the current round number for this image
state.last_request_round = round_number;
match state.last_response {
ImageReady(ref image) => {
let (chan, port) = channel();
chan.send(ImageReady(image.clone()));
return port;
}
ImageNotReady => {
if last_round == round_number {
let (chan, port) = channel();
chan.send(ImageNotReady);
return port;
} else {
// We haven't requested the image from the
// remote cache this round
}
}
ImageFailed => {
let (chan, port) = channel();
chan.send(ImageFailed);
return port;
}
}
}
let (response_chan, response_port) = channel();
self.image_cache_task.send(GetImage((*url).clone(), response_chan));
let response = response_port.recv();
match response {
ImageNotReady => {
// Need to reflow when the image is available
// FIXME: Instead we should be just passing a Future
// to the caller, then to the display list. Finally,
// the compositor should be resonsible for waiting
// on the image to load and triggering layout
let image_cache_task = self.image_cache_task.clone();
assert!(self.on_image_available.is_some());
let on_image_available: proc(ImageResponseMsg):Send = self.on_image_available.as_ref().unwrap().respond();
let url = (*url).clone();
spawn_named("LocalImageCache", proc() {
let (response_chan, response_port) = channel();
image_cache_task.send(WaitForImage(url.clone(), response_chan));
on_image_available(response_port.recv());
});
}
_ => ()
}
// Put a copy of the response in the cache
let response_copy = match response {
ImageReady(ref image) => ImageReady(image.clone()),
ImageNotReady => ImageNotReady,
ImageFailed => ImageFailed
};
self.get_state(url).last_response = response_copy;
let (chan, port) = channel();
chan.send(response);
return port;
}
fn get_state<'a>(&'a mut self, url: &Url) -> &'a mut ImageState {
let state = self.state_map.find_or_insert_with(url.clone(), |_| {
let new_state = ImageState {
prefetched: false,
decoded: false,
last_request_round: 0,
last_response: ImageNotReady
};
new_state
});
state
}
}

View file

@ -0,0 +1,267 @@
/* 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/. */
//! A task that takes a URL and streams back the binary data.
use file_loader;
use http_loader;
use data_loader;
use std::comm::{channel, Receiver, Sender};
use std::task::TaskBuilder;
use std::os;
use http::headers::content_type::MediaType;
use ResponseHeaderCollection = http::headers::response::HeaderCollection;
use RequestHeaderCollection = http::headers::request::HeaderCollection;
use http::method::{Method, Get};
use url::Url;
use StatusOk = http::status::Ok;
use http::status::Status;
pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(LoadData, Sender<LoadResponse>),
Exit
}
#[deriving(Clone)]
pub struct LoadData {
pub url: Url,
pub method: Method,
pub headers: RequestHeaderCollection,
pub data: Option<Vec<u8>>,
pub cors: Option<ResourceCORSData>
}
impl LoadData {
pub fn new(url: Url) -> LoadData {
LoadData {
url: url,
method: Get,
headers: RequestHeaderCollection::new(),
data: None,
cors: None
}
}
}
#[deriving(Clone)]
pub struct ResourceCORSData {
/// CORS Preflight flag
pub preflight: bool,
/// Origin of CORS Request
pub origin: Url
}
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
pub struct Metadata {
/// Final URL after redirects.
pub final_url: Url,
/// MIME type / subtype.
pub content_type: Option<(String, String)>,
/// Character set.
pub charset: Option<String>,
/// Headers
pub headers: Option<ResponseHeaderCollection>,
/// HTTP Status
pub status: Status
}
impl Metadata {
/// Metadata with defaults for everything optional.
pub fn default(url: Url) -> Metadata {
Metadata {
final_url: url,
content_type: None,
charset: None,
headers: None,
status: StatusOk // http://fetch.spec.whatwg.org/#concept-response-status-message
}
}
/// Extract the parts of a MediaType that we care about.
pub fn set_content_type(&mut self, content_type: &Option<MediaType>) {
match *content_type {
None => (),
Some(MediaType { type_: ref type_,
subtype: ref subtype,
parameters: ref parameters }) => {
self.content_type = Some((type_.clone(), subtype.clone()));
for &(ref k, ref v) in parameters.iter() {
if "charset" == k.as_slice() {
self.charset = Some(v.clone());
}
}
}
}
}
}
/// Message sent in response to `Load`. Contains metadata, and a port
/// for receiving the data.
///
/// Even if loading fails immediately, we send one of these and the
/// progress_port will provide the error.
pub struct LoadResponse {
/// Metadata, such as from HTTP headers.
pub metadata: Metadata,
/// Port for reading data.
pub progress_port: Receiver<ProgressMsg>,
}
/// Messages sent in response to a `Load` message
#[deriving(PartialEq,Show)]
pub enum ProgressMsg {
/// Binary data - there may be multiple of these
Payload(Vec<u8>),
/// Indicates loading is complete, either successfully or not
Done(Result<(), String>)
}
/// For use by loaders in responding to a Load message.
pub fn start_sending(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Sender<ProgressMsg> {
start_sending_opt(start_chan, metadata).ok().unwrap()
}
/// For use by loaders in responding to a Load message.
pub fn start_sending_opt(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Result<Sender<ProgressMsg>, ()> {
let (progress_chan, progress_port) = channel();
let result = start_chan.send_opt(LoadResponse {
metadata: metadata,
progress_port: progress_port,
});
match result {
Ok(_) => Ok(progress_chan),
Err(_) => Err(())
}
}
/// Convenience function for synchronously loading a whole resource.
pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
-> Result<(Metadata, Vec<u8>), String> {
let (start_chan, start_port) = channel();
resource_task.send(Load(LoadData::new(url), start_chan));
let response = start_port.recv();
let mut buf = vec!();
loop {
match response.progress_port.recv() {
Payload(data) => buf.push_all(data.as_slice()),
Done(Ok(())) => return Ok((response.metadata, buf)),
Done(Err(e)) => return Err(e)
}
}
}
/// Handle to a resource task
pub type ResourceTask = Sender<ControlMsg>;
pub type LoaderTask = proc(load_data: LoadData, Sender<LoadResponse>);
/**
Creates a task to load a specific resource
The ResourceManager delegates loading to a different type of loader task for
each URL scheme
*/
type LoaderTaskFactory = extern "Rust" fn() -> LoaderTask;
/// Create a ResourceTask
pub fn new_resource_task() -> ResourceTask {
let (setup_chan, setup_port) = channel();
let builder = TaskBuilder::new().named("ResourceManager");
builder.spawn(proc() {
ResourceManager::new(setup_port).start();
});
setup_chan
}
struct ResourceManager {
from_client: Receiver<ControlMsg>,
}
impl ResourceManager {
fn new(from_client: Receiver<ControlMsg>) -> ResourceManager {
ResourceManager {
from_client : from_client,
}
}
}
impl ResourceManager {
fn start(&self) {
loop {
match self.from_client.recv() {
Load(load_data, start_chan) => {
self.load(load_data, start_chan)
}
Exit => {
break
}
}
}
}
fn load(&self, mut load_data: LoadData, start_chan: Sender<LoadResponse>) {
let loader = match load_data.url.scheme.as_slice() {
"file" => file_loader::factory(),
"http" | "https" => http_loader::factory(),
"data" => data_loader::factory(),
"about" => {
match load_data.url.non_relative_scheme_data().unwrap() {
"crash" => fail!("Loading the about:crash URL."),
"failure" => {
// FIXME: Find a way to load this without relying on the `../src` directory.
let mut path = os::self_exe_path().expect("can't get exe path");
path.pop();
path.push_many(["src", "test", "html", "failure.html"]);
load_data.url = Url::from_file_path(&path).unwrap();
file_loader::factory()
}
_ => {
start_sending(start_chan, Metadata::default(load_data.url))
.send(Done(Err("Unknown about: URL.".to_string())));
return
}
}
},
_ => {
debug!("resource_task: no loader for scheme {:s}", load_data.url.scheme);
start_sending(start_chan, Metadata::default(load_data.url))
.send(Done(Err("no loader for scheme".to_string())));
return
}
};
debug!("resource_task: loading url: {:s}", load_data.url.serialize());
loader(load_data, start_chan);
}
}
#[test]
fn test_exit() {
let resource_task = new_resource_task();
resource_task.send(Exit);
}
#[test]
fn test_bad_scheme() {
let resource_task = new_resource_task();
let (start_chan, start) = channel();
let url = Url::parse("bogus://whatever").unwrap();
resource_task.send(Load(LoadData::new(url), start_chan));
let response = start.recv();
match response.progress_port.recv() {
Done(result) => { assert!(result.is_err()) }
_ => fail!("bleh")
}
resource_task.send(Exit);
}