servo: Merge #8658 - Implement origin concept and browsing contextless documents (from jdm:origin2); r=Ms2ger+jdm
authorJosh Matthews <josh@joshmatthews.net>
Wed, 13 Apr 2016 15:40:08 +0500
changeset 338501 7752900f9ea1560d1ec6d0df2dac9e43b969def9
parent 338500 b5efeb1185d807fb1e345b61b24e6bde19b0e2c1
child 338502 d04915d321c180ab2543bb2ea984a90afc8420dc
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)
reviewersMs2ger
servo: Merge #8658 - Implement origin concept and browsing contextless documents (from jdm:origin2); r=Ms2ger+jdm These pave the way for implementing other parts of specifications more thoroughly. Source-Repo: https://github.com/servo/servo Source-Revision: 9b57d8d686d361c0dfba1056523cbea12abd148b
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/document.rs
servo/components/script/lib.rs
servo/components/script/origin.rs
servo/components/servo/Cargo.lock
servo/tests/unit/script/Cargo.toml
servo/tests/unit/script/lib.rs
servo/tests/unit/script/origin.rs
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -84,16 +84,17 @@ use std::sync::atomic::{AtomicBool, Atom
 use std::sync::mpsc::{Receiver, Sender};
 use string_cache::{Atom, Namespace, QualName};
 use style::attr::{AttrIdentifier, AttrValue};
 use style::element_state::*;
 use style::properties::PropertyDeclarationBlock;
 use style::restyle_hints::ElementSnapshot;
 use style::selector_impl::PseudoElement;
 use style::values::specified::Length;
+use url::Origin as UrlOrigin;
 use url::Url;
 use util::str::{DOMString, LengthOrPercentageOrAuto};
 use uuid::Uuid;
 use webrender_traits::WebGLError;
 
 /// A trait to allow tracing (only) DOM objects.
 pub trait JSTraceable {
     /// Trace `self`.
@@ -271,17 +272,17 @@ impl<A: JSTraceable, B: JSTraceable, C: 
     fn trace(&self, trc: *mut JSTracer) {
         let (ref a, ref b, ref c) = *self;
         a.trace(trc);
         b.trace(trc);
         c.trace(trc);
     }
 }
 
-no_jsmanaged_fields!(bool, f32, f64, String, Url, AtomicBool, AtomicUsize, Uuid);
+no_jsmanaged_fields!(bool, f32, f64, String, Url, AtomicBool, AtomicUsize, UrlOrigin, Uuid);
 no_jsmanaged_fields!(usize, u8, u16, u32, u64);
 no_jsmanaged_fields!(isize, i8, i16, i32, i64);
 no_jsmanaged_fields!(Sender<T>);
 no_jsmanaged_fields!(Receiver<T>);
 no_jsmanaged_fields!(Rect<T>);
 no_jsmanaged_fields!(Size2D<T>);
 no_jsmanaged_fields!(Arc<T>);
 no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheChan, ImageCacheThread);
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -89,16 +89,17 @@ use layout_interface::{LayoutChan, Msg, 
 use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
 use msg::constellation_msg::{ConstellationChan, Key, KeyModifiers, KeyState};
 use msg::constellation_msg::{PipelineId, SubpageId};
 use net_traits::ControlMsg::{GetCookiesForUrl, SetCookiesForUrl};
 use net_traits::CookieSource::NonHTTP;
 use net_traits::response::HttpsState;
 use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
 use num::ToPrimitive;
+use origin::Origin;
 use script_runtime::ScriptChan;
 use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable};
 use script_traits::UntrustedNodeAddress;
 use script_traits::{AnimationState, MouseButton, MouseEventType, MozBrowserEvent};
 use script_traits::{ScriptMsg as ConstellationMsg, ScriptToCompositorMsg};
 use script_traits::{TouchpadPressurePhase, TouchEventType, TouchId};
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
@@ -218,16 +219,18 @@ pub struct Document {
     dom_complete: Cell<u64>,
     load_event_start: Cell<u64>,
     load_event_end: Cell<u64>,
     /// Vector to store CSS errors
     css_errors_store: DOMRefCell<Vec<CSSError>>,
     /// https://html.spec.whatwg.org/multipage/#concept-document-https-state
     https_state: Cell<HttpsState>,
     touchpad_pressure_phase: Cell<TouchpadPressurePhase>,
+    /// The document's origin.
+    origin: Origin,
 }
 
 #[derive(JSTraceable, HeapSizeOf)]
 struct ImagesFilter;
 impl CollectionFilter for ImagesFilter {
     fn filter(&self, elem: &Element, _root: &Node) -> bool {
         elem.is::<HTMLImageElement>()
     }
@@ -1539,24 +1542,16 @@ impl Document {
         let event = event.upcast::<Event>();
         event.set_trusted(true);
         let target = node.upcast();
         event.fire(target);
     }
 
     /// https://html.spec.whatwg.org/multipage/#cookie-averse-document-object
     fn is_cookie_averse(&self) -> bool {
-        /// https://url.spec.whatwg.org/#network-scheme
-        fn url_has_network_scheme(url: &Url) -> bool {
-            match &*url.scheme {
-                "ftp" | "http" | "https" => true,
-                _ => false,
-            }
-        }
-
         self.browsing_context.is_none() || !url_has_network_scheme(&self.url)
     }
 
     pub fn nodes_from_point(&self, page_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
         assert!(self.GetDocumentElement().is_some());
 
         self.window.layout().nodes_from_point(*page_point)
     }
@@ -1585,16 +1580,24 @@ impl LayoutDocumentHelpers for LayoutJS<
     #[allow(unrooted_must_root)]
     unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementSnapshot)> {
         let mut elements = (*self.unsafe_get()).modified_elements.borrow_mut_for_layout();
         let result = elements.drain().map(|(k, v)| (k.to_layout(), v)).collect();
         result
     }
 }
 
+/// https://url.spec.whatwg.org/#network-scheme
+fn url_has_network_scheme(url: &Url) -> bool {
+    match &*url.scheme {
+        "ftp" | "http" | "https" => true,
+        _ => false,
+    }
+}
+
 impl Document {
     pub fn new_inherited(window: &Window,
                          browsing_context: Option<&BrowsingContext>,
                          url: Option<Url>,
                          is_html_document: IsHTMLDocument,
                          content_type: Option<DOMString>,
                          last_modified: Option<String>,
                          source: DocumentSource,
@@ -1603,16 +1606,25 @@ impl Document {
         let url = url.unwrap_or_else(|| url!("about:blank"));
 
         let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
             (DocumentReadyState::Loading, false)
         } else {
             (DocumentReadyState::Complete, true)
         };
 
+        // Incomplete implementation of Document origin specification at
+        // https://html.spec.whatwg.org/multipage/#origin:document
+        let origin = if url_has_network_scheme(&url) {
+            Origin::new(&url)
+        } else {
+            // Default to DOM standard behaviour
+            Origin::opaque_identifier()
+        };
+
         Document {
             node: Node::new_document_node(),
             window: JS::from_ref(window),
             browsing_context: browsing_context.map(JS::from_ref),
             implementation: Default::default(),
             location: Default::default(),
             content_type: match content_type {
                 Some(string) => string,
@@ -1668,16 +1680,17 @@ impl Document {
             dom_content_loaded_event_start: Cell::new(Default::default()),
             dom_content_loaded_event_end: Cell::new(Default::default()),
             dom_complete: Cell::new(Default::default()),
             load_event_start: Cell::new(Default::default()),
             load_event_end: Cell::new(Default::default()),
             css_errors_store: DOMRefCell::new(vec![]),
             https_state: Cell::new(HttpsState::None),
             touchpad_pressure_phase: Cell::new(TouchpadPressurePhase::BeforeClick),
+            origin: origin,
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-document
     pub fn Constructor(global: GlobalRef) -> Fallible<Root<Document>> {
         let win = global.as_window();
         let doc = win.Document();
         let doc = doc.r();
@@ -1863,19 +1876,28 @@ impl DocumentMethods for Document {
                 }
             }
             None => false,
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#relaxing-the-same-origin-restriction
     fn Domain(&self) -> DOMString {
-        // TODO: This should use the effective script origin when it exists
-        let origin = self.window.get_url();
-        DOMString::from(origin.serialize_host().unwrap_or_else(|| "".to_owned()))
+        // Step 1.
+        if self.browsing_context().is_none() {
+            return DOMString::new();
+        }
+
+        if let Some(host) = self.origin.host() {
+            // Step 4.
+            DOMString::from(host.serialize())
+        } else {
+            // Step 3.
+            DOMString::new()
+        }
     }
 
     // https://dom.spec.whatwg.org/#dom-document-documenturi
     fn DocumentURI(&self) -> DOMString {
         self.URL()
     }
 
     // https://dom.spec.whatwg.org/#dom-document-compatmode
@@ -2492,36 +2514,38 @@ impl DocumentMethods for Document {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-document-cookie
     fn GetCookie(&self) -> Fallible<DOMString> {
         if self.is_cookie_averse() {
             return Ok(DOMString::new());
         }
 
-        let url = self.url();
-        if !is_scheme_host_port_tuple(&url) {
+        if !self.origin.is_scheme_host_port_tuple() {
             return Err(Error::Security);
         }
+
+        let url = self.url();
         let (tx, rx) = ipc::channel().unwrap();
         let _ = self.window.resource_thread().send(GetCookiesForUrl((*url).clone(), tx, NonHTTP));
         let cookies = rx.recv().unwrap();
         Ok(cookies.map_or(DOMString::new(), DOMString::from))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-document-cookie
     fn SetCookie(&self, cookie: DOMString) -> ErrorResult {
         if self.is_cookie_averse() {
             return Ok(());
         }
 
-        let url = self.url();
-        if !is_scheme_host_port_tuple(url) {
+        if !self.origin.is_scheme_host_port_tuple() {
             return Err(Error::Security);
         }
+
+        let url = self.url();
         let _ = self.window
                     .resource_thread()
                     .send(SetCookiesForUrl((*url).clone(), String::from(cookie), NonHTTP));
         Ok(())
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-document-bgcolor
     fn BgColor(&self) -> DOMString {
@@ -2715,20 +2739,16 @@ impl DocumentMethods for Document {
             }
         }
 
         // Step 5
         elements
     }
 }
 
-fn is_scheme_host_port_tuple(url: &Url) -> bool {
-    url.host().is_some() && url.port_or_default().is_some()
-}
-
 fn update_with_current_time_ms(marker: &Cell<u64>) {
     if marker.get() == Default::default() {
         let time = time::get_time();
         let current_time_ms = time.sec * 1000 + time.nsec as i64 / 1000000;
         marker.set(current_time_ms as u64);
     }
 }
 
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -85,16 +85,17 @@ pub mod clipboard_provider;
 pub mod cors;
 mod devtools;
 pub mod document_loader;
 #[macro_use]
 pub mod dom;
 pub mod layout_interface;
 mod mem;
 mod network_listener;
+pub mod origin;
 pub mod page;
 pub mod parse;
 pub mod reporter;
 pub mod script_runtime;
 #[allow(unsafe_code)]
 pub mod script_thread;
 mod task_source;
 pub mod textinput;
new file mode 100644
--- /dev/null
+++ b/servo/components/script/origin.rs
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::cell::RefCell;
+use std::rc::Rc;
+use url::{OpaqueOrigin, Origin as UrlOrigin};
+use url::{Url, Host};
+
+/// A representation of an [origin](https://html.spec.whatwg.org/multipage/#origin-2).
+#[derive(HeapSizeOf)]
+pub struct Origin {
+    #[ignore_heap_size_of = "Rc<T> has unclear ownership semantics"]
+    inner: Rc<RefCell<UrlOrigin>>,
+}
+
+// We can't use RefCell inside JSTraceable, but Origin doesn't contain JS values and
+// DOMRefCell makes it much harder to write unit tests (due to setting up required TLS).
+no_jsmanaged_fields!(Origin);
+
+impl Origin {
+    /// Create a new origin comprising a unique, opaque identifier.
+    pub fn opaque_identifier() -> Origin {
+        let opaque = UrlOrigin::UID(OpaqueOrigin::new());
+        Origin {
+            inner: Rc::new(RefCell::new(opaque)),
+        }
+    }
+
+    /// Create a new origin for the given URL.
+    pub fn new(url: &Url) -> Origin {
+        Origin {
+            inner: Rc::new(RefCell::new(url.origin())),
+        }
+    }
+
+    pub fn set(&self, origin: UrlOrigin) {
+        *self.inner.borrow_mut() = origin;
+    }
+
+    /// Does this origin represent a host/scheme/port tuple?
+    pub fn is_scheme_host_port_tuple(&self) -> bool {
+        match *self.inner.borrow() {
+            UrlOrigin::Tuple(..) => true,
+            UrlOrigin::UID(..) => false,
+        }
+    }
+
+    /// Return the host associated with this origin.
+    pub fn host(&self) -> Option<Host> {
+        match *self.inner.borrow() {
+            UrlOrigin::Tuple(_, ref host, _) => Some(host.clone()),
+            UrlOrigin::UID(..) => None,
+        }
+    }
+
+    /// https://html.spec.whatwg.org/multipage/#same-origin
+    pub fn same_origin(&self, other: &Origin) -> bool {
+        *self.inner.borrow() == *other.inner.borrow()
+    }
+
+    pub fn copy(&self) -> Origin {
+        Origin {
+            inner: Rc::new(RefCell::new(self.inner.borrow().clone())),
+        }
+    }
+
+    pub fn alias(&self) -> Origin {
+        Origin {
+            inner: self.inner.clone(),
+        }
+    }
+}
--- a/servo/components/servo/Cargo.lock
+++ b/servo/components/servo/Cargo.lock
@@ -1747,17 +1747,19 @@ dependencies = [
  "xml5ever 0.1.1 (git+https://github.com/Ygg01/xml5ever)",
 ]
 
 [[package]]
 name = "script_tests"
 version = "0.0.1"
 dependencies = [
  "msg 0.0.1",
+ "plugins 0.0.1",
  "script 0.0.1",
+ "url 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "util 0.0.1",
 ]
 
 [[package]]
 name = "script_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/tests/unit/script/Cargo.toml
+++ b/servo/tests/unit/script/Cargo.toml
@@ -6,13 +6,19 @@ authors = ["The Servo Project Developers
 [lib]
 name = "script_tests"
 path = "lib.rs"
 doctest = false
 
 [dependencies.msg]
 path = "../../../components/msg"
 
+[dependencies.plugins]
+path = "../../../components/plugins"
+
 [dependencies.script]
 path = "../../../components/script"
 
 [dependencies.util]
 path = "../../../components/util"
+
+[dependencies]
+url = {version = "0.5.8", features = ["heap_size"]}
--- a/servo/tests/unit/script/lib.rs
+++ b/servo/tests/unit/script/lib.rs
@@ -1,15 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#![feature(plugin)]
+#![plugin(plugins)]
+
 extern crate msg;
 extern crate script;
+extern crate url;
 extern crate util;
 
+#[cfg(test)] mod origin;
 #[cfg(all(test, target_pointer_width = "64"))] mod size_of;
 #[cfg(test)] mod textinput;
 #[cfg(test)] mod dom {
     mod bindings;
     mod blob;
     mod xmlhttprequest;
 }
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/script/origin.rs
@@ -0,0 +1,105 @@
+/* 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 script::origin::Origin;
+
+#[test]
+fn same_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.com/b.html"));
+    assert!(a.same_origin(&b));
+    assert_eq!(a.is_scheme_host_port_tuple(), true);
+}
+
+#[test]
+fn identical_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    assert!(a.same_origin(&a));
+}
+
+#[test]
+fn cross_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.org/b.html"));
+    assert!(!a.same_origin(&b));
+}
+
+#[test]
+fn alias_same_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.com/b.html"));
+    let c = b.alias();
+    assert!(a.same_origin(&c));
+    assert!(b.same_origin(&b));
+    assert!(c.same_origin(&b));
+    assert_eq!(c.is_scheme_host_port_tuple(), true);
+}
+
+#[test]
+fn alias_cross_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.org/b.html"));
+    let c = b.alias();
+    assert!(!a.same_origin(&c));
+    assert!(b.same_origin(&c));
+    assert!(c.same_origin(&c));
+}
+
+#[test]
+fn alias_update_same_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.org/b.html"));
+    let c = b.alias();
+    b.set(url!("http://example.com/c.html").origin());
+    assert!(a.same_origin(&c));
+    assert!(b.same_origin(&c));
+    assert!(c.same_origin(&c));
+}
+
+#[test]
+fn alias_update_cross_origin() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.com/b.html"));
+    let c = b.alias();
+    b.set(url!("http://example.org/c.html").origin());
+    assert!(!a.same_origin(&c));
+    assert!(b.same_origin(&c));
+    assert!(c.same_origin(&c));
+}
+
+#[test]
+fn alias_chain() {
+    let a = Origin::new(&url!("http://example.com/a.html"));
+    let b = Origin::new(&url!("http://example.com/b.html"));
+    let c = b.copy();
+    let d = c.alias();
+    let e = d.alias();
+    assert!(a.same_origin(&e));
+    assert!(b.same_origin(&e));
+    assert!(c.same_origin(&e));
+    assert!(d.same_origin(&e));
+    assert!(e.same_origin(&e));
+    c.set(url!("http://example.org/c.html").origin());
+    assert!(a.same_origin(&b));
+    assert!(!b.same_origin(&c));
+    assert!(c.same_origin(&d));
+    assert!(d.same_origin(&e));
+    assert!(!e.same_origin(&a));
+}
+
+#[test]
+fn opaque() {
+    let a = Origin::opaque_identifier();
+    let b = Origin::opaque_identifier();
+    assert!(!a.same_origin(&b));
+    assert_eq!(a.is_scheme_host_port_tuple(), false);
+}
+
+#[test]
+fn opaque_clone() {
+    let a = Origin::opaque_identifier();
+    let b = a.alias();
+    assert!(a.same_origin(&b));
+    assert_eq!(a.is_scheme_host_port_tuple(), false);
+}