servo: Merge #19329 - Add RAII guard for cancelling fetch when the consumer no longer cares about it (from Manishearth:fetchcanceller); r=jdm
authorManish Goregaokar <manishsmail@gmail.com>
Wed, 22 Nov 2017 18:30:57 -0600
changeset 437836 6286ca9b5067a317c812a473fd583ebafc3229b7
parent 437835 987eb1eb9b47cf5df71caedec28552211ceeb796
child 437837 c009848f854e8cfe733bb2f2a1ddc53737a82c33
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersjdm
milestone59.0a1
servo: Merge #19329 - Add RAII guard for cancelling fetch when the consumer no longer cares about it (from Manishearth:fetchcanceller); r=jdm Source-Repo: https://github.com/servo/servo Source-Revision: 976f9e3d13b6a7676dd863dd397dd6c60e868d58
servo/components/constellation/constellation.rs
servo/components/constellation/network_listener.rs
servo/components/script/dom/document.rs
servo/components/script/dom/domimplementation.rs
servo/components/script/dom/domparser.rs
servo/components/script/dom/node.rs
servo/components/script/dom/servoparser/mod.rs
servo/components/script/dom/xmldocument.rs
servo/components/script/dom/xmlhttprequest.rs
servo/components/script/fetch.rs
servo/components/script/script_thread.rs
servo/components/script_traits/script_msg.rs
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1105,19 +1105,19 @@ impl<Message, LTF, STF> Constellation<Me
             .get(&BrowsingContextId::from(source_top_ctx_id))
             .map(|ctx| ctx.pipeline_id == source_pipeline_id)
             .unwrap_or(false);
 
         match content {
             FromScriptMsg::PipelineExited => {
                 self.handle_pipeline_exited(source_pipeline_id);
             }
-            FromScriptMsg::InitiateNavigateRequest(req_init) => {
+            FromScriptMsg::InitiateNavigateRequest(req_init, cancel_chan) => {
                 debug!("constellation got initiate navigate request message");
-                self.handle_navigate_request(source_pipeline_id, req_init);
+                self.handle_navigate_request(source_pipeline_id, req_init, cancel_chan);
             }
             FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => {
                 debug!("constellation got iframe URL load message {:?} {:?} {:?}",
                        load_info.info.parent_pipeline_id,
                        load_info.old_pipeline_id,
                        load_info.info.new_pipeline_id);
                 self.handle_script_loaded_url_in_iframe_msg(load_info);
             }
@@ -1684,24 +1684,25 @@ impl<Message, LTF, STF> Constellation<Me
         };
         if let Err(e) = result {
             self.handle_send_error(parent_id, e);
         }
     }
 
     fn handle_navigate_request(&self,
                               id: PipelineId,
-                              req_init: RequestInit) {
+                              req_init: RequestInit,
+                              cancel_chan: IpcReceiver<()>) {
         let listener = NetworkListener::new(
                            req_init,
                            id,
                            self.public_resource_threads.clone(),
                            self.network_listener_sender.clone());
 
-        listener.initiate_fetch();
+        listener.initiate_fetch(Some(cancel_chan));
     }
 
     // The script thread associated with pipeline_id has loaded a URL in an iframe via script. This
     // will result in a new pipeline being spawned and a child being added to
     // the parent pipeline. This message is never the result of a
     // page navigation.
     fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfoWithData) {
         let (load_data, window_size, is_private) = {
--- a/servo/components/constellation/network_listener.rs
+++ b/servo/components/constellation/network_listener.rs
@@ -36,17 +36,17 @@ impl NetworkListener {
             req_init,
             pipeline_id,
             resource_threads,
             sender,
             should_send: false
         }
     }
 
-    pub fn initiate_fetch(&self) {
+    pub fn initiate_fetch(&self, cancel_chan: Option<ipc::IpcReceiver<()>>) {
         let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!");
 
         let mut listener = NetworkListener {
             res_init: self.res_init.clone(),
             req_init: self.req_init.clone(),
             resource_threads: self.resource_threads.clone(),
             sender: self.sender.clone(),
             pipeline_id: self.pipeline_id.clone(),
@@ -59,17 +59,17 @@ impl NetworkListener {
                                    res_init_.clone(),
                                    ipc_sender, None),
             None => {
                 set_default_accept(Destination::Document, &mut listener.req_init.headers);
                 set_default_accept_language(&mut listener.req_init.headers);
 
                 CoreResourceMsg::Fetch(
                 listener.req_init.clone(),
-                FetchChannels::ResponseMsg(ipc_sender, None))
+                FetchChannels::ResponseMsg(ipc_sender, cancel_chan))
             }
         };
 
         ROUTER.add_route(ipc_receiver.to_opaque(), Box::new(move |message| {
             let msg = message.to();
             match msg {
                 Ok(FetchResponseMsg::ProcessResponse(res)) => listener.check_redirect(res),
                 Ok(msg_) => listener.send(msg_),
@@ -103,17 +103,21 @@ impl NetworkListener {
 
                         self.res_init = Some(ResponseInit {
                             url: metadata.final_url.clone(),
                             location_url: metadata.location_url.clone(),
                             headers: headers.clone().into_inner(),
                             referrer: metadata.referrer.clone(),
                         });
 
-                        self.initiate_fetch();
+                        // XXXManishearth we don't have the cancel_chan anymore and
+                        // can't use it here.
+                        //
+                        // Ideally the Fetch code would handle manual redirects on its own
+                        self.initiate_fetch(None);
                     },
                     _ => {
                         // Response should be processed by script thread.
                         self.should_send = true;
                         self.send(FetchResponseMsg::ProcessResponse(Ok(res_metadata.clone())));
                     }
                 };
             },
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -86,16 +86,17 @@ use dom::treewalker::TreeWalker;
 use dom::uievent::UIEvent;
 use dom::virtualmethods::vtable_for;
 use dom::webglcontextevent::WebGLContextEvent;
 use dom::window::{ReflowReason, Window};
 use dom::windowproxy::WindowProxy;
 use dom_struct::dom_struct;
 use encoding_rs::{Encoding, UTF_8};
 use euclid::Point2D;
+use fetch::FetchCanceller;
 use html5ever::{LocalName, Namespace, QualName};
 use hyper::header::{Header, SetCookie};
 use hyper_serde::Serde;
 use ipc_channel::ipc::{self, IpcSender};
 use js::jsapi::{JSContext, JSRuntime};
 use js::jsapi::JS_GetRuntime;
 use metrics::{InteractiveFlag, InteractiveMetrics, InteractiveWindow, ProfilerMetadataFactory, ProgressiveWebMetric};
 use msg::constellation_msg::{BrowsingContextId, Key, KeyModifiers, KeyState, TopLevelBrowsingContextId};
@@ -355,16 +356,18 @@ pub struct Document {
     /// Map from ID to set of form control elements that have that ID as
     /// their 'form' content attribute. Used to reset form controls
     /// whenever any element with the same ID as the form attribute
     /// is inserted or removed from the document.
     /// See https://html.spec.whatwg.org/multipage/#form-owner
     form_id_listener_map: DomRefCell<HashMap<Atom, HashSet<Dom<Element>>>>,
     interactive_time: DomRefCell<InteractiveMetrics>,
     tti_window: DomRefCell<InteractiveWindow>,
+    /// RAII canceller for Fetch
+    canceller: FetchCanceller,
 }
 
 #[derive(JSTraceable, MallocSizeOf)]
 struct ImagesFilter;
 impl CollectionFilter for ImagesFilter {
     fn filter(&self, elem: &Element, _root: &Node) -> bool {
         elem.is::<HTMLImageElement>()
     }
@@ -2160,17 +2163,18 @@ impl Document {
                          origin: MutableOrigin,
                          is_html_document: IsHTMLDocument,
                          content_type: Option<DOMString>,
                          last_modified: Option<String>,
                          activity: DocumentActivity,
                          source: DocumentSource,
                          doc_loader: DocumentLoader,
                          referrer: Option<String>,
-                         referrer_policy: Option<ReferrerPolicy>)
+                         referrer_policy: Option<ReferrerPolicy>,
+                         canceller: FetchCanceller)
                          -> Document {
         let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
 
         let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
             (DocumentReadyState::Loading, false)
         } else {
             (DocumentReadyState::Complete, true)
         };
@@ -2265,16 +2269,17 @@ impl Document {
             last_click_info: DomRefCell::new(None),
             ignore_destructive_writes_counter: Default::default(),
             spurious_animation_frames: Cell::new(0),
             dom_count: Cell::new(1),
             fullscreen_element: MutNullableDom::new(None),
             form_id_listener_map: Default::default(),
             interactive_time: DomRefCell::new(interactive_time),
             tti_window: DomRefCell::new(InteractiveWindow::new()),
+            canceller: canceller,
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-document-document
     pub fn Constructor(window: &Window) -> Fallible<DomRoot<Document>> {
         let doc = window.Document();
         let docloader = DocumentLoader::new(&*doc.loader());
         Ok(Document::new(window,
@@ -2283,46 +2288,49 @@ impl Document {
                          doc.origin().clone(),
                          IsHTMLDocument::NonHTMLDocument,
                          None,
                          None,
                          DocumentActivity::Inactive,
                          DocumentSource::NotFromParser,
                          docloader,
                          None,
-                         None))
+                         None,
+                         Default::default()))
     }
 
     pub fn new(window: &Window,
                has_browsing_context: HasBrowsingContext,
                url: Option<ServoUrl>,
                origin: MutableOrigin,
                doctype: IsHTMLDocument,
                content_type: Option<DOMString>,
                last_modified: Option<String>,
                activity: DocumentActivity,
                source: DocumentSource,
                doc_loader: DocumentLoader,
                referrer: Option<String>,
-               referrer_policy: Option<ReferrerPolicy>)
+               referrer_policy: Option<ReferrerPolicy>,
+               canceller: FetchCanceller)
                -> DomRoot<Document> {
         let document = reflect_dom_object(
             Box::new(Document::new_inherited(
                 window,
                 has_browsing_context,
                 url,
                 origin,
                 doctype,
                 content_type,
                 last_modified,
                 activity,
                 source,
                 doc_loader,
                 referrer,
-                referrer_policy
+                referrer_policy,
+                canceller
             )),
             window,
             DocumentBinding::Wrap
         );
         {
             let node = document.upcast::<Node>();
             node.set_owner_doc(&document);
         }
@@ -2469,17 +2477,18 @@ impl Document {
                                         MutableOrigin::new(ImmutableOrigin::new_opaque()),
                                         doctype,
                                         None,
                                         None,
                                         DocumentActivity::Inactive,
                                         DocumentSource::NotFromParser,
                                         DocumentLoader::new(&self.loader()),
                                         None,
-                                        None);
+                                        None,
+                                        Default::default());
             new_doc.appropriate_template_contents_owner_document.set(Some(&new_doc));
             new_doc
         })
     }
 
     pub fn get_element_by_id(&self, id: &Atom) -> Option<DomRoot<Element>> {
         self.id_map.borrow().get(&id).map(|ref elements| DomRoot::from_ref(&*(*elements)[0]))
     }
--- a/servo/components/script/dom/domimplementation.rs
+++ b/servo/components/script/dom/domimplementation.rs
@@ -132,17 +132,18 @@ impl DOMImplementationMethods for DOMImp
                                 self.document.origin().clone(),
                                 IsHTMLDocument::HTMLDocument,
                                 None,
                                 None,
                                 DocumentActivity::Inactive,
                                 DocumentSource::NotFromParser,
                                 loader,
                                 None,
-                                None);
+                                None,
+                                Default::default());
 
         {
             // Step 3.
             let doc_node = doc.upcast::<Node>();
             let doc_type = DocumentType::new(DOMString::from("html"), None, None, &doc);
             doc_node.AppendChild(doc_type.upcast()).unwrap();
         }
 
--- a/servo/components/script/dom/domparser.rs
+++ b/servo/components/script/dom/domparser.rs
@@ -65,17 +65,18 @@ impl DOMParserMethods for DOMParser {
                                              doc.origin().clone(),
                                              IsHTMLDocument::HTMLDocument,
                                              Some(content_type),
                                              None,
                                              DocumentActivity::Inactive,
                                              DocumentSource::FromParser,
                                              loader,
                                              None,
-                                             None);
+                                             None,
+                                             Default::default());
                 ServoParser::parse_html_document(&document, s, url);
                 document.set_ready_state(DocumentReadyState::Complete);
                 Ok(document)
             }
             Text_xml | Application_xml | Application_xhtml_xml => {
                 // FIXME: this should probably be FromParser when we actually parse the string (#3756).
                 let document = Document::new(&self.window,
                                              HasBrowsingContext::No,
@@ -83,15 +84,16 @@ impl DOMParserMethods for DOMParser {
                                              doc.origin().clone(),
                                              IsHTMLDocument::NonHTMLDocument,
                                              Some(content_type),
                                              None,
                                              DocumentActivity::Inactive,
                                              DocumentSource::NotFromParser,
                                              loader,
                                              None,
-                                             None);
+                                             None,
+                                             Default::default());
                 ServoParser::parse_xml_document(&document, s, url);
                 Ok(document)
             }
         }
     }
 }
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -1809,17 +1809,17 @@ impl Node {
                 let loader = DocumentLoader::new(&*document.loader());
                 let document = Document::new(window, HasBrowsingContext::No,
                                              Some(document.url()),
                                              // https://github.com/whatwg/dom/issues/378
                                              document.origin().clone(),
                                              is_html_doc, None,
                                              None, DocumentActivity::Inactive,
                                              DocumentSource::NotFromParser, loader,
-                                             None, None);
+                                             None, None, Default::default());
                 DomRoot::upcast::<Node>(document)
             },
             NodeTypeId::Element(..) => {
                 let element = node.downcast::<Element>().unwrap();
                 let name = QualName {
                     prefix: element.prefix().as_ref().map(|p| Prefix::from(&**p)),
                     ns: element.namespace().clone(),
                     local: element.local_name().clone()
--- a/servo/components/script/dom/servoparser/mod.rs
+++ b/servo/components/script/dom/servoparser/mod.rs
@@ -133,17 +133,18 @@ impl ServoParser {
                                      context_document.origin().clone(),
                                      IsHTMLDocument::HTMLDocument,
                                      None,
                                      None,
                                      DocumentActivity::Inactive,
                                      DocumentSource::FromParser,
                                      loader,
                                      None,
-                                     None);
+                                     None,
+                                     Default::default());
 
         // Step 2.
         document.set_quirks_mode(context_document.quirks_mode());
 
         // Step 11.
         let form = context_node.inclusive_ancestors()
             .find(|element| element.is::<HTMLFormElement>());
 
--- a/servo/components/script/dom/xmldocument.rs
+++ b/servo/components/script/dom/xmldocument.rs
@@ -43,17 +43,18 @@ impl XMLDocument {
                                               origin,
                                               is_html_document,
                                               content_type,
                                               last_modified,
                                               activity,
                                               source,
                                               doc_loader,
                                               None,
-                                              None),
+                                              None,
+                                              Default::default()),
         }
     }
 
     pub fn new(window: &Window,
                has_browsing_context: HasBrowsingContext,
                url: Option<ServoUrl>,
                origin: MutableOrigin,
                doctype: IsHTMLDocument,
--- a/servo/components/script/dom/xmlhttprequest.rs
+++ b/servo/components/script/dom/xmlhttprequest.rs
@@ -33,16 +33,17 @@ use dom::servoparser::ServoParser;
 use dom::urlsearchparams::URLSearchParams;
 use dom::window::Window;
 use dom::workerglobalscope::WorkerGlobalScope;
 use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
 use dom::xmlhttprequestupload::XMLHttpRequestUpload;
 use dom_struct::dom_struct;
 use encoding_rs::{Encoding, UTF_8};
 use euclid::Length;
+use fetch::FetchCanceller;
 use html5ever::serialize;
 use html5ever::serialize::SerializeOpts;
 use hyper::header::{ContentLength, ContentType, ContentEncoding};
 use hyper::header::Headers;
 use hyper::method::Method;
 use hyper::mime::{self, Attr as MimeAttr, Mime, Value as MimeValue};
 use hyper_serde::Serde;
 use ipc_channel::ipc;
@@ -149,18 +150,17 @@ pub struct XMLHttpRequest {
     send_flag: Cell<bool>,
 
     timeout_cancel: DomRefCell<Option<OneshotTimerHandle>>,
     fetch_time: Cell<i64>,
     generation_id: Cell<GenerationId>,
     response_status: Cell<Result<(), ()>>,
     referrer_url: Option<ServoUrl>,
     referrer_policy: Option<ReferrerPolicy>,
-    #[ignore_malloc_size_of = "channels are hard"]
-    cancellation_chan: DomRefCell<Option<ipc::IpcSender<()>>>,
+    canceller: DomRefCell<FetchCanceller>,
 }
 
 impl XMLHttpRequest {
     fn new_inherited(global: &GlobalScope) -> XMLHttpRequest {
         //TODO - update this when referrer policy implemented for workers
         let (referrer_url, referrer_policy) = if let Some(window) = global.downcast::<Window>() {
             let document = window.Document();
             (Some(document.url()), document.get_referrer_policy())
@@ -195,17 +195,17 @@ impl XMLHttpRequest {
             send_flag: Cell::new(false),
 
             timeout_cancel: DomRefCell::new(None),
             fetch_time: Cell::new(0),
             generation_id: Cell::new(GenerationId(0)),
             response_status: Cell::new(Ok(())),
             referrer_url: referrer_url,
             referrer_policy: referrer_policy,
-            cancellation_chan: DomRefCell::new(None),
+            canceller: DomRefCell::new(Default::default()),
         }
     }
     pub fn new(global: &GlobalScope) -> DomRoot<XMLHttpRequest> {
         reflect_dom_object(Box::new(XMLHttpRequest::new_inherited(global)),
                            global,
                            XMLHttpRequestBinding::Wrap)
     }
 
@@ -973,32 +973,34 @@ impl XMLHttpRequest {
                 }
             },
             XHRProgress::Done(_) => {
                 assert!(self.ready_state.get() == XMLHttpRequestState::HeadersReceived ||
                         self.ready_state.get() == XMLHttpRequestState::Loading ||
                         self.sync.get());
 
                 self.cancel_timeout();
+                self.canceller.borrow_mut().ignore();
 
                 // Part of step 11, send() (processing response end of file)
                 // XXXManishearth handle errors, if any (substep 2)
 
                 // Subsubsteps 6-8
                 self.send_flag.set(false);
 
                 self.change_ready_state(XMLHttpRequestState::Done);
                 return_if_fetch_was_terminated!();
                 // Subsubsteps 11-12
                 self.dispatch_response_progress_event(atom!("load"));
                 return_if_fetch_was_terminated!();
                 self.dispatch_response_progress_event(atom!("loadend"));
             },
             XHRProgress::Errored(_, e) => {
                 self.cancel_timeout();
+                self.canceller.borrow_mut().ignore();
 
                 self.discard_subsequent_responses();
                 self.send_flag.set(false);
                 // XXXManishearth set response to NetworkError
                 self.change_ready_state(XMLHttpRequestState::Done);
                 return_if_fetch_was_terminated!();
 
                 let errormsg = match e {
@@ -1018,22 +1020,17 @@ impl XMLHttpRequest {
                 self.dispatch_response_progress_event(Atom::from(errormsg));
                 return_if_fetch_was_terminated!();
                 self.dispatch_response_progress_event(atom!("loadend"));
             }
         }
     }
 
     fn terminate_ongoing_fetch(&self) {
-        if let Some(ref cancel_chan) = *self.cancellation_chan.borrow() {
-            // The receiver will be destroyed if the request has already completed;
-            // so we throw away the error. Cancellation is a courtesy call,
-            // we don't actually care if the other side heard.
-            let _ = cancel_chan.send(());
-        }
+        self.canceller.borrow_mut().cancel();
         let GenerationId(prev_id) = self.generation_id.get();
         self.generation_id.set(GenerationId(prev_id + 1));
         self.response_status.set(Ok(()));
     }
 
     fn dispatch_progress_event(&self, upload: bool, type_: Atom, loaded: u64, total: Option<u64>) {
         let (total_length, length_computable) = if self.response_headers.borrow().has::<ContentEncoding>() {
             (0, false)
@@ -1259,17 +1256,18 @@ impl XMLHttpRequest {
                       doc.origin().clone(),
                       is_html_document,
                       content_type,
                       None,
                       DocumentActivity::Inactive,
                       DocumentSource::FromParser,
                       docloader,
                       None,
-                      None)
+                      None,
+                      Default::default())
     }
 
     fn filter_response_headers(&self) -> Headers {
         // https://fetch.spec.whatwg.org/#concept-response-header-list
         use hyper::error::Result;
         use hyper::header::{Header, HeaderFormat};
         use hyper::header::SetCookie;
         use std::fmt;
@@ -1317,18 +1315,17 @@ impl XMLHttpRequest {
 
         let (task_source, script_port) = if self.sync.get() {
             let (tx, rx) = global.new_script_pair();
             (NetworkingTaskSource(tx, global.pipeline_id()), Some(rx))
         } else {
             (global.networking_task_source(), None)
         };
 
-        let (cancel_sender, cancel_receiver) = ipc::channel().unwrap();
-        *self.cancellation_chan.borrow_mut() = Some(cancel_sender);
+        let cancel_receiver = self.canceller.borrow_mut().initialize();
 
         XMLHttpRequest::initiate_async_xhr(context.clone(), task_source,
                                            global, init, cancel_receiver);
 
         if let Some(script_port) = script_port {
             loop {
                 global.process_event(script_port.recv().unwrap());
                 let context = context.lock().unwrap();
--- a/servo/components/script/fetch.rs
+++ b/servo/components/script/fetch.rs
@@ -33,16 +33,68 @@ use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 
 struct FetchContext {
     fetch_promise: Option<TrustedPromise>,
     response_object: Trusted<Response>,
     body: Vec<u8>,
 }
 
+/// RAII fetch canceller object. By default initialized to not having a canceller
+/// in it, however you can ask it for a cancellation receiver to send to Fetch
+/// in which case it will store the sender. You can manually cancel it
+/// or let it cancel on Drop in that case.
+#[derive(Default, JSTraceable, MallocSizeOf)]
+pub struct FetchCanceller {
+    #[ignore_malloc_size_of = "channels are hard"]
+    cancel_chan: Option<ipc::IpcSender<()>>
+}
+
+impl FetchCanceller {
+    /// Create an empty FetchCanceller
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Obtain an IpcReceiver to send over to Fetch, and initialize
+    /// the internal sender
+    pub fn initialize(&mut self) -> ipc::IpcReceiver<()> {
+        // cancel previous fetch
+        self.cancel();
+        let (rx, tx) = ipc::channel().unwrap();
+        self.cancel_chan = Some(rx);
+        tx
+    }
+
+    /// Cancel a fetch if it is ongoing
+    pub fn cancel(&mut self) {
+        if let Some(chan) = self.cancel_chan.take() {
+            // stop trying to make fetch happen
+            // it's not going to happen
+
+            // The receiver will be destroyed if the request has already completed;
+            // so we throw away the error. Cancellation is a courtesy call,
+            // we don't actually care if the other side heard.
+            let _ = chan.send(());
+        }
+    }
+
+    /// Use this if you don't want it to send a cancellation request
+    /// on drop (e.g. if the fetch completes)
+    pub fn ignore(&mut self) {
+        let _ = self.cancel_chan.take();
+    }
+}
+
+impl Drop for FetchCanceller {
+    fn drop(&mut self) {
+        self.cancel()
+    }
+}
+
 fn from_referrer_to_referrer_url(request: &NetTraitsRequest) -> Option<ServoUrl> {
     request.referrer.to_url().map(|url| url.clone())
 }
 
 fn request_init_from_request(request: NetTraitsRequest) -> NetTraitsRequestInit {
     NetTraitsRequestInit {
         method: request.method.clone(),
         url: request.url(),
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -57,16 +57,17 @@ use dom::servoparser::{ParserContext, Se
 use dom::transitionevent::TransitionEvent;
 use dom::uievent::UIEvent;
 use dom::window::{ReflowReason, Window};
 use dom::windowproxy::WindowProxy;
 use dom::worker::TrustedWorkerAddress;
 use dom::worklet::WorkletThreadPool;
 use dom::workletglobalscope::WorkletGlobalScopeInit;
 use euclid::{Point2D, Vector2D, Rect};
+use fetch::FetchCanceller;
 use hyper::header::{ContentType, HttpDate, Headers, LastModified};
 use hyper::header::ReferrerPolicy as ReferrerPolicyHeader;
 use hyper::mime::{Mime, SubLevel, TopLevel};
 use hyper_serde::Serde;
 use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use js::glue::GetWindowProxyClass;
 use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks};
@@ -165,16 +166,18 @@ struct InProgressLoad {
     /// The requested URL of the load.
     url: ServoUrl,
     /// The origin for the document
     origin: MutableOrigin,
     /// Timestamp reporting the time when the browser started this load.
     navigation_start: u64,
     /// High res timestamp reporting the time when the browser started this load.
     navigation_start_precise: u64,
+    /// For cancelling the fetch
+    canceller: FetchCanceller,
 }
 
 impl InProgressLoad {
     /// Create a new InProgressLoad object.
     fn new(id: PipelineId,
            browsing_context_id: BrowsingContextId,
            top_level_browsing_context_id: TopLevelBrowsingContextId,
            parent_info: Option<(PipelineId, FrameType)>,
@@ -193,16 +196,17 @@ impl InProgressLoad {
             layout_chan: layout_chan,
             window_size: window_size,
             activity: DocumentActivity::FullyActive,
             is_visible: true,
             url: url,
             origin: origin,
             navigation_start: (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64,
             navigation_start_precise: navigation_start_precise,
+            canceller: Default::default(),
         }
     }
 }
 
 #[derive(Debug)]
 enum MixedMessage {
     FromConstellation(ConstellationControlMsg),
     FromScript(MainThreadScriptMsg),
@@ -2210,17 +2214,18 @@ impl ScriptThread {
                                      incomplete.origin,
                                      is_html_document,
                                      content_type,
                                      last_modified,
                                      incomplete.activity,
                                      DocumentSource::FromParser,
                                      loader,
                                      referrer,
-                                     referrer_policy);
+                                     referrer_policy,
+                                     incomplete.canceller);
         document.set_ready_state(DocumentReadyState::Loading);
 
         self.documents.borrow_mut().insert(incomplete.pipeline_id, &*document);
 
         window.init_document(&document);
 
         self.script_sender
             .send((incomplete.pipeline_id, ScriptMsg::ActivateDocument))
@@ -2531,17 +2536,17 @@ impl ScriptThread {
         // https://html.spec.whatwg.org/multipage/#event-loop-processing-model
         // Step 7.7 - evaluate media queries and report changes
         // Since we have resized, we need to re-evaluate MQLs
         window.evaluate_media_queries_and_report_changes();
     }
 
     /// Instructs the constellation to fetch the document that will be loaded. Stores the InProgressLoad
     /// argument until a notification is received that the fetch is complete.
-    fn pre_page_load(&self, incomplete: InProgressLoad, load_data: LoadData) {
+    fn pre_page_load(&self, mut incomplete: InProgressLoad, load_data: LoadData) {
         let id = incomplete.pipeline_id.clone();
         let req_init = RequestInit {
             url: load_data.url.clone(),
             method: load_data.method,
             destination: Destination::Document,
             credentials_mode: CredentialsMode::Include,
             use_url_credentials: true,
             pipeline_id: Some(id),
@@ -2552,17 +2557,19 @@ impl ScriptThread {
             redirect_mode: RedirectMode::Manual,
             origin: incomplete.origin.immutable().clone(),
             .. RequestInit::default()
         };
 
         let context = ParserContext::new(id, load_data.url);
         self.incomplete_parser_contexts.borrow_mut().push((id, context));
 
-        self.script_sender.send((id, ScriptMsg::InitiateNavigateRequest(req_init))).unwrap();
+        let cancel_chan = incomplete.canceller.initialize();
+
+        self.script_sender.send((id, ScriptMsg::InitiateNavigateRequest(req_init, cancel_chan))).unwrap();
         self.incomplete_loads.borrow_mut().push(incomplete);
     }
 
     fn handle_fetch_metadata(&self, id: PipelineId, fetch_metadata: Result<FetchMetadata, NetworkError>) {
         match fetch_metadata {
             Ok(_) => {},
             Err(ref e) => warn!("Network error: {:?}", e),
         };
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -11,17 +11,17 @@ use LayoutControlMsg;
 use LoadData;
 use MozBrowserEvent;
 use WorkerGlobalScopeInit;
 use WorkerScriptLoadOrigin;
 use canvas_traits::canvas::CanvasMsg;
 use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
 use euclid::{Point2D, Size2D, TypedSize2D};
 use gfx_traits::Epoch;
-use ipc_channel::ipc::IpcSender;
+use ipc_channel::ipc::{IpcReceiver, IpcSender};
 use msg::constellation_msg::{BrowsingContextId, FrameType, PipelineId, TraversalDirection};
 use msg::constellation_msg::{Key, KeyModifiers, KeyState};
 use net_traits::CoreResourceMsg;
 use net_traits::request::RequestInit;
 use net_traits::storage_thread::StorageType;
 use servo_url::ImmutableOrigin;
 use servo_url::ServoUrl;
 use style_traits::CSSPixel;
@@ -66,17 +66,17 @@ pub enum LogEntry {
     Warn(String),
 }
 
 /// Messages from the script to the constellation.
 #[derive(Deserialize, Serialize)]
 pub enum ScriptMsg {
     /// Requests are sent to constellation and fetches are checked manually
     /// for cross-origin loads
-    InitiateNavigateRequest(RequestInit),
+    InitiateNavigateRequest(RequestInit, /* cancellation_chan */ IpcReceiver<()>),
     /// Broadcast a storage event to every same-origin pipeline.
     /// The strings are key, old value and new value.
     BroadcastStorageEvent(StorageType, ServoUrl, Option<String>, Option<String>, Option<String>),
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(AnimationState),
     /// Requests that a new 2D canvas thread be created. (This is done in the constellation because
     /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.)
     CreateCanvasPaintThread(Size2D<i32>, IpcSender<IpcSender<CanvasMsg>>),