merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 09 Sep 2017 23:31:48 +0200
changeset 429396 d53ba311ca2f0c3d81d4a5e88a7449c18ec5e4b6
parent 429387 21699515e84bc218b3a963f4af426a9ed7c7f8a4 (current diff)
parent 429395 1a510d80a28f10b8b6ae08b39de5d6b11a4aef9a (diff)
child 429403 f8f481901e2d86dd5be8b8a9af1f81f67e1e9158
child 429428 d9d579592035ca53f43ef585ca83250fa944d216
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
d53ba311ca2f / 57.0a1 / 20170909220406 / files
nightly linux64
d53ba311ca2f / 57.0a1 / 20170909220406 / files
nightly mac
d53ba311ca2f / 57.0a1 / 20170909220406 / files
nightly win32
d53ba311ca2f / 57.0a1 / 20170909220406 / files
nightly win64
d53ba311ca2f / 57.0a1 / 20170909220406 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: 1WoO4FpD1Hs
--- a/devtools/shared/layout/dom-matrix-2d.js
+++ b/devtools/shared/layout/dom-matrix-2d.js
@@ -115,29 +115,30 @@ exports.apply = apply;
 const isIdentity = (M) =>
   M[0] === 1 && M[1] === 0 && M[2] === 0 &&
   M[3] === 0 && M[4] === 1 && M[5] === 0 &&
   M[6] === 0 && M[7] === 0 && M[8] === 1;
 exports.isIdentity = isIdentity;
 
 /**
  * Returns the transformation matrix for the given node, relative to the ancestor passed
- * as second argument.
+ * as second argument; considering the ancestor transformation too.
  * If no ancestor is specified, it will returns the transformation matrix relative to the
  * node's parent element.
  *
  * @param {DOMNode} node
  *        The node.
  * @param {DOMNode} ancestor
  *        The ancestor of the node given.
  ** @return {Array}
  *        The transformation matrix.
  */
 function getNodeTransformationMatrix(node, ancestor = node.parentElement) {
-  let { a, b, c, d, e, f } = node.getTransformToAncestor(ancestor);
+  let { a, b, c, d, e, f } = ancestor.getTransformToParent()
+                                     .multiply(node.getTransformToAncestor(ancestor));
 
   return [
     a, c, e,
     b, d, f,
     0, 0, 1
   ];
 }
 exports.getNodeTransformationMatrix = getNodeTransformationMatrix;
--- a/layout/style/test/test_css_parse_error_smoketest.html
+++ b/layout/style/test/test_css_parse_error_smoketest.html
@@ -31,16 +31,26 @@
 
     { css: "x| {}", error: "Unknown namespace prefix ‘x’.  Ruleset ignored due to bad selector." },
     { css: "a> {}", error: "Dangling combinator.  Ruleset ignored due to bad selector." },
     { css: "~ {}", error: "Selector expected.  Ruleset ignored due to bad selector." },
     { css: "| {}", error: "Expected element name or ‘*’ but found ‘ ’.  Ruleset ignored due to bad selector." },
     { css: ". {}", error: "Expected identifier for class selector but found ‘ ’.  Ruleset ignored due to bad selector." },
 
     { css: ":not() {}", error: "Missing argument in negation pseudo-class ‘)’.  Ruleset ignored due to bad selector." },
+
+    { css: "@media (totally-unknown-feature) {}", error: "Expected media feature name but found ‘totally-unknown-feature’." },
+    { css: "@media \"foo\" {}", error: "Expected identifier in media list but found ‘\"foo\"’." },
+    { css: "@media (min-width) {}", error: "Media features with min- or max- must have a value." },
+    { css: "@media (device-height: -1px) {}", error: "Found invalid value for media feature." },
+    { css: "@media (min-width: -1px) {}", error: "Found invalid value for media feature." },
+    { css: "@media (min-resolution: 2) {}", error: "Found invalid value for media feature." },
+    { css: "@media (min-monochrome: 1.1) {}", error: "Found invalid value for media feature." },
+    { css: "@media (min-aspect-ratio: 1) {}", error: "Found invalid value for media feature." },
+    { css: "@media (orientation: invalid-orientation-value) {}", error: "Found invalid value for media feature." },
   ];
 
   var test = -1;
   function nextTest() {
     test++;
     if (test == tests.length) {
       SimpleTest.finish();
       return;
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/BottomSheetContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/BottomSheetContextMenu.java
@@ -226,17 +226,17 @@ import java.net.URISyntaxException;
 
     /** Updates the given TextView's text to the page domain. */
     private static class UpdatePageDomainAsyncTask extends URIUtils.GetFormattedDomainAsyncTask {
         private final WeakReference<TextView> pageDomainViewWeakReference;
         private final String[] pageDomainTextReference;
 
         private UpdatePageDomainAsyncTask(final Context context, final TextView pageDomainView, final URI uri,
                 final String[] pageDomainTextReference) {
-            super(context, uri, true, 0); // baseDomain.
+            super(context, uri, true, 1); // subdomain.domain.tld.
             this.pageDomainViewWeakReference = new WeakReference<>(pageDomainView);
             this.pageDomainTextReference = pageDomainTextReference;
         }
 
         @Override
         protected void onPostExecute(final String baseDomain) {
             super.onPostExecute(baseDomain);
 
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/WebpageItemRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/WebpageItemRow.java
@@ -14,16 +14,17 @@ import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
 import org.mozilla.gecko.activitystream.Utils;
 import org.mozilla.gecko.activitystream.homepanel.StreamHighlightItemRowContextMenuListener;
 import org.mozilla.gecko.activitystream.homepanel.model.WebpageRowModel;
 import org.mozilla.gecko.util.DrawableUtil;
+import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.TouchTargetUtil;
 import org.mozilla.gecko.util.URIUtils;
 import org.mozilla.gecko.util.ViewUtil;
 
 import java.lang.ref.WeakReference;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.UUID;
@@ -139,36 +140,36 @@ public class WebpageItemRow extends Stre
     /** Updates the text of the given view to the host second level domain. */
     private static class UpdatePageDomainAsyncTask extends URIUtils.GetFormattedDomainAsyncTask {
         private static final int VIEW_TAG_ID = R.id.page; // same as the view.
 
         private final WeakReference<TextView> pageDomainViewWeakReference;
         private final UUID viewTagAtStart;
 
         UpdatePageDomainAsyncTask(final Context contextReference, final URI uri, final TextView pageDomainView) {
-            super(contextReference, uri, false, 0); // hostSLD.
+            super(contextReference, uri, false, 1); // subdomain.domain.
             this.pageDomainViewWeakReference = new WeakReference<>(pageDomainView);
 
             // See isTagSameAsStartTag for details.
             viewTagAtStart = UUID.randomUUID();
             pageDomainView.setTag(VIEW_TAG_ID, viewTagAtStart);
         }
 
         @Override
-        protected void onPostExecute(final String hostSLD) {
-            super.onPostExecute(hostSLD);
+        protected void onPostExecute(final String domainTitle) {
+            super.onPostExecute(domainTitle);
             final TextView viewToUpdate = pageDomainViewWeakReference.get();
 
             if (viewToUpdate == null || !isTagSameAsStartTag(viewToUpdate)) {
                 return;
             }
 
-            // hostSLD will be the empty String if the host cannot be determined. This is fine: it's very unlikely
+            // The title will be the empty String if the host cannot be determined. This is fine: it's very unlikely
             // and the page title will be URL if there's an error there so we wouldn't want to write the URL again here.
-            viewToUpdate.setText(hostSLD);
+            viewToUpdate.setText(StringUtils.stripCommonSubdomains(domainTitle));
         }
 
         /**
          * Returns true if the tag on the given view matches the tag from the constructor. We do this to ensure
          * the View we're making this request for hasn't been re-used by the time this request completes.
          */
         @UiThread
         private boolean isTagSameAsStartTag(final View viewToCheck) {
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1062,16 +1062,20 @@ impl<Message, LTF, STF> Constellation<Me
             }
             // Load a new page from a mouse click
             // If there is already a pending page (self.pending_changes), it will not be overridden;
             // However, if the id is not encompassed by another change, it will be.
             FromScriptMsg::LoadUrl(load_data, replace) => {
                 debug!("constellation got URL load message from script");
                 self.handle_load_url_msg(source_top_ctx_id, source_pipeline_id, load_data, replace);
             }
+            FromScriptMsg::AbortLoadUrl => {
+                debug!("constellation got abort URL load message from script");
+                self.handle_abort_load_url_msg(source_pipeline_id);
+            }
             // A page loaded has completed all parsing, script, and reflow messages have been sent.
             FromScriptMsg::LoadComplete => {
                 debug!("constellation got load complete message");
                 self.handle_load_complete_msg(source_top_ctx_id, source_pipeline_id)
             }
             // Handle a forward or back request
             FromScriptMsg::TraverseHistory(direction) => {
                 debug!("constellation got traverse history message from script");
@@ -1886,16 +1890,28 @@ impl<Message, LTF, STF> Constellation<Me
                     load_data: load_data,
                     replace_instant: replace_instant,
                 });
                 Some(new_pipeline_id)
             }
         }
     }
 
+    fn handle_abort_load_url_msg(&mut self, new_pipeline_id: PipelineId) {
+        let pending_index = self.pending_changes.iter().rposition(|change| {
+            change.new_pipeline_id == new_pipeline_id
+        });
+
+        // If it is found, remove it from the pending changes.
+        if let Some(pending_index) = pending_index {
+            self.pending_changes.remove(pending_index);
+            self.close_pipeline(new_pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
+        }
+    }
+
     fn handle_load_start_msg(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId,
                              pipeline_id: PipelineId) {
         if self.pipelines.get(&pipeline_id).and_then(|p| p.parent_info).is_none() {
             // Notify embedder top level document started loading.
             self.embedder_proxy.send(EmbedderMsg::LoadStart(top_level_browsing_context_id));
         }
     }
 
--- a/servo/components/script/dom/htmliframeelement.rs
+++ b/servo/components/script/dom/htmliframeelement.rs
@@ -39,17 +39,17 @@ use dom_struct::dom_struct;
 use html5ever::{LocalName, Prefix};
 use ipc_channel::ipc;
 use js::jsapi::{JSAutoCompartment, JSContext, MutableHandleValue};
 use js::jsval::{NullValue, UndefinedValue};
 use msg::constellation_msg::{FrameType, BrowsingContextId, PipelineId, TopLevelBrowsingContextId, TraversalDirection};
 use net_traits::response::HttpsState;
 use script_layout_interface::message::ReflowQueryType;
 use script_thread::{ScriptThread, Runnable};
-use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, LoadData, UpdatePipelineIdReason};
+use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, UpdatePipelineIdReason};
 use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptMsg};
 use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
 use servo_atoms::Atom;
 use servo_config::prefs::PREFS;
 use servo_config::servo_version;
 use servo_url::ServoUrl;
 use std::cell::Cell;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
@@ -109,17 +109,17 @@ impl HTMLIFrameElement {
                 None
             } else {
                 document_from_node(self).base_url().join(&url).ok()
             }
         }).unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap())
     }
 
     pub fn navigate_or_reload_child_browsing_context(&self,
-                                                     load_data: Option<LoadData>,
+                                                     mut load_data: Option<LoadData>,
                                                      nav_type: NavigationType,
                                                      replace: bool) {
         let sandboxed = if self.is_sandboxed() {
             IFrameSandboxed
         } else {
             IFrameUnsandboxed
         };
 
@@ -135,21 +135,36 @@ impl HTMLIFrameElement {
 
         let document = document_from_node(self);
 
         let mut load_blocker = self.load_blocker.borrow_mut();
         // Any oustanding load is finished from the point of view of the blocked
         // document; the new navigation will continue blocking it.
         LoadBlocker::terminate(&mut load_blocker);
 
+        if let Some(ref mut load_data) = load_data {
+            let is_javascript = load_data.url.scheme() == "javascript";
+            if is_javascript {
+                let window_proxy = self.GetContentWindow();
+                if let Some(window_proxy) = window_proxy {
+                    ScriptThread::eval_js_url(&window_proxy.global(), load_data);
+                }
+            }
+        }
+
         //TODO(#9592): Deal with the case where an iframe is being reloaded so url is None.
         //      The iframe should always have access to the nested context's active
         //      document URL through the browsing context.
         if let Some(ref load_data) = load_data {
-            *load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone())));
+            match load_data.js_eval_result {
+                Some(JsEvalResult::NoContent) => (),
+                _ => {
+                    *load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone())));
+                }
+            };
         }
 
         let window = window_from_node(self);
         let old_pipeline_id = self.pipeline_id();
         let new_pipeline_id = PipelineId::new();
         self.pending_pipeline_id.set(Some(new_pipeline_id));
         let private_iframe = self.privatebrowsing();
         let frame_type = if self.Mozbrowser() { FrameType::MozBrowserIFrame } else { FrameType::IFrame };
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -84,18 +84,18 @@ use net_traits::request::{CredentialsMod
 use net_traits::storage_thread::StorageType;
 use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan};
 use profile_traits::time::{self, ProfilerCategory, profile};
 use script_layout_interface::message::{self, Msg, NewLayoutThreadInfo, ReflowQueryType};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
 use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx};
 use script_traits::{CompositorEvent, ConstellationControlMsg, PaintMetricType};
 use script_traits::{DocumentActivity, DiscardBrowsingContext, EventResult};
-use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent};
-use script_traits::{NewLayoutInfo, ScriptToConstellationChan, ScriptMsg, UpdatePipelineIdReason};
+use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, MouseButton, MouseEventType};
+use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptToConstellationChan, ScriptMsg, UpdatePipelineIdReason};
 use script_traits::{ScriptThreadFactory, TimerEvent, TimerSchedulerMsg, TimerSource};
 use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
 use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
 use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent};
 use script_traits::webdriver_msg::WebDriverScriptCommand;
 use serviceworkerjob::{Job, JobQueue, AsyncJobHandler};
 use servo_config::opts;
 use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
@@ -117,16 +117,17 @@ use style::thread_state;
 use task_source::dom_manipulation::DOMManipulationTaskSource;
 use task_source::file_reading::FileReadingTaskSource;
 use task_source::history_traversal::HistoryTraversalTaskSource;
 use task_source::networking::NetworkingTaskSource;
 use task_source::performance_timeline::PerformanceTimelineTaskSource;
 use task_source::user_interaction::UserInteractionTaskSource;
 use time::{get_time, precise_time_ns, Tm};
 use url::Position;
+use url::percent_encoding::percent_decode;
 use webdriver_handlers;
 use webvr_traits::{WebVREvent, WebVRMsg};
 
 pub type ImageCacheMsg = (PipelineId, PendingImageResponse);
 
 thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None));
 thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = Cell::new(None));
 
@@ -1504,17 +1505,17 @@ impl ScriptThread {
                                            browsing_context_id,
                                            top_level_browsing_context_id,
                                            parent_info,
                                            layout_chan,
                                            window_size,
                                            load_data.url.clone(),
                                            origin);
         if load_data.url.as_str() == "about:blank" {
-            self.start_page_load_about_blank(new_load);
+            self.start_page_load_about_blank(new_load, load_data.js_eval_result);
         } else {
             self.pre_page_load(new_load, load_data);
         }
     }
 
     fn collect_reports(&self, reports_chan: ReportsChan) {
         let mut path_seg = String::from("url(");
         let mut dom_tree_size = 0;
@@ -1674,16 +1675,28 @@ impl ScriptThread {
     /// Kick off the document and frame tree creation process using the result.
     fn handle_page_headers_available(&self, id: &PipelineId,
                                      metadata: Option<Metadata>) -> Option<Root<ServoParser>> {
         let idx = self.incomplete_loads.borrow().iter().position(|load| { load.pipeline_id == *id });
         // The matching in progress load structure may not exist if
         // the pipeline exited before the page load completed.
         match idx {
             Some(idx) => {
+                // https://html.spec.whatwg.org/multipage/#process-a-navigate-response
+                // 2. If response's status is 204 or 205, then abort these steps.
+                match metadata {
+                    Some(Metadata { status: Some((204 ... 205, _)), .. }) => {
+                        self.script_sender
+                            .send((id.clone(), ScriptMsg::AbortLoadUrl))
+                            .unwrap();
+                        return None;
+                    },
+                    _ => ()
+                };
+
                 let load = self.incomplete_loads.borrow_mut().remove(idx);
                 metadata.map(|meta| self.load(meta, load))
             }
             None => {
                 assert!(self.closed_pipelines.borrow().contains(id));
                 None
             }
         }
@@ -2113,49 +2126,17 @@ impl ScriptThread {
 
         self.script_sender
             .send((incomplete.pipeline_id, ScriptMsg::ActivateDocument))
             .unwrap();
 
         // Notify devtools that a new script global exists.
         self.notify_devtools(document.Title(), final_url.clone(), (incomplete.pipeline_id, None));
 
-        let is_javascript = incomplete.url.scheme() == "javascript";
-        let parse_input = if is_javascript {
-            use url::percent_encoding::percent_decode;
-
-            // Turn javascript: URL into JS code to eval, according to the steps in
-            // https://html.spec.whatwg.org/multipage/#javascript-protocol
-
-            // This slice of the URL’s serialization is equivalent to (5.) to (7.):
-            // Start with the scheme data of the parsed URL;
-            // append question mark and query component, if any;
-            // append number sign and fragment component if any.
-            let encoded = &incomplete.url[Position::BeforePath..];
-
-            // Percent-decode (8.) and UTF-8 decode (9.)
-            let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();
-
-            // Script source is ready to be evaluated (11.)
-            unsafe {
-                let _ac = JSAutoCompartment::new(self.get_cx(), window.reflector().get_jsobject().get());
-                rooted!(in(self.get_cx()) let mut jsval = UndefinedValue());
-                window.upcast::<GlobalScope>().evaluate_js_on_global_with_result(
-                    &script_source, jsval.handle_mut());
-                let strval = DOMString::from_jsval(self.get_cx(),
-                                                   jsval.handle(),
-                                                   StringificationBehavior::Empty);
-                match strval {
-                    Ok(ConversionResult::Success(s)) => s,
-                    _ => DOMString::new(),
-                }
-            }
-        } else {
-            DOMString::new()
-        };
+        let parse_input = DOMString::new();
 
         document.set_https_state(metadata.https_state);
 
         if is_html_document == IsHTMLDocument::NonHTMLDocument {
             ServoParser::parse_xml_document(&document, parse_input, final_url);
         } else {
             ServoParser::parse_html_document(&document, parse_input, final_url);
         }
@@ -2324,33 +2305,78 @@ impl ScriptThread {
         document.handle_touch_event(self.js_runtime.rt(), event_type, identifier, point)
     }
 
     /// https://html.spec.whatwg.org/multipage/#navigating-across-documents
     /// The entry point for content to notify that a new load has been requested
     /// for the given pipeline (specifically the "navigate" algorithm).
     fn handle_navigate(&self, parent_pipeline_id: PipelineId,
                               browsing_context_id: Option<BrowsingContextId>,
-                              load_data: LoadData,
+                              mut load_data: LoadData,
                               replace: bool) {
+        let is_javascript = load_data.url.scheme() == "javascript";
+        if is_javascript {
+            let window = self.documents.borrow().find_window(parent_pipeline_id);
+            if let Some(window) = window {
+                ScriptThread::eval_js_url(window.upcast::<GlobalScope>(), &mut load_data);
+            }
+        }
+
         match browsing_context_id {
             Some(browsing_context_id) => {
                 let iframe = self.documents.borrow().find_iframe(parent_pipeline_id, browsing_context_id);
                 if let Some(iframe) = iframe {
                     iframe.navigate_or_reload_child_browsing_context(Some(load_data), NavigationType::Regular, replace);
                 }
             }
             None => {
                 self.script_sender
                     .send((parent_pipeline_id, ScriptMsg::LoadUrl(load_data, replace)))
                     .unwrap();
             }
         }
     }
 
+    pub fn eval_js_url(global_scope: &GlobalScope, load_data: &mut LoadData) {
+        // Turn javascript: URL into JS code to eval, according to the steps in
+        // https://html.spec.whatwg.org/multipage/#javascript-protocol
+
+        // This slice of the URL’s serialization is equivalent to (5.) to (7.):
+        // Start with the scheme data of the parsed URL;
+        // append question mark and query component, if any;
+        // append number sign and fragment component if any.
+        let encoded = &load_data.url.clone()[Position::BeforePath..];
+
+        // Percent-decode (8.) and UTF-8 decode (9.)
+        let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();
+
+        // Script source is ready to be evaluated (11.)
+        let _ac = JSAutoCompartment::new(global_scope.get_cx(), global_scope.reflector().get_jsobject().get());
+        rooted!(in(global_scope.get_cx()) let mut jsval = UndefinedValue());
+        global_scope.evaluate_js_on_global_with_result(&script_source, jsval.handle_mut());
+
+        load_data.js_eval_result = if jsval.get().is_string() {
+            unsafe {
+                let strval = DOMString::from_jsval(global_scope.get_cx(),
+                                                   jsval.handle(),
+                                                   StringificationBehavior::Empty);
+                match strval {
+                    Ok(ConversionResult::Success(s)) => {
+                        Some(JsEvalResult::Ok(String::from(s).as_bytes().to_vec()))
+                    },
+                    _ => None,
+                }
+            }
+        } else {
+            Some(JsEvalResult::NoContent)
+        };
+
+        load_data.url = ServoUrl::parse("about:blank").unwrap();
+    }
+
     fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData, size_type: WindowSizeType) {
         let document = match { self.documents.borrow().find_document(pipeline_id) } {
             Some(document) => document,
             None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
         };
 
         let window = document.window();
         window.set_window_size(new_size);
@@ -2372,36 +2398,32 @@ impl ScriptThread {
         // 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) {
         let id = incomplete.pipeline_id.clone();
-        let mut req_init = RequestInit {
+        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),
             referrer_url: load_data.referrer_url,
             referrer_policy: load_data.referrer_policy,
             headers: load_data.headers,
             body: load_data.data,
             redirect_mode: RedirectMode::Manual,
             origin: incomplete.origin.immutable().clone(),
             .. RequestInit::default()
         };
 
-        if req_init.url.scheme() == "javascript" {
-            req_init.url = ServoUrl::parse("about:blank").unwrap();
-        }
-
         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();
         self.incomplete_loads.borrow_mut().push(incomplete);
     }
 
     fn handle_fetch_metadata(&self, id: PipelineId, fetch_metadata: Result<FetchMetadata, NetworkError>) {
@@ -2431,28 +2453,40 @@ impl ScriptThread {
         if let Some(idx) = idx {
             let (_, mut ctxt) = self.incomplete_parser_contexts.borrow_mut().remove(idx);
             ctxt.process_response_eof(eof);
         }
     }
 
     /// Synchronously fetch `about:blank`. Stores the `InProgressLoad`
     /// argument until a notification is received that the fetch is complete.
-    fn start_page_load_about_blank(&self, incomplete: InProgressLoad) {
+    fn start_page_load_about_blank(&self, incomplete: InProgressLoad, js_eval_result: Option<JsEvalResult>) {
         let id = incomplete.pipeline_id;
 
         self.incomplete_loads.borrow_mut().push(incomplete);
 
         let url = ServoUrl::parse("about:blank").unwrap();
         let mut context = ParserContext::new(id, url.clone());
 
         let mut meta = Metadata::default(url);
         meta.set_content_type(Some(&mime!(Text / Html)));
+
+        // If this page load is the result of a javascript scheme url, map
+        // the evaluation result into a response.
+        let chunk = match js_eval_result {
+            Some(JsEvalResult::Ok(content)) => content,
+            Some(JsEvalResult::NoContent) => {
+                meta.status = Some((204, b"No Content".to_vec()));
+                vec![]
+            },
+            None => vec![]
+        };
+
         context.process_response(Ok(FetchMetadata::Unfiltered(meta)));
-        context.process_response_chunk(vec![]);
+        context.process_response_chunk(chunk);
         context.process_response_eof(Ok(()));
     }
 
     fn handle_css_error_reporting(&self, pipeline_id: PipelineId, filename: String,
                                   line: u32, column: u32, msg: String) {
         let sender = match self.devtools_chan {
             Some(ref sender) => sender,
             None => return,
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -141,35 +141,48 @@ pub struct LoadData {
             serialize_with = "::hyper_serde::serialize")]
     pub method: Method,
     /// The headers.
     #[serde(deserialize_with = "::hyper_serde::deserialize",
             serialize_with = "::hyper_serde::serialize")]
     pub headers: Headers,
     /// The data.
     pub data: Option<Vec<u8>>,
+    /// The result of evaluating a javascript scheme url.
+    pub js_eval_result: Option<JsEvalResult>,
     /// The referrer policy.
     pub referrer_policy: Option<ReferrerPolicy>,
     /// The referrer URL.
     pub referrer_url: Option<ServoUrl>,
 }
 
+/// The result of evaluating a javascript scheme url.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub enum JsEvalResult {
+    /// The js evaluation had a non-string result, 204 status code.
+    /// https://html.spec.whatwg.org/multipage/#navigate 12.11
+    NoContent,
+    /// The js evaluation had a string result.
+    Ok(Vec<u8>)
+}
+
 impl LoadData {
     /// Create a new `LoadData` object.
     pub fn new(url: ServoUrl,
                creator_pipeline_id: Option<PipelineId>,
                referrer_policy: Option<ReferrerPolicy>,
                referrer_url: Option<ServoUrl>)
                -> LoadData {
         LoadData {
             url: url,
             creator_pipeline_id: creator_pipeline_id,
             method: Method::Get,
             headers: Headers::new(),
             data: None,
+            js_eval_result: None,
             referrer_policy: referrer_policy,
             referrer_url: referrer_url,
         }
     }
 }
 
 /// The initial data required to create a new layout attached to an existing script thread.
 #[derive(Deserialize, Serialize)]
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -94,16 +94,18 @@ pub enum ScriptMsg {
     /// <head> tag finished parsing
     HeadParsed,
     /// All pending loads are complete, and the `load` event for this pipeline
     /// has been dispatched.
     LoadComplete,
     /// A new load has been requested, with an option to replace the current entry once loaded
     /// instead of adding a new entry.
     LoadUrl(LoadData, bool),
+    /// Abort loading after sending a LoadUrl message.
+    AbortLoadUrl,
     /// Post a message to the currently active window of a given browsing context.
     PostMessage(BrowsingContextId, Option<ImmutableOrigin>, Vec<u8>),
     /// Dispatch a mozbrowser event to the parent of a mozbrowser iframe.
     MozBrowserEvent(PipelineId, MozBrowserEvent),
     /// HTMLIFrameElement Forward or Back traversal.
     TraverseHistory(TraversalDirection),
     /// Gets the length of the joint session history from the constellation.
     JointSessionHistoryLength(IpcSender<u32>),
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -294,17 +294,20 @@ impl<T: SelectorMapEntry> SelectorMap<T>
                 // html document (in which case we match against lower_name) or
                 // not (in which case we match against name).
                 //
                 // In the case of a non-html-element-in-html-document with a
                 // lowercase localname and a non-lowercase selector, the
                 // rulehash lookup may produce superfluous selectors, but the
                 // subsequent selector matching work will filter them out.
                 if name != lower_name {
-                    find_push(&mut self.local_name_hash, lower_name.clone(), entry.clone());
+                    self.local_name_hash
+                        .entry(lower_name.clone())
+                        .or_insert_with(SmallVec::new)
+                        .push(entry.clone());
                 }
                 self.local_name_hash
                     .entry(name.clone())
                     .or_insert_with(SmallVec::new)
             }
             Bucket::Universal => {
                 &mut self.other
             }
@@ -480,25 +483,16 @@ fn find_bucket<'a>(mut iter: SelectorIte
         if iter.next_sequence() != Some(Combinator::PseudoElement) {
             break;
         }
     }
 
     return current_bucket
 }
 
-#[inline]
-fn find_push<Str: Eq + Hash, V, VL>(map: &mut PrecomputedHashMap<Str, VL>,
-                                    key: Str,
-                                    value: V)
-    where VL: VecLike<V> + Default
-{
-    map.entry(key).or_insert_with(VL::default).push(value)
-}
-
 /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode.
 #[derive(Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V: 'static>(PrecomputedHashMap<K, V>);
 
 // FIXME(Manishearth) the 'static bound can be removed when
 // our HashMap fork (hashglobe) is able to use NonZero,
 // or when stdlib gets fallible collections