servo: Merge #13323 - Implement the Fetch method (from jeenalee:fetch); r=jdm
authorJeena Lee <ijeenalee@gmail.com>
Thu, 29 Sep 2016 14:28:54 -0500
changeset 339775 ec00ba6cef79d82cd92d66db37caab7c3e153b87
parent 339774 fde9ddf9dd1e30bdbb6352445cef181b9f435d1c
child 339776 182b357adca7d3c3b5a591c81cf848abbeed8b63
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #13323 - Implement the Fetch method (from jeenalee:fetch); r=jdm <!-- Please describe your changes on the following line: --> This PR implements the fetch method, which is described in [the Fetch Spec](https://fetch.spec.whatwg.org/#fetch-method). The expected wpt results are updated as well. A few comments about the current fetch implementation: - The fetch method manually calls `JSAutoCompartment` in order to prevent SpiderMonkey from crashing. This may have to change in the future. - Not all `FetchResponseListener` methods are implemented. - `net_traits::CoreResourceMsg::Fetch` message takes a `net_traits::request::RequestInit`. However, when the fetch method is called, a `RequestBinding::RequestInit` is given. The fetch method constructs a `dom::request::Request` with the given `RequestInit`, then creates `net_traits::request::RequestInit` from the dom Request object for the fetch message. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11898 (github issue number if applicable). <!-- 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. --> Source-Repo: https://github.com/servo/servo Source-Revision: 5996e008a34dd92602acb7bbd3ea41a880053110
servo/components/script/dom/headers.rs
servo/components/script/dom/request.rs
servo/components/script/dom/response.rs
servo/components/script/dom/webidls/Fetch.webidl
servo/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl
servo/components/script/dom/window.rs
servo/components/script/dom/workerglobalscope.rs
servo/components/script/fetch.rs
servo/components/script/lib.rs
--- a/servo/components/script/dom/headers.rs
+++ b/servo/components/script/dom/headers.rs
@@ -224,16 +224,20 @@ impl Headers {
     pub fn get_guard(&self) -> Guard {
         self.guard.get()
     }
 
     pub fn empty_header_list(&self) {
         *self.header_list.borrow_mut() = HyperHeaders::new();
     }
 
+    pub fn set_headers(&self, hyper_headers: HyperHeaders) {
+        *self.header_list.borrow_mut() = hyper_headers;
+    }
+
     // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
     pub fn extract_mime_type(&self) -> Vec<u8> {
         self.header_list.borrow().get_raw("content-type").map_or(vec![], |v| v[0].clone())
     }
 
     pub fn sort_header_list(&self) -> Vec<(String, String)> {
         let borrowed_header_list = self.header_list.borrow();
         let headers_iter = borrowed_header_list.iter();
--- a/servo/components/script/dom/request.rs
+++ b/servo/components/script/dom/request.rs
@@ -440,16 +440,20 @@ impl Request {
             *borrowed_r_request.origin.borrow_mut() = req.origin.borrow().clone();
         }
         *r_clone.request.borrow_mut() = req.clone();
         r_clone.body_used.set(body_used);
         *r_clone.mime_type.borrow_mut() = mime_type;
         r_clone.Headers().set_guard(headers_guard);
         r_clone
     }
+
+    pub fn get_request(&self) -> NetTraitsRequest {
+        self.request.borrow().clone()
+    }
 }
 
 fn net_request_from_global(global: GlobalRef,
                            url: Url,
                            is_service_worker_global_scope: bool) -> NetTraitsRequest {
     let origin = Origin::Origin(global.get_url().origin());
     let pipeline_id = global.pipeline_id();
     NetTraitsRequest::new(url,
--- a/servo/components/script/dom/response.rs
+++ b/servo/components/script/dom/response.rs
@@ -13,17 +13,19 @@ use dom::bindings::error::{Error, Fallib
 use dom::bindings::global::GlobalRef;
 use dom::bindings::js::{JS, MutNullableHeap, Root};
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
 use dom::bindings::str::{ByteString, USVString};
 use dom::headers::{Headers, Guard};
 use dom::headers::{is_vchar, is_obs_text};
 use dom::promise::Promise;
 use dom::xmlhttprequest::Extractable;
+use hyper::header::Headers as HyperHeaders;
 use hyper::status::StatusCode;
+use hyper_serde::Serde;
 use net_traits::response::{ResponseBody as NetTraitsResponseBody};
 use std::mem;
 use std::rc::Rc;
 use std::str::FromStr;
 use style::refcell::Ref;
 use url::Position;
 use url::Url;
 
@@ -339,8 +341,29 @@ impl ResponseMethods for Response {
     fn Json(&self) -> Rc<Promise> {
         consume_body(self, BodyType::Json)
     }
 }
 
 fn serialize_without_fragment(url: &Url) -> &str {
     &url[..Position::AfterQuery]
 }
+
+impl Response {
+    pub fn set_type(&self, new_response_type: DOMResponseType) {
+        *self.response_type.borrow_mut() = new_response_type;
+    }
+
+    pub fn set_headers(&self, option_hyper_headers: Option<Serde<HyperHeaders>>) {
+        self.Headers().set_headers(match option_hyper_headers {
+            Some(hyper_headers) => hyper_headers.into_inner(),
+            None => HyperHeaders::new(),
+        });
+    }
+
+    pub fn set_raw_status(&self, status: Option<(u16, Vec<u8>)>) {
+        *self.raw_status.borrow_mut() = status;
+    }
+
+    pub fn set_final_url(&self, final_url: Url) {
+        *self.url.borrow_mut() = Some(final_url);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/Fetch.webidl
@@ -0,0 +1,11 @@
+/* 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/. */
+
+// https://fetch.spec.whatwg.org/#fetch-method
+
+[Exposed=(Window,Worker)]
+
+partial interface WindowOrWorkerGlobalScope {
+  [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
+};
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl
@@ -0,0 +1,30 @@
+/* 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/. */
+
+// https://html.spec.whatwg.org/multipage/#windoworworkerglobalscope
+
+// typedef (DOMString or Function) TimerHandler;
+
+[NoInterfaceObject, Exposed=(Window,Worker)]
+interface WindowOrWorkerGlobalScope {
+  // [Replaceable] readonly attribute USVString origin;
+
+  // base64 utility methods
+  // DOMString btoa(DOMString data);
+  // DOMString atob(DOMString data);
+
+  // timers
+  // long setTimeout(TimerHandler handler, optional long timeout = 0, any... arguments);
+  // void clearTimeout(optional long handle = 0);
+  // long setInterval(TimerHandler handler, optional long timeout = 0, any... arguments);
+  // void clearInterval(optional long handle = 0);
+
+  // ImageBitmap
+  // Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options);
+  // Promise<ImageBitmap> createImageBitmap(
+  //   ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options);
+};
+
+Window implements WindowOrWorkerGlobalScope;
+WorkerGlobalScope implements WindowOrWorkerGlobalScope;
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -7,18 +7,20 @@ use devtools_traits::{ScriptToDevtoolsCo
 use dom::bindings::callback::ExceptionHandling;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
 use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
 use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull;
 use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
 use dom::bindings::codegen::Bindings::FunctionBinding::Function;
 use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
 use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
 use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
+use dom::bindings::codegen::UnionTypes::RequestOrUSVString;
 use dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible, report_pending_exception};
 use dom::bindings::global::{GlobalRef, global_root_from_object};
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, MutNullableHeap, Root};
 use dom::bindings::num::Finite;
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::Reflectable;
 use dom::bindings::str::DOMString;
@@ -35,19 +37,21 @@ use dom::event::{Event, EventBubbles, Ev
 use dom::eventtarget::EventTarget;
 use dom::history::History;
 use dom::htmliframeelement::build_mozbrowser_custom_event;
 use dom::location::Location;
 use dom::messageevent::MessageEvent;
 use dom::navigator::Navigator;
 use dom::node::{Node, from_untrusted_node_address, window_from_node};
 use dom::performance::Performance;
+use dom::promise::Promise;
 use dom::screen::Screen;
 use dom::storage::Storage;
 use euclid::{Point2D, Rect, Size2D};
+use fetch;
 use gfx_traits::LayerId;
 use ipc_channel::ipc::{self, IpcSender};
 use js::jsapi::{Evaluate2, HandleObject, HandleValue, JSAutoCompartment, JSContext};
 use js::jsapi::{JS_GC, JS_GetRuntime, MutableHandleValue, SetWindowProxy};
 use js::jsval::UndefinedValue;
 use js::rust::CompileOptionsWrapper;
 use js::rust::Runtime;
 use libc;
@@ -897,16 +901,22 @@ impl WindowMethods for Window {
         let url = try!(Url::parse(&href).map_err(|e| {
             Error::Type(format!("Couldn't parse URL: {}", e))
         }));
         match open::that(url.as_str()) {
             Ok(_) => Ok(()),
             Err(e) => Err(Error::Type(format!("Couldn't open URL: {}", e))),
         }
     }
+
+    #[allow(unrooted_must_root)]
+    // https://fetch.spec.whatwg.org/#fetch-method
+    fn Fetch(&self, input: RequestOrUSVString, init: &RequestInit) -> Rc<Promise> {
+        fetch::Fetch(self.global().r(), input, init)
+    }
 }
 
 pub trait ScriptHelpers {
     fn evaluate_js_on_global_with_result(self, code: &str,
                                          rval: MutableHandleValue);
     fn evaluate_script_on_global_with_result(self, code: &str, filename: &str,
                                              rval: MutableHandleValue);
 }
--- a/servo/components/script/dom/workerglobalscope.rs
+++ b/servo/components/script/dom/workerglobalscope.rs
@@ -1,31 +1,35 @@
 /* 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 devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
 use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
 use dom::bindings::codegen::Bindings::FunctionBinding::Function;
+use dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
 use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
-use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception, ErrorInfo};
+use dom::bindings::codegen::UnionTypes::RequestOrUSVString;
+use dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible, report_pending_exception};
 use dom::bindings::global::{GlobalRef, GlobalRoot};
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, MutNullableHeap, Root};
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::Reflectable;
 use dom::bindings::str::DOMString;
 use dom::console::TimerSet;
 use dom::crypto::Crypto;
 use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
 use dom::eventtarget::EventTarget;
+use dom::promise::Promise;
 use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
 use dom::window::{base64_atob, base64_btoa};
 use dom::workerlocation::WorkerLocation;
 use dom::workernavigator::WorkerNavigator;
+use fetch;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, JSRuntime};
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
 use msg::constellation_msg::{PipelineId, ReferrerPolicy};
 use net_traits::{IpcSend, LoadOrigin};
 use net_traits::{LoadContext, ResourceThreads, load_whole_resource};
 use profile_traits::{mem, time};
@@ -388,16 +392,22 @@ impl WorkerGlobalScopeMethods for Worker
                                             IsInterval::Interval,
                                             TimerSource::FromWorker)
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
     fn ClearInterval(&self, handle: i32) {
         self.ClearTimeout(handle);
     }
+
+    #[allow(unrooted_must_root)]
+    // https://fetch.spec.whatwg.org/#fetch-method
+    fn Fetch(&self, input: RequestOrUSVString, init: &RequestInit) -> Rc<Promise> {
+        fetch::Fetch(self.global().r(), input, init)
+    }
 }
 
 
 impl WorkerGlobalScope {
     #[allow(unsafe_code)]
     pub fn execute_script(&self, source: DOMString) {
         rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue());
         match self.runtime.evaluate_script(
new file mode 100644
--- /dev/null
+++ b/servo/components/script/fetch.rs
@@ -0,0 +1,172 @@
+/* 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 dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
+use dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods;
+use dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
+use dom::bindings::codegen::UnionTypes::RequestOrUSVString;
+use dom::bindings::error::Error;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::Root;
+use dom::bindings::refcounted::{Trusted, TrustedPromise};
+use dom::bindings::reflector::Reflectable;
+use dom::headers::Guard;
+use dom::promise::Promise;
+use dom::request::Request;
+use dom::response::Response;
+use ipc_channel::ipc;
+use ipc_channel::router::ROUTER;
+use js::jsapi::JSAutoCompartment;
+use net_traits::{FetchResponseListener, NetworkError};
+use net_traits::{FilteredMetadata, FetchMetadata, Metadata};
+use net_traits::CoreResourceMsg::Fetch as NetTraitsFetch;
+use net_traits::request::Request as NetTraitsRequest;
+use net_traits::request::RequestInit as NetTraitsRequestInit;
+use network_listener::{NetworkListener, PreInvoke};
+use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+use url::Url;
+
+struct FetchContext {
+    fetch_promise: Option<TrustedPromise>,
+    response_object: Trusted<Response>,
+}
+
+fn from_referrer_to_referrer_url(request: &NetTraitsRequest) -> Option<Url> {
+    let referrer = request.referrer.borrow();
+    referrer.to_url().map(|url| url.clone())
+}
+
+fn request_init_from_request(request: NetTraitsRequest) -> NetTraitsRequestInit {
+    NetTraitsRequestInit {
+        method: request.method.borrow().clone(),
+        url: request.url(),
+        headers: request.headers.borrow().clone(),
+        unsafe_request: request.unsafe_request,
+        same_origin_data: request.same_origin_data.get(),
+        body: request.body.borrow().clone(),
+        type_: request.type_,
+        destination: request.destination,
+        synchronous: request.synchronous,
+        mode: request.mode,
+        use_cors_preflight: request.use_cors_preflight,
+        credentials_mode: request.credentials_mode,
+        use_url_credentials: request.use_url_credentials,
+        // TODO: NetTraitsRequestInit and NetTraitsRequest have different "origin"
+        // ... NetTraitsRequestInit.origin: Url
+        // ... NetTraitsRequest.origin: RefCell<Origin>
+        origin: request.url(),
+        referrer_url: from_referrer_to_referrer_url(&request),
+        referrer_policy: request.referrer_policy.get(),
+        pipeline_id: request.pipeline_id.get(),
+    }
+}
+
+// https://fetch.spec.whatwg.org/#fetch-method
+#[allow(unrooted_must_root)]
+pub fn Fetch(global: GlobalRef, input: RequestOrUSVString, init: &RequestInit) -> Rc<Promise> {
+    let core_resource_thread = global.core_resource_thread();
+
+    // Step 1
+    let promise = Promise::new(global);
+    let response = Response::new(global);
+
+    // Step 2
+    let request = match Request::Constructor(global, input, init) {
+        Err(e) => {
+            promise.reject_error(promise.global().r().get_cx(), e);
+            return promise;
+        },
+        Ok(r) => r.get_request(),
+    };
+    let request_init = request_init_from_request(request);
+
+    // Step 3
+    response.Headers().set_guard(Guard::Immutable);
+
+    // Step 4
+    let (action_sender, action_receiver) = ipc::channel().unwrap();
+    let fetch_context = Arc::new(Mutex::new(FetchContext {
+        fetch_promise: Some(TrustedPromise::new(promise.clone())),
+        response_object: Trusted::new(&*response),
+    }));
+    let listener = NetworkListener {
+        context: fetch_context,
+        script_chan: global.networking_task_source(),
+        wrapper: None,
+    };
+
+    ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
+        listener.notify_fetch(message.to().unwrap());
+    });
+    core_resource_thread.send(NetTraitsFetch(request_init, action_sender)).unwrap();
+
+    promise
+}
+
+impl PreInvoke for FetchContext {}
+
+impl FetchResponseListener for FetchContext {
+    fn process_request_body(&mut self) {
+        // TODO
+    }
+
+    fn process_request_eof(&mut self) {
+        // TODO
+    }
+
+    #[allow(unrooted_must_root)]
+    fn process_response(&mut self, fetch_metadata: Result<FetchMetadata, NetworkError>) {
+        let promise = self.fetch_promise.take().expect("fetch promise is missing").root();
+
+        // JSAutoCompartment needs to be manually made.
+        // Otherwise, Servo will crash.
+        let promise_cx = promise.global().r().get_cx();
+        let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get());
+        match fetch_metadata {
+            // Step 4.1
+            Err(_) => {
+                promise.reject_error(
+                    promise.global().r().get_cx(),
+                    Error::Type("Network error occurred".to_string()));
+                self.fetch_promise = Some(TrustedPromise::new(promise));
+                return;
+            },
+            // Step 4.2
+            Ok(metadata) => {
+                match metadata {
+                    FetchMetadata::Unfiltered(m) =>
+                        fill_headers_with_metadata(self.response_object.root(), m),
+                    FetchMetadata::Filtered { filtered, .. } => match filtered {
+                        FilteredMetadata::Transparent(m) =>
+                            fill_headers_with_metadata(self.response_object.root(), m),
+                        FilteredMetadata::Opaque =>
+                            self.response_object.root().set_type(DOMResponseType::Opaque),
+                    }
+                }
+            }
+        }
+        // Step 4.3
+        promise.resolve_native(
+            promise_cx,
+            &self.response_object.root());
+        self.fetch_promise = Some(TrustedPromise::new(promise));
+    }
+
+    fn process_response_chunk(&mut self, mut chunk: Vec<u8>) {
+        // TODO when body is implemented
+        // ... this will append the chunk to Response's body.
+    }
+
+    fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
+        // TODO
+        // ... trailerObject is not supported in Servo yet.
+    }
+}
+
+fn fill_headers_with_metadata(r: Root<Response>, m: Metadata) {
+    r.set_headers(m.headers);
+    r.set_raw_status(m.status);
+    r.set_final_url(m.final_url);
+}
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -93,16 +93,17 @@ extern crate xml5ever;
 
 pub mod bluetooth_blacklist;
 mod body;
 pub mod clipboard_provider;
 mod devtools;
 pub mod document_loader;
 #[macro_use]
 pub mod dom;
+pub mod fetch;
 pub mod layout_wrapper;
 mod mem;
 mod network_listener;
 pub mod origin;
 pub mod parse;
 pub mod script_runtime;
 #[allow(unsafe_code)]
 pub mod script_thread;