mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Auto merge of #11556 - Manishearth:make-fetch-happen, r=jdm
Make fetch happen <!-- Please describe your changes on the following line: --> Moves XHR over to the fetch backend. Previous PR: https://github.com/servo/servo/pull/114 --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [ ] `./mach test-tidy` does not report any errors (Will fix later) <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11556) <!-- Reviewable:end -->
This commit is contained in:
commit
0c11e8340b
33 changed files with 861 additions and 1085 deletions
|
@ -60,7 +60,6 @@ smallvec = "0.1"
|
|||
string_cache = {version = "0.2.18", features = ["heap_size", "unstable"]}
|
||||
style = {path = "../style"}
|
||||
time = "0.1.12"
|
||||
unicase = "1.0"
|
||||
url = {version = "1.0.0", features = ["heap_size", "query_encoding"]}
|
||||
util = {path = "../util"}
|
||||
uuid = {version = "0.2", features = ["v4"]}
|
||||
|
|
|
@ -1,490 +0,0 @@
|
|||
/* 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 partial implementation of CORS
|
||||
//! 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 hyper::client::Request;
|
||||
use hyper::header::{AccessControlAllowHeaders, AccessControlRequestHeaders};
|
||||
use hyper::header::{AccessControlAllowMethods, AccessControlRequestMethod};
|
||||
use hyper::header::{AccessControlAllowOrigin, AccessControlMaxAge};
|
||||
use hyper::header::{ContentType, Host};
|
||||
use hyper::header::{HeaderView, Headers};
|
||||
use hyper::method::Method;
|
||||
use hyper::mime::{Mime, SubLevel, TopLevel};
|
||||
use hyper::status::StatusClass::Success;
|
||||
use net_traits::{AsyncResponseListener, Metadata, NetworkError, ResponseAction};
|
||||
use network_listener::{NetworkListener, PreInvoke};
|
||||
use script_runtime::ScriptChan;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use time::{self, Timespec, now};
|
||||
use unicase::UniCase;
|
||||
use url::Url;
|
||||
use util::thread::spawn_named;
|
||||
|
||||
/// Interface for network listeners concerned with CORS checks. Proper network requests
|
||||
/// should be initiated from this method, based on the response provided.
|
||||
pub trait AsyncCORSResponseListener {
|
||||
fn response_available(&self, response: CORSResponse);
|
||||
}
|
||||
|
||||
#[derive(Clone, HeapSizeOf)]
|
||||
pub struct CORSRequest {
|
||||
pub origin: Url,
|
||||
pub destination: Url,
|
||||
pub mode: RequestMode,
|
||||
#[ignore_heap_size_of = "Defined in hyper"]
|
||||
pub method: Method,
|
||||
#[ignore_heap_size_of = "Defined in hyper"]
|
||||
pub headers: Headers,
|
||||
/// CORS preflight flag (https://fetch.spec.whatwg.org/#concept-http-fetch)
|
||||
/// Indicates that a CORS preflight request and/or cache check is to be performed
|
||||
pub preflight_flag: bool,
|
||||
}
|
||||
|
||||
/// https://fetch.spec.whatwg.org/#concept-request-mode
|
||||
/// This only covers some of the request modes. The
|
||||
/// `same-origin` and `no CORS` modes are unnecessary for XHR.
|
||||
#[derive(PartialEq, Copy, Clone, HeapSizeOf)]
|
||||
pub enum RequestMode {
|
||||
CORS, // CORS
|
||||
ForcedPreflight, // CORS-with-forced-preflight
|
||||
}
|
||||
|
||||
impl CORSRequest {
|
||||
/// Creates a CORS request if necessary. Will return an error when fetching is forbidden
|
||||
pub fn maybe_new(referer: Url,
|
||||
destination: Url,
|
||||
mode: RequestMode,
|
||||
method: Method,
|
||||
headers: Headers,
|
||||
same_origin_data_url_flag: bool)
|
||||
-> Result<Option<CORSRequest>, ()> {
|
||||
if referer.origin() == destination.origin() {
|
||||
return Ok(None); // Not cross-origin, proceed with a normal fetch
|
||||
}
|
||||
match destination.scheme() {
|
||||
// As per (https://fetch.spec.whatwg.org/#main-fetch 5.1.9), about URLs can be fetched
|
||||
// the same as a basic request.
|
||||
"about" if destination.path() == "blank" => Ok(None),
|
||||
// As per (https://fetch.spec.whatwg.org/#main-fetch 5.1.9), data URLs can be fetched
|
||||
// the same as a basic request if the request's method is GET and the
|
||||
// same-origin data-URL flag is set.
|
||||
"data" if same_origin_data_url_flag && method == Method::Get => Ok(None),
|
||||
"http" | "https" => {
|
||||
let mut req = CORSRequest::new(referer, destination, mode, method, headers);
|
||||
req.preflight_flag = !is_simple_method(&req.method) ||
|
||||
mode == RequestMode::ForcedPreflight;
|
||||
if req.headers.iter().any(|h| !is_simple_header(&h)) {
|
||||
req.preflight_flag = true;
|
||||
}
|
||||
Ok(Some(req))
|
||||
},
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(mut referer: Url,
|
||||
destination: Url,
|
||||
mode: RequestMode,
|
||||
method: Method,
|
||||
headers: Headers)
|
||||
-> CORSRequest {
|
||||
referer.set_fragment(None);
|
||||
referer.set_query(None);
|
||||
referer.set_path("");
|
||||
CORSRequest {
|
||||
origin: referer,
|
||||
destination: destination,
|
||||
mode: mode,
|
||||
method: method,
|
||||
headers: headers,
|
||||
preflight_flag: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_fetch_async(&self,
|
||||
listener: Box<AsyncCORSResponseListener + Send>,
|
||||
script_chan: Box<ScriptChan + Send>) {
|
||||
struct CORSContext {
|
||||
listener: Box<AsyncCORSResponseListener + Send>,
|
||||
response: Option<CORSResponse>,
|
||||
}
|
||||
|
||||
// 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.
|
||||
impl AsyncResponseListener for CORSContext {
|
||||
fn headers_available(&mut self, _metadata: Result<Metadata, NetworkError>) {
|
||||
}
|
||||
|
||||
fn data_available(&mut self, _payload: Vec<u8>) {
|
||||
}
|
||||
|
||||
fn response_complete(&mut self, _status: Result<(), NetworkError>) {
|
||||
let response = self.response.take().unwrap();
|
||||
self.listener.response_available(response);
|
||||
}
|
||||
}
|
||||
impl PreInvoke for CORSContext {}
|
||||
|
||||
let context = CORSContext {
|
||||
listener: listener,
|
||||
response: None,
|
||||
};
|
||||
let listener = NetworkListener {
|
||||
context: Arc::new(Mutex::new(context)),
|
||||
script_chan: script_chan,
|
||||
};
|
||||
|
||||
// TODO: this exists only to make preflight check non-blocking
|
||||
// perhaps should be handled by the resource thread?
|
||||
let req = self.clone();
|
||||
spawn_named("cors".to_owned(), move || {
|
||||
let response = req.http_fetch();
|
||||
let mut context = listener.context.lock();
|
||||
let context = context.as_mut().unwrap();
|
||||
context.response = Some(response);
|
||||
listener.notify(ResponseAction::ResponseComplete(Ok(())));
|
||||
});
|
||||
}
|
||||
|
||||
/// http://fetch.spec.whatwg.org/#concept-http-fetch
|
||||
/// This method assumes that the CORS flag is set
|
||||
/// This does not perform the full HTTP fetch, rather it handles part of the CORS filtering
|
||||
/// if self.mode is ForcedPreflight, then the CORS-with-forced-preflight
|
||||
/// fetch flag is set as well
|
||||
pub fn http_fetch(&self) -> CORSResponse {
|
||||
let response = CORSResponse::new();
|
||||
// Step 2: Handle service workers (unimplemented)
|
||||
// Step 3
|
||||
// Substep 1: Service workers (unimplemented )
|
||||
// Substep 2
|
||||
let cache = &mut CORSCache(vec!()); // XXXManishearth Should come from user agent
|
||||
if self.preflight_flag &&
|
||||
!cache.match_method(self, &self.method) &&
|
||||
!self.headers.iter().all(|h| is_simple_header(&h) && cache.match_header(self, h.name())) &&
|
||||
(!is_simple_method(&self.method) || self.mode == RequestMode::ForcedPreflight) {
|
||||
return self.preflight_fetch();
|
||||
// Everything after this is part of XHR::fetch()
|
||||
// Expect the organization of code to improve once we have a fetch crate
|
||||
}
|
||||
response
|
||||
}
|
||||
|
||||
/// https://fetch.spec.whatwg.org/#cors-preflight-fetch
|
||||
fn preflight_fetch(&self) -> CORSResponse {
|
||||
let error = CORSResponse::new_error();
|
||||
let mut cors_response = CORSResponse::new();
|
||||
|
||||
// Step 1
|
||||
let mut preflight = self.clone();
|
||||
preflight.method = Method::Options;
|
||||
preflight.headers = Headers::new();
|
||||
// Step 2
|
||||
preflight.headers.set(AccessControlRequestMethod(self.method.clone()));
|
||||
|
||||
// Steps 3-5
|
||||
let mut header_names = vec![];
|
||||
for header in self.headers.iter() {
|
||||
header_names.push(header.name().to_owned());
|
||||
}
|
||||
header_names.sort();
|
||||
preflight.headers
|
||||
.set(AccessControlRequestHeaders(header_names.into_iter().map(UniCase).collect()));
|
||||
|
||||
let preflight_request = Request::new(preflight.method, preflight.destination);
|
||||
let mut req = match preflight_request {
|
||||
Ok(req) => req,
|
||||
Err(_) => return error,
|
||||
};
|
||||
|
||||
let host = req.headers().get::<Host>().unwrap().clone();
|
||||
*req.headers_mut() = preflight.headers.clone();
|
||||
req.headers_mut().set(host);
|
||||
let stream = match req.start() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return error,
|
||||
};
|
||||
// Step 6
|
||||
let response = match stream.send() {
|
||||
Ok(r) => r,
|
||||
Err(_) => return error,
|
||||
};
|
||||
|
||||
// Step 7: We don't perform a CORS check here
|
||||
// FYI, fn allow_cross_origin_request() performs the CORS check
|
||||
match response.status.class() {
|
||||
Success => {}
|
||||
_ => return error,
|
||||
}
|
||||
cors_response.headers = response.headers.clone();
|
||||
// Substeps 1-3 (parsing rules: https://fetch.spec.whatwg.org/#http-new-header-syntax)
|
||||
let methods_substep4 = [self.method.clone()];
|
||||
let mut methods = match response.headers.get() {
|
||||
Some(&AccessControlAllowMethods(ref v)) => &**v,
|
||||
_ => return error,
|
||||
};
|
||||
let headers = match response.headers.get() {
|
||||
Some(&AccessControlAllowHeaders(ref h)) => h,
|
||||
_ => return error,
|
||||
};
|
||||
// Substep 4
|
||||
if methods.is_empty() && preflight.mode == RequestMode::ForcedPreflight {
|
||||
methods = &methods_substep4;
|
||||
}
|
||||
// Substep 5
|
||||
if !is_simple_method(&self.method) && !methods.iter().any(|m| m == &self.method) {
|
||||
return error;
|
||||
}
|
||||
// Substep 6
|
||||
for h in self.headers.iter() {
|
||||
if is_simple_header(&h) {
|
||||
continue;
|
||||
}
|
||||
if !headers.iter().any(|ref h2| h.name().eq_ignore_ascii_case(h2)) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
// Substeps 7-8
|
||||
let max_age = match response.headers.get() {
|
||||
Some(&AccessControlMaxAge(num)) => num,
|
||||
None => 0,
|
||||
};
|
||||
// Substep 9: Impose restrictions on max-age, if any (unimplemented)
|
||||
// Substeps 10-12: Add a cache (partially implemented, XXXManishearth)
|
||||
// This cache should come from the user agent, creating a new one here to check
|
||||
// for compile time errors
|
||||
let cache = &mut CORSCache(vec![]);
|
||||
for m in methods {
|
||||
let cache_match = cache.match_method_and_update(self, m, max_age);
|
||||
if !cache_match {
|
||||
cache.insert(CORSCacheEntry::new(self.origin.clone(),
|
||||
self.destination.clone(),
|
||||
max_age,
|
||||
false,
|
||||
HeaderOrMethod::MethodData(m.clone())));
|
||||
}
|
||||
}
|
||||
// Substeps 13-14
|
||||
for h in response.headers.iter() {
|
||||
let cache_match = cache.match_header_and_update(self, h.name(), max_age);
|
||||
if !cache_match {
|
||||
cache.insert(CORSCacheEntry::new(self.origin.clone(),
|
||||
self.destination.clone(),
|
||||
max_age,
|
||||
false,
|
||||
HeaderOrMethod::HeaderData(h.to_string())));
|
||||
}
|
||||
}
|
||||
// Substep 15
|
||||
cors_response
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct CORSResponse {
|
||||
pub network_error: bool,
|
||||
pub headers: Headers,
|
||||
}
|
||||
|
||||
impl CORSResponse {
|
||||
fn new() -> CORSResponse {
|
||||
CORSResponse {
|
||||
network_error: false,
|
||||
headers: Headers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_error() -> CORSResponse {
|
||||
CORSResponse {
|
||||
network_error: true,
|
||||
headers: Headers::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CORS Cache stuff
|
||||
|
||||
/// A CORS cache object. Anchor it somewhere to the user agent.
|
||||
#[derive(Clone)]
|
||||
pub struct CORSCache(Vec<CORSCacheEntry>);
|
||||
|
||||
/// Union type for CORS cache entries
|
||||
/// Each entry might pertain to a header or method
|
||||
#[derive(Clone)]
|
||||
pub enum HeaderOrMethod {
|
||||
HeaderData(String),
|
||||
MethodData(Method),
|
||||
}
|
||||
|
||||
impl HeaderOrMethod {
|
||||
fn match_header(&self, header_name: &str) -> bool {
|
||||
match *self {
|
||||
HeaderOrMethod::HeaderData(ref s) => (&**s).eq_ignore_ascii_case(header_name),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn match_method(&self, method: &Method) -> bool {
|
||||
match *self {
|
||||
HeaderOrMethod::MethodData(ref m) => m == method,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An entry in the CORS cache
|
||||
#[derive(Clone)]
|
||||
pub struct CORSCacheEntry {
|
||||
pub origin: Url,
|
||||
pub url: Url,
|
||||
pub max_age: u32,
|
||||
pub credentials: bool,
|
||||
pub header_or_method: HeaderOrMethod,
|
||||
created: Timespec,
|
||||
}
|
||||
|
||||
impl CORSCacheEntry {
|
||||
fn new(origin: Url,
|
||||
url: Url,
|
||||
max_age: u32,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CORSCache {
|
||||
/// https://fetch.spec.whatwg.org/#concept-cache-clear
|
||||
#[allow(dead_code)]
|
||||
fn clear(&mut self, request: &CORSRequest) {
|
||||
let CORSCache(buf) = self.clone();
|
||||
let new_buf: Vec<CORSCacheEntry> =
|
||||
buf.into_iter()
|
||||
.filter(|e| e.origin == request.origin && request.destination == e.url)
|
||||
.collect();
|
||||
*self = CORSCache(new_buf);
|
||||
}
|
||||
|
||||
// Remove old entries
|
||||
fn cleanup(&mut self) {
|
||||
let CORSCache(buf) = self.clone();
|
||||
let now = time::now().to_timespec();
|
||||
let new_buf: Vec<CORSCacheEntry> = buf.into_iter()
|
||||
.filter(|e| now.sec > e.created.sec + e.max_age as i64)
|
||||
.collect();
|
||||
*self = CORSCache(new_buf);
|
||||
}
|
||||
|
||||
/// https://fetch.spec.whatwg.org/#concept-cache-match-header
|
||||
fn find_entry_by_header<'a>(&'a mut self,
|
||||
request: &CORSRequest,
|
||||
header_name: &str)
|
||||
-> Option<&'a mut CORSCacheEntry> {
|
||||
self.cleanup();
|
||||
// Credentials are not yet implemented here
|
||||
self.0.iter_mut().find(|e| {
|
||||
e.origin.scheme() == request.origin.scheme() &&
|
||||
e.origin.host_str() == request.origin.host_str() &&
|
||||
e.origin.port() == request.origin.port() &&
|
||||
e.url == request.destination &&
|
||||
e.header_or_method.match_header(header_name)
|
||||
})
|
||||
}
|
||||
|
||||
fn match_header(&mut self, request: &CORSRequest, header_name: &str) -> bool {
|
||||
self.find_entry_by_header(request, header_name).is_some()
|
||||
}
|
||||
|
||||
fn match_header_and_update(&mut self,
|
||||
request: &CORSRequest,
|
||||
header_name: &str,
|
||||
new_max_age: u32)
|
||||
-> bool {
|
||||
self.find_entry_by_header(request, header_name).map(|e| e.max_age = new_max_age).is_some()
|
||||
}
|
||||
|
||||
fn find_entry_by_method<'a>(&'a mut self,
|
||||
request: &CORSRequest,
|
||||
method: &Method)
|
||||
-> Option<&'a mut CORSCacheEntry> {
|
||||
// we can take the method from CORSRequest itself
|
||||
self.cleanup();
|
||||
// Credentials are not yet implemented here
|
||||
self.0.iter_mut().find(|e| {
|
||||
e.origin.scheme() == request.origin.scheme() &&
|
||||
e.origin.host_str() == request.origin.host_str() &&
|
||||
e.origin.port() == request.origin.port() &&
|
||||
e.url == request.destination &&
|
||||
e.header_or_method.match_method(method)
|
||||
})
|
||||
}
|
||||
|
||||
/// https://fetch.spec.whatwg.org/#concept-cache-match-method
|
||||
fn match_method(&mut self, request: &CORSRequest, method: &Method) -> bool {
|
||||
self.find_entry_by_method(request, method).is_some()
|
||||
}
|
||||
|
||||
fn match_method_and_update(&mut self,
|
||||
request: &CORSRequest,
|
||||
method: &Method,
|
||||
new_max_age: u32)
|
||||
-> bool {
|
||||
self.find_entry_by_method(request, method).map(|e| e.max_age = new_max_age).is_some()
|
||||
}
|
||||
|
||||
fn insert(&mut self, entry: CORSCacheEntry) {
|
||||
self.cleanup();
|
||||
self.0.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_simple_header(h: &HeaderView) -> bool {
|
||||
// FIXME: use h.is::<HeaderType>() when AcceptLanguage and
|
||||
// ContentLanguage headers exist
|
||||
match &*h.name().to_ascii_lowercase() {
|
||||
"accept" | "accept-language" | "content-language" => true,
|
||||
"content-type" => match h.value() {
|
||||
Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) |
|
||||
Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _))) |
|
||||
Some(&ContentType(Mime(TopLevel::Multipart, SubLevel::FormData, _))) => true,
|
||||
|
||||
_ => false,
|
||||
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_simple_method(m: &Method) -> bool {
|
||||
match *m {
|
||||
Method::Get | Method::Head | Method::Post => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a CORS check on a header list and CORS request
|
||||
/// https://fetch.spec.whatwg.org/#cors-check
|
||||
pub fn allow_cross_origin_request(req: &CORSRequest, headers: &Headers) -> bool {
|
||||
match headers.get::<AccessControlAllowOrigin>() {
|
||||
Some(&AccessControlAllowOrigin::Any) => true, // Not always true, depends on credentials mode
|
||||
Some(&AccessControlAllowOrigin::Value(ref url)) => req.origin.as_str() == *url,
|
||||
Some(&AccessControlAllowOrigin::Null) |
|
||||
None => false,
|
||||
}
|
||||
}
|
|
@ -227,7 +227,7 @@ impl HTMLLinkElement {
|
|||
sender: action_sender,
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
listener.notify_action(message.to().unwrap());
|
||||
});
|
||||
|
||||
if self.parser_inserted.get() {
|
||||
|
|
|
@ -484,7 +484,7 @@ impl HTMLMediaElement {
|
|||
sender: action_sender,
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
listener.notify_action(message.to().unwrap());
|
||||
});
|
||||
|
||||
// FIXME: we're supposed to block the load event much earlier than now
|
||||
|
|
|
@ -314,7 +314,7 @@ impl HTMLScriptElement {
|
|||
sender: action_sender,
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
listener.notify_action(message.to().unwrap());
|
||||
});
|
||||
|
||||
doc.load_async(LoadType::Script(url), response_target);
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
* 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 cors::CORSResponse;
|
||||
use cors::{AsyncCORSResponseListener, CORSRequest, RequestMode, allow_cross_origin_request};
|
||||
use document_loader::DocumentLoader;
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
||||
|
@ -35,27 +33,28 @@ use encoding::label::encoding_from_whatwg_label;
|
|||
use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef};
|
||||
use euclid::length::Length;
|
||||
use hyper::header::Headers;
|
||||
use hyper::header::{Accept, ContentLength, ContentType, qitem};
|
||||
use hyper::header::{ContentLength, ContentType};
|
||||
use hyper::http::RawStatus;
|
||||
use hyper::method::Method;
|
||||
use hyper::mime::{self, Mime};
|
||||
use hyper::mime::{self, Mime, Attr as MimeAttr, Value as MimeValue};
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::JS_ClearPendingException;
|
||||
use js::jsapi::{JSContext, JS_ParseJSON, RootedValue};
|
||||
use js::jsval::{JSVal, NullValue, UndefinedValue};
|
||||
use msg::constellation_msg::{PipelineId, ReferrerPolicy};
|
||||
use net_traits::CoreResourceMsg::Load;
|
||||
use net_traits::CoreResourceMsg::Fetch;
|
||||
use net_traits::request::{CredentialsMode, Destination, RequestInit, RequestMode};
|
||||
use net_traits::trim_http_whitespace;
|
||||
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError, RequestSource};
|
||||
use net_traits::{LoadConsumer, LoadContext, LoadData, ResourceCORSData, CoreResourceThread, LoadOrigin};
|
||||
use net_traits::{CoreResourceThread, LoadOrigin};
|
||||
use net_traits::{FetchResponseListener, Metadata, NetworkError, RequestSource};
|
||||
use network_listener::{NetworkListener, PreInvoke};
|
||||
use parse::html::{ParseContext, parse_html};
|
||||
use parse::xml::{self, parse_xml};
|
||||
use script_runtime::ScriptChan;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::str;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
@ -82,7 +81,6 @@ pub struct GenerationId(u32);
|
|||
struct XHRContext {
|
||||
xhr: TrustedXHRAddress,
|
||||
gen_id: GenerationId,
|
||||
cors_request: Option<CORSRequest>,
|
||||
buf: DOMRefCell<Vec<u8>>,
|
||||
sync_status: DOMRefCell<Option<ErrorResult>>,
|
||||
}
|
||||
|
@ -142,7 +140,6 @@ pub struct XMLHttpRequest {
|
|||
request_body_len: Cell<usize>,
|
||||
sync: Cell<bool>,
|
||||
upload_complete: Cell<bool>,
|
||||
upload_events: Cell<bool>,
|
||||
send_flag: Cell<bool>,
|
||||
|
||||
timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>,
|
||||
|
@ -187,7 +184,6 @@ impl XMLHttpRequest {
|
|||
request_body_len: Cell::new(0),
|
||||
sync: Cell::new(false),
|
||||
upload_complete: Cell::new(false),
|
||||
upload_events: Cell::new(false),
|
||||
send_flag: Cell::new(false),
|
||||
|
||||
timeout_cancel: DOMRefCell::new(None),
|
||||
|
@ -216,75 +212,40 @@ impl XMLHttpRequest {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_cors(context: Arc<Mutex<XHRContext>>,
|
||||
load_data: LoadData,
|
||||
req: CORSRequest,
|
||||
script_chan: Box<ScriptChan + Send>,
|
||||
core_resource_thread: CoreResourceThread) {
|
||||
struct CORSContext {
|
||||
xhr: Arc<Mutex<XHRContext>>,
|
||||
load_data: RefCell<Option<LoadData>>,
|
||||
req: CORSRequest,
|
||||
script_chan: Box<ScriptChan + Send>,
|
||||
core_resource_thread: CoreResourceThread,
|
||||
}
|
||||
|
||||
impl AsyncCORSResponseListener for CORSContext {
|
||||
fn response_available(&self, response: CORSResponse) {
|
||||
if response.network_error {
|
||||
let mut context = self.xhr.lock().unwrap();
|
||||
let xhr = context.xhr.root();
|
||||
xhr.process_partial_response(XHRProgress::Errored(context.gen_id, Error::Network));
|
||||
*context.sync_status.borrow_mut() = Some(Err(Error::Network));
|
||||
return;
|
||||
}
|
||||
|
||||
let mut load_data = self.load_data.borrow_mut().take().unwrap();
|
||||
load_data.cors = Some(ResourceCORSData {
|
||||
preflight: self.req.preflight_flag,
|
||||
origin: self.req.origin.clone()
|
||||
});
|
||||
|
||||
XMLHttpRequest::initiate_async_xhr(self.xhr.clone(), self.script_chan.clone(),
|
||||
self.core_resource_thread.clone(), load_data);
|
||||
}
|
||||
}
|
||||
|
||||
let cors_context = CORSContext {
|
||||
xhr: context,
|
||||
load_data: RefCell::new(Some(load_data)),
|
||||
req: req.clone(),
|
||||
script_chan: script_chan.clone(),
|
||||
core_resource_thread: core_resource_thread,
|
||||
};
|
||||
|
||||
req.http_fetch_async(box cors_context, script_chan);
|
||||
}
|
||||
|
||||
fn initiate_async_xhr(context: Arc<Mutex<XHRContext>>,
|
||||
script_chan: Box<ScriptChan + Send>,
|
||||
core_resource_thread: CoreResourceThread,
|
||||
load_data: LoadData) {
|
||||
impl AsyncResponseListener for XHRContext {
|
||||
fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>) {
|
||||
let xhr = self.xhr.root();
|
||||
let rv = xhr.process_headers_available(self.cors_request.clone(),
|
||||
self.gen_id,
|
||||
metadata);
|
||||
if rv.is_err() {
|
||||
init: RequestInit) {
|
||||
impl FetchResponseListener for XHRContext {
|
||||
fn process_request_body(&mut self) {
|
||||
// todo
|
||||
}
|
||||
fn process_request_eof(&mut self) {
|
||||
// todo
|
||||
}
|
||||
fn process_response(&mut self, metadata: Result<Metadata, NetworkError>) {
|
||||
let xhr = self.xhr.root();
|
||||
let rv = xhr.process_headers_available(self.gen_id,
|
||||
metadata);
|
||||
if rv.is_err() {
|
||||
*self.sync_status.borrow_mut() = Some(rv);
|
||||
}
|
||||
}
|
||||
fn process_response_chunk(&mut self, mut chunk: Vec<u8>) {
|
||||
self.buf.borrow_mut().append(&mut chunk);
|
||||
self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone());
|
||||
}
|
||||
fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
|
||||
let rv = match response {
|
||||
Ok(()) => {
|
||||
self.xhr.root().process_response_complete(self.gen_id, Ok(()))
|
||||
}
|
||||
Err(e) => {
|
||||
self.xhr.root().process_response_complete(self.gen_id, Err(e))
|
||||
}
|
||||
};
|
||||
*self.sync_status.borrow_mut() = Some(rv);
|
||||
}
|
||||
}
|
||||
|
||||
fn data_available(&mut self, payload: Vec<u8>) {
|
||||
self.buf.borrow_mut().extend_from_slice(&payload);
|
||||
self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone());
|
||||
}
|
||||
|
||||
fn response_complete(&mut self, status: Result<(), NetworkError>) {
|
||||
let rv = self.xhr.root().process_response_complete(self.gen_id, status);
|
||||
*self.sync_status.borrow_mut() = Some(rv);
|
||||
}
|
||||
}
|
||||
|
||||
impl PreInvoke for XHRContext {
|
||||
|
@ -298,13 +259,10 @@ impl XMLHttpRequest {
|
|||
context: context,
|
||||
script_chan: script_chan,
|
||||
};
|
||||
let response_target = AsyncResponseTarget {
|
||||
sender: action_sender,
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
listener.notify_fetch(message.to().unwrap());
|
||||
});
|
||||
core_resource_thread.send(Load(load_data, LoadConsumer::Listener(response_target), None)).unwrap();
|
||||
core_resource_thread.send(Fetch(init, action_sender)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -563,12 +521,15 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
Method::Get | Method::Head => None,
|
||||
_ => data
|
||||
};
|
||||
// Step 4
|
||||
// Step 4 (first half)
|
||||
let extracted = data.as_ref().map(|d| d.extract());
|
||||
|
||||
self.request_body_len.set(extracted.as_ref().map_or(0, |e| e.0.len()));
|
||||
|
||||
// todo preserved headers?
|
||||
|
||||
// Step 6
|
||||
self.upload_events.set(false);
|
||||
self.upload_complete.set(false);
|
||||
// Step 7
|
||||
self.upload_complete.set(match extracted {
|
||||
None => true,
|
||||
|
@ -580,11 +541,6 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
|
||||
// Step 9
|
||||
if !self.sync.get() {
|
||||
let event_target = self.upload.upcast::<EventTarget>();
|
||||
if event_target.has_handlers() {
|
||||
self.upload_events.set(true);
|
||||
}
|
||||
|
||||
// If one of the event handlers below aborts the fetch by calling
|
||||
// abort or open we will need the current generation id to detect it.
|
||||
// Substep 1
|
||||
|
@ -604,67 +560,108 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
|
|||
}
|
||||
|
||||
// Step 5
|
||||
let global = self.global();
|
||||
//TODO - set referrer_policy/referrer_url in request
|
||||
let has_handlers = self.upload.upcast::<EventTarget>().has_handlers();
|
||||
let credentials_mode = if self.with_credentials.get() {
|
||||
CredentialsMode::Include
|
||||
} else {
|
||||
CredentialsMode::CredentialsSameOrigin
|
||||
};
|
||||
let use_url_credentials = if let Some(ref url) = *self.request_url.borrow() {
|
||||
!url.username().is_empty() || url.password().is_some()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut load_data =
|
||||
LoadData::new(LoadContext::Browsing,
|
||||
self.request_url.borrow().clone().unwrap(),
|
||||
self);
|
||||
let bypass_cross_origin_check = {
|
||||
// We want to be able to do cross-origin requests in browser.html.
|
||||
// If the XHR happens in a top level window and the mozbrowser
|
||||
// preference is enabled, we allow bypassing the CORS check.
|
||||
// This is a temporary measure until we figure out Servo privilege
|
||||
// story. See https://github.com/servo/servo/issues/9582
|
||||
if let GlobalRoot::Window(win) = self.global() {
|
||||
let is_root_pipeline = win.parent_info().is_none();
|
||||
let is_mozbrowser_enabled = mozbrowser_enabled();
|
||||
is_root_pipeline && is_mozbrowser_enabled
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if load_data.url.origin().ne(&global.r().get_url().origin()) {
|
||||
load_data.credentials_flag = self.WithCredentials();
|
||||
let mut request = RequestInit {
|
||||
method: self.request_method.borrow().clone(),
|
||||
url: self.request_url.borrow().clone().unwrap(),
|
||||
headers: (*self.request_headers.borrow()).clone(),
|
||||
unsafe_request: true,
|
||||
same_origin_data: true,
|
||||
// XXXManishearth figure out how to avoid this clone
|
||||
body: extracted.as_ref().map(|e| e.0.clone()),
|
||||
// XXXManishearth actually "subresource", but it doesn't exist
|
||||
// https://github.com/whatwg/xhr/issues/71
|
||||
destination: Destination::None,
|
||||
synchronous: self.sync.get(),
|
||||
mode: RequestMode::CORSMode,
|
||||
use_cors_preflight: has_handlers,
|
||||
credentials_mode: credentials_mode,
|
||||
use_url_credentials: use_url_credentials,
|
||||
origin: self.global().r().get_url(),
|
||||
referer_url: self.referrer_url.clone(),
|
||||
referrer_policy: self.referrer_policy.clone(),
|
||||
};
|
||||
|
||||
if bypass_cross_origin_check {
|
||||
request.mode = RequestMode::Navigate;
|
||||
}
|
||||
load_data.data = extracted.as_ref().map(|e| e.0.clone());
|
||||
|
||||
// XHR spec differs from http, and says UTF-8 should be in capitals,
|
||||
// instead of "utf-8", which is what Hyper defaults to. So not
|
||||
// using content types provided by Hyper.
|
||||
let n = "content-type";
|
||||
// step 4 (second half)
|
||||
match extracted {
|
||||
Some((_, Some(ref content_type))) =>
|
||||
load_data.headers.set_raw(n.to_owned(), vec![content_type.bytes().collect()]),
|
||||
Some((_, ref content_type)) => {
|
||||
// this should handle Document bodies too, not just BodyInit
|
||||
let encoding = if let Some(BodyInit::String(_)) = data {
|
||||
// XHR spec differs from http, and says UTF-8 should be in capitals,
|
||||
// instead of "utf-8", which is what Hyper defaults to. So not
|
||||
// using content types provided by Hyper.
|
||||
Some(MimeValue::Ext("UTF-8".to_string()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut content_type_set = false;
|
||||
if let Some(ref ct) = *content_type {
|
||||
if !request.headers.has::<ContentType>() {
|
||||
request.headers.set_raw("content-type", vec![ct.bytes().collect()]);
|
||||
content_type_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !content_type_set {
|
||||
let ct = request.headers.get::<ContentType>().map(|x| x.clone());
|
||||
if let Some(mut ct) = ct {
|
||||
if let Some(encoding) = encoding {
|
||||
for param in &mut (ct.0).2 {
|
||||
if param.0 == MimeAttr::Charset {
|
||||
if !param.0.as_str().eq_ignore_ascii_case(encoding.as_str()) {
|
||||
*param = (MimeAttr::Charset, encoding.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove instead of mutate in place
|
||||
// https://github.com/hyperium/hyper/issues/821
|
||||
request.headers.remove_raw("content-type");
|
||||
request.headers.set(ct);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
load_data.preserved_headers = (*self.request_headers.borrow()).clone();
|
||||
|
||||
if !load_data.preserved_headers.has::<Accept>() {
|
||||
let mime = Mime(mime::TopLevel::Star, mime::SubLevel::Star, vec![]);
|
||||
load_data.preserved_headers.set(Accept(vec![qitem(mime)]));
|
||||
}
|
||||
|
||||
load_data.method = (*self.request_method.borrow()).clone();
|
||||
|
||||
// CORS stuff
|
||||
let global = self.global();
|
||||
let referer_url = self.global().r().get_url();
|
||||
let mode = if self.upload_events.get() {
|
||||
RequestMode::ForcedPreflight
|
||||
} else {
|
||||
RequestMode::CORS
|
||||
};
|
||||
let mut combined_headers = load_data.headers.clone();
|
||||
combined_headers.extend(load_data.preserved_headers.iter());
|
||||
let cors_request = CORSRequest::maybe_new(referer_url.clone(),
|
||||
load_data.url.clone(),
|
||||
mode,
|
||||
load_data.method.clone(),
|
||||
combined_headers,
|
||||
true);
|
||||
match cors_request {
|
||||
Ok(None) => {
|
||||
let bytes = referer_url[..Position::AfterPath].as_bytes().to_vec();
|
||||
self.request_headers.borrow_mut().set_raw("Referer".to_owned(), vec![bytes]);
|
||||
},
|
||||
Ok(Some(ref req)) => self.insert_trusted_header("origin".to_owned(),
|
||||
req.origin.to_string()),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
debug!("request_headers = {:?}", *self.request_headers.borrow());
|
||||
debug!("request.headers = {:?}", request.headers);
|
||||
|
||||
self.fetch_time.set(time::now().to_timespec().sec);
|
||||
let rv = self.fetch(load_data, cors_request, global.r());
|
||||
|
||||
let rv = self.fetch(request, self.global().r());
|
||||
// Step 10
|
||||
if self.sync.get() {
|
||||
return rv;
|
||||
|
@ -879,7 +876,7 @@ impl XMLHttpRequest {
|
|||
event.fire(self.upcast());
|
||||
}
|
||||
|
||||
fn process_headers_available(&self, cors_request: Option<CORSRequest>,
|
||||
fn process_headers_available(&self,
|
||||
gen_id: GenerationId, metadata: Result<Metadata, NetworkError>)
|
||||
-> Result<(), Error> {
|
||||
let metadata = match metadata {
|
||||
|
@ -890,35 +887,6 @@ impl XMLHttpRequest {
|
|||
},
|
||||
};
|
||||
|
||||
let bypass_cross_origin_check = {
|
||||
// We want to be able to do cross-origin requests in browser.html.
|
||||
// If the XHR happens in a top level window and the mozbrowser
|
||||
// preference is enabled, we allow bypassing the CORS check.
|
||||
// This is a temporary measure until we figure out Servo privilege
|
||||
// story. See https://github.com/servo/servo/issues/9582
|
||||
if let GlobalRoot::Window(win) = self.global() {
|
||||
let is_root_pipeline = win.parent_info().is_none();
|
||||
let is_mozbrowser_enabled = mozbrowser_enabled();
|
||||
is_root_pipeline && is_mozbrowser_enabled
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !bypass_cross_origin_check {
|
||||
if let Some(ref req) = cors_request {
|
||||
match metadata.headers {
|
||||
Some(ref h) if allow_cross_origin_request(req, h) => {},
|
||||
_ => {
|
||||
self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network));
|
||||
return Err(Error::Network);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Bypassing cross origin check");
|
||||
}
|
||||
|
||||
*self.response_url.borrow_mut() = metadata.final_url[..Position::AfterQuery].to_owned();
|
||||
|
||||
// XXXManishearth Clear cache entries in case of a network error
|
||||
|
@ -1318,25 +1286,12 @@ impl XMLHttpRequest {
|
|||
}
|
||||
|
||||
fn fetch(&self,
|
||||
load_data: LoadData,
|
||||
cors_request: Result<Option<CORSRequest>, ()>,
|
||||
init: RequestInit,
|
||||
global: GlobalRef) -> ErrorResult {
|
||||
let cors_request = match cors_request {
|
||||
Err(_) => {
|
||||
// Happens in case of unsupported cross-origin URI schemes.
|
||||
// Supported schemes are http, https, data, and about.
|
||||
self.process_partial_response(XHRProgress::Errored(
|
||||
self.generation_id.get(), Error::Network));
|
||||
return Err(Error::Network);
|
||||
}
|
||||
Ok(req) => req,
|
||||
};
|
||||
|
||||
let xhr = Trusted::new(self);
|
||||
|
||||
let context = Arc::new(Mutex::new(XHRContext {
|
||||
xhr: xhr,
|
||||
cors_request: cors_request.clone(),
|
||||
gen_id: self.generation_id.get(),
|
||||
buf: DOMRefCell::new(vec!()),
|
||||
sync_status: DOMRefCell::new(None),
|
||||
|
@ -1350,13 +1305,8 @@ impl XMLHttpRequest {
|
|||
};
|
||||
|
||||
let core_resource_thread = global.core_resource_thread();
|
||||
if let Some(req) = cors_request {
|
||||
XMLHttpRequest::check_cors(context.clone(), load_data, req.clone(),
|
||||
script_chan.clone(), core_resource_thread);
|
||||
} else {
|
||||
XMLHttpRequest::initiate_async_xhr(context.clone(), script_chan,
|
||||
core_resource_thread, load_data);
|
||||
}
|
||||
XMLHttpRequest::initiate_async_xhr(context.clone(), script_chan,
|
||||
core_resource_thread, init);
|
||||
|
||||
if let Some(script_port) = script_port {
|
||||
loop {
|
||||
|
|
|
@ -79,7 +79,6 @@ extern crate style;
|
|||
extern crate time;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
extern crate tinyfiledialogs;
|
||||
extern crate unicase;
|
||||
extern crate url;
|
||||
#[macro_use]
|
||||
extern crate util;
|
||||
|
@ -91,7 +90,6 @@ extern crate xml5ever;
|
|||
mod blob_url_store;
|
||||
pub mod bluetooth_blacklist;
|
||||
pub mod clipboard_provider;
|
||||
pub mod cors;
|
||||
mod devtools;
|
||||
pub mod document_loader;
|
||||
#[macro_use]
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
* 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 net_traits::{AsyncResponseListener, ResponseAction};
|
||||
use net_traits::{Action, AsyncResponseListener, FetchResponseListener};
|
||||
use net_traits::{FetchResponseMsg, ResponseAction};
|
||||
use script_runtime::ScriptThreadEventCategory::NetworkEvent;
|
||||
use script_runtime::{CommonScriptMsg, ScriptChan};
|
||||
use script_thread::Runnable;
|
||||
|
@ -10,13 +11,13 @@ use std::sync::{Arc, Mutex};
|
|||
|
||||
/// An off-thread sink for async network event runnables. All such events are forwarded to
|
||||
/// a target thread, where they are invoked on the provided context object.
|
||||
pub struct NetworkListener<T: AsyncResponseListener + PreInvoke + Send + 'static> {
|
||||
pub context: Arc<Mutex<T>>,
|
||||
pub struct NetworkListener<Listener: PreInvoke + Send + 'static> {
|
||||
pub context: Arc<Mutex<Listener>>,
|
||||
pub script_chan: Box<ScriptChan + Send>,
|
||||
}
|
||||
|
||||
impl<T: AsyncResponseListener + PreInvoke + Send + 'static> NetworkListener<T> {
|
||||
pub fn notify(&self, action: ResponseAction) {
|
||||
impl<Listener: PreInvoke + Send + 'static> NetworkListener<Listener> {
|
||||
pub fn notify<A: Action<Listener> + Send + 'static>(&self, action: A) {
|
||||
if let Err(err) = self.script_chan.send(CommonScriptMsg::RunnableMsg(NetworkEvent, box ListenerRunnable {
|
||||
context: self.context.clone(),
|
||||
action: action,
|
||||
|
@ -26,6 +27,20 @@ impl<T: AsyncResponseListener + PreInvoke + Send + 'static> NetworkListener<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// helps type inference
|
||||
impl<Listener: AsyncResponseListener + PreInvoke + Send + 'static> NetworkListener<Listener> {
|
||||
pub fn notify_action(&self, action: ResponseAction) {
|
||||
self.notify(action);
|
||||
}
|
||||
}
|
||||
|
||||
// helps type inference
|
||||
impl<Listener: FetchResponseListener + PreInvoke + Send + 'static> NetworkListener<Listener> {
|
||||
pub fn notify_fetch(&self, action: FetchResponseMsg) {
|
||||
self.notify(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// A gating mechanism that runs before invoking the runnable on the target thread.
|
||||
/// If the `should_invoke` method returns false, the runnable is discarded without
|
||||
/// being invoked.
|
||||
|
@ -36,13 +51,13 @@ pub trait PreInvoke {
|
|||
}
|
||||
|
||||
/// A runnable for moving the async network events between threads.
|
||||
struct ListenerRunnable<T: AsyncResponseListener + PreInvoke + Send> {
|
||||
context: Arc<Mutex<T>>,
|
||||
action: ResponseAction,
|
||||
struct ListenerRunnable<A: Action<Listener> + Send + 'static, Listener: PreInvoke + Send> {
|
||||
context: Arc<Mutex<Listener>>,
|
||||
action: A,
|
||||
}
|
||||
|
||||
impl<T: AsyncResponseListener + PreInvoke + Send> Runnable for ListenerRunnable<T> {
|
||||
fn handler(self: Box<ListenerRunnable<T>>) {
|
||||
impl<A: Action<Listener> + Send + 'static, Listener: PreInvoke + Send> Runnable for ListenerRunnable<A, Listener> {
|
||||
fn handler(self: Box<ListenerRunnable<A, Listener>>) {
|
||||
let this = *self;
|
||||
let mut context = this.context.lock().unwrap();
|
||||
if context.should_invoke() {
|
||||
|
|
|
@ -1962,7 +1962,7 @@ impl ScriptThread {
|
|||
script_chan: self.chan.clone(),
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
listener.notify_action(message.to().unwrap());
|
||||
});
|
||||
let response_target = AsyncResponseTarget {
|
||||
sender: action_sender,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue