Implement Document.readyState. Prevent iframes from notifying the compositor after the initial parse. Fixes #1720. Fixes #3738.

This commit is contained in:
Josh Matthews 2014-10-21 14:44:30 -04:00
parent 470d27a668
commit 539c21f380
10 changed files with 113 additions and 41 deletions

View file

@ -5,8 +5,10 @@
use dom::attr::AttrHelpers;
use dom::bindings::cell::{DOMRefCell, Ref};
use dom::bindings::codegen::Bindings::DocumentBinding;
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentReadyStateValues;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
@ -34,7 +36,7 @@ use dom::domimplementation::DOMImplementation;
use dom::element::{Element, AttributeHandlers, get_attribute_parts};
use dom::element::{HTMLHtmlElementTypeId, HTMLHeadElementTypeId, HTMLTitleElementTypeId};
use dom::element::{HTMLBodyElementTypeId, HTMLFrameSetElementTypeId};
use dom::event::Event;
use dom::event::{Event, DoesNotBubble, NotCancelable};
use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers};
use dom::htmlanchorelement::HTMLAnchorElement;
use dom::htmlcollection::{HTMLCollection, CollectionFilter};
@ -93,6 +95,7 @@ pub struct Document {
scripts: MutNullableJS<HTMLCollection>,
anchors: MutNullableJS<HTMLCollection>,
applets: MutNullableJS<HTMLCollection>,
ready_state: Cell<DocumentReadyState>,
}
impl DocumentDerived for EventTarget {
@ -171,6 +174,7 @@ pub trait DocumentHelpers<'a> {
fn register_named_element(self, element: JSRef<Element>, id: Atom);
fn load_anchor_href(self, href: DOMString);
fn find_fragment_node(self, fragid: DOMString) -> Option<Temporary<Element>>;
fn set_ready_state(self, state: DocumentReadyState);
}
impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
@ -288,15 +292,39 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
.map(|node| Temporary::from_rooted(ElementCast::from_ref(node)))
})
}
// https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness
fn set_ready_state(self, state: DocumentReadyState) {
self.ready_state.set(state);
let window = self.window.root();
let event = Event::new(&global::Window(*window), "readystatechange".to_string(),
DoesNotBubble, NotCancelable).root();
let target: JSRef<EventTarget> = EventTargetCast::from_ref(self);
let _ = target.DispatchEvent(*event);
}
}
#[deriving(PartialEq)]
pub enum DocumentSource {
FromParser,
NotFromParser,
}
impl Document {
fn new_inherited(window: JSRef<Window>,
url: Option<Url>,
is_html_document: IsHTMLDocument,
content_type: Option<DOMString>) -> Document {
url: Option<Url>,
is_html_document: IsHTMLDocument,
content_type: Option<DOMString>,
source: DocumentSource) -> Document {
let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());
let ready_state = if source == FromParser {
DocumentReadyStateValues::Loading
} else {
DocumentReadyStateValues::Complete
};
Document {
node: Node::new_without_doc(DocumentNodeTypeId),
window: JS::from_rooted(window),
@ -325,16 +353,22 @@ impl Document {
scripts: Default::default(),
anchors: Default::default(),
applets: Default::default(),
ready_state: Cell::new(ready_state),
}
}
// http://dom.spec.whatwg.org/#dom-document
pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<Document>> {
Ok(Document::new(global.as_window(), None, NonHTMLDocument, None))
Ok(Document::new(global.as_window(), None, NonHTMLDocument, None, NotFromParser))
}
pub fn new(window: JSRef<Window>, url: Option<Url>, doctype: IsHTMLDocument, content_type: Option<DOMString>) -> Temporary<Document> {
let document = reflect_dom_object(box Document::new_inherited(window, url, doctype, content_type),
pub fn new(window: JSRef<Window>,
url: Option<Url>,
doctype: IsHTMLDocument,
content_type: Option<DOMString>,
source: DocumentSource) -> Temporary<Document> {
let document = reflect_dom_object(box Document::new_inherited(window, url, doctype,
content_type, source),
&global::Window(window),
DocumentBinding::Wrap).root();
@ -888,6 +922,12 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
root.query_selector_all(selectors)
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-document-readystate
fn ReadyState(self) -> DocumentReadyState {
self.ready_state.get()
}
event_handler!(click, GetOnclick, SetOnclick)
event_handler!(load, GetOnload, SetOnload)
event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange)
}

View file

@ -12,7 +12,7 @@ use dom::bindings::global::Window;
use dom::bindings::js::{JS, JSRef, Root, Temporary, OptionalRootable};
use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
use dom::bindings::utils::{QName, Name, InvalidXMLName, xml_name_type};
use dom::document::{Document, HTMLDocument, NonHTMLDocument};
use dom::document::{Document, HTMLDocument, NonHTMLDocument, NotFromParser};
use dom::documenttype::DocumentType;
use dom::htmlbodyelement::HTMLBodyElement;
use dom::htmlheadelement::HTMLHeadElement;
@ -74,7 +74,7 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> {
let win = doc.window().root();
// Step 1.
let doc = Document::new(*win, None, NonHTMLDocument, None).root();
let doc = Document::new(*win, None, NonHTMLDocument, None, NotFromParser).root();
// Step 2-3.
let maybe_elem = if qname.is_empty() {
None
@ -119,7 +119,7 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> {
let win = document.window().root();
// Step 1-2.
let doc = Document::new(*win, None, HTMLDocument, None).root();
let doc = Document::new(*win, None, HTMLDocument, None, NotFromParser).root();
let doc_node: JSRef<Node> = NodeCast::from_ref(*doc);
{

View file

@ -10,7 +10,7 @@ use dom::bindings::global::GlobalRef;
use dom::bindings::global;
use dom::bindings::js::{JS, JSRef, Temporary};
use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
use dom::document::{Document, HTMLDocument, NonHTMLDocument};
use dom::document::{Document, HTMLDocument, NonHTMLDocument, NotFromParser};
use dom::window::Window;
use servo_util::str::DOMString;
@ -44,12 +44,15 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
ty: DOMParserBinding::SupportedType)
-> Fallible<Temporary<Document>> {
let window = self.window.root();
//FIXME: these should probably be FromParser when we actually parse the string (#3756).
match ty {
Text_html => {
Ok(Document::new(*window, None, HTMLDocument, Some("text/html".to_string())))
Ok(Document::new(*window, None, HTMLDocument, Some("text/html".to_string()),
NotFromParser))
}
Text_xml => {
Ok(Document::new(*window, None, NonHTMLDocument, Some("text/xml".to_string())))
Ok(Document::new(*window, None, NonHTMLDocument, Some("text/xml".to_string()),
NotFromParser))
}
_ => {
Err(FailureUnknown)

View file

@ -4,6 +4,7 @@
use dom::attr::Attr;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyStateValues};
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding;
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
@ -15,7 +16,7 @@ use dom::element::{HTMLIFrameElementTypeId, Element};
use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node, document_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::window::Window;
use page::IterablePage;
@ -119,8 +120,13 @@ impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> {
subpage_id: subpage_id,
}));
let ConstellationChan(ref chan) = page.constellation_chan;
chan.send(LoadIframeUrlMsg(url, page.id, subpage_id, sandboxed));
let doc = document_from_node(self).root();
if doc.ReadyState() != DocumentReadyStateValues::Complete {
// https://github.com/servo/servo/issues/3738
// We can't handle dynamic frame tree changes in the compositor right now.
let ConstellationChan(ref chan) = page.constellation_chan;
chan.send(LoadIframeUrlMsg(url, page.id, subpage_id, sandboxed));
}
}
}

View file

@ -30,7 +30,7 @@ use dom::bindings::utils;
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::characterdata::CharacterData;
use dom::comment::Comment;
use dom::document::{Document, DocumentHelpers, HTMLDocument, NonHTMLDocument};
use dom::document::{Document, DocumentHelpers, HTMLDocument, NonHTMLDocument, NotFromParser};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::element::{AttributeHandlers, Element, ElementTypeId};
@ -1511,7 +1511,7 @@ impl Node {
};
let window = document.window().root();
let document = Document::new(*window, Some(document.url().clone()),
is_html_doc, None);
is_html_doc, None, NotFromParser);
NodeCast::from_temporary(document)
},
ElementNodeTypeId(..) => {

View file

@ -17,7 +17,6 @@ interface Document : Node {
readonly attribute DOMString compatMode;
readonly attribute DOMString characterSet;
readonly attribute DOMString contentType;
readonly attribute Location location;
readonly attribute DocumentType? doctype;
readonly attribute Element? documentElement;
@ -52,17 +51,23 @@ interface Document : Node {
[NewObject]
TreeWalker createTreeWalker(Node root, optional unsigned long whatToShow = 0xFFFFFFFF, optional NodeFilter? filter = null);
};
Document implements ParentNode;
enum DocumentReadyState { "loading", "interactive", "complete" };
/* http://www.whatwg.org/specs/web-apps/current-work/#the-document-object */
partial interface Document {
// resource metadata management
readonly attribute DocumentReadyState readyState;
readonly attribute DOMString lastModified;
readonly attribute Location location;
// DOM tree accessors
[SetterThrows]
attribute DOMString title;
[SetterThrows]
attribute HTMLElement? body;
readonly attribute HTMLHeadElement? head;
NodeList getElementsByName(DOMString elementName);
readonly attribute HTMLCollection images;
readonly attribute HTMLCollection embeds;
readonly attribute HTMLCollection plugins;
@ -71,7 +76,9 @@ partial interface Document {
readonly attribute HTMLCollection scripts;
readonly attribute HTMLCollection anchors;
readonly attribute HTMLCollection applets;
};
NodeList getElementsByName(DOMString elementName);
Document implements ParentNode;
// special event handler IDL attributes that only apply to Document objects
/*[LenientThis]*/ attribute EventHandler onreadystatechange;
};
Document implements GlobalEventHandlers;

View file

@ -6,9 +6,10 @@
//! and layout tasks.
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyStateValues};
use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast, ElementCast};
use dom::bindings::conversions;
use dom::bindings::conversions::{FromJSValConvertible, Empty};
@ -17,7 +18,7 @@ use dom::bindings::js::{JS, JSRef, RootCollection, Temporary, OptionalRootable};
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::Reflectable;
use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap};
use dom::document::{Document, HTMLDocument, DocumentHelpers};
use dom::document::{Document, HTMLDocument, DocumentHelpers, FromParser};
use dom::element::{Element, HTMLButtonElementTypeId, HTMLInputElementTypeId};
use dom::element::{HTMLSelectElementTypeId, HTMLTextAreaElementTypeId, HTMLOptionElementTypeId};
use dom::event::{Event, Bubbles, DoesNotBubble, Cancelable, NotCancelable};
@ -762,7 +763,7 @@ impl ScriptTask {
url.clone()
};
let document = Document::new(*window, Some(doc_url), HTMLDocument,
None).root();
None, FromParser).root();
window.init_browser_context(*document);
@ -793,6 +794,8 @@ impl ScriptTask {
});
}
document.set_ready_state(DocumentReadyStateValues::Interactive);
// Send style sheets over to layout.
//
// FIXME: These should be streamed to layout as they're parsed. We don't need to stop here
@ -849,11 +852,20 @@ impl ScriptTask {
}
});
// https://html.spec.whatwg.org/multipage/#the-end step 4
let event = Event::new(&global::Window(*window), "DOMContentLoaded".to_string(),
DoesNotBubble, NotCancelable).root();
let doctarget: JSRef<EventTarget> = EventTargetCast::from_ref(*document);
let _ = doctarget.DispatchEvent(*event);
// We have no concept of a document loader right now, so just dispatch the
// "load" event as soon as we've finished executing all scripts parsed during
// the initial load.
// https://html.spec.whatwg.org/multipage/#the-end step 7
document.set_ready_state(DocumentReadyStateValues::Complete);
let event = Event::new(&global::Window(*window), "load".to_string(), DoesNotBubble, NotCancelable).root();
let doctarget: JSRef<EventTarget> = EventTargetCast::from_ref(*document);
let wintarget: JSRef<EventTarget> = EventTargetCast::from_ref(*window);
let _ = wintarget.dispatch_event_with_target(Some(doctarget), *event);

View file

@ -0,0 +1,15 @@
<html>
<head>
<script src="harness.js"></script>
</head>
<!-- gNumChanges should be 2 once synchronous script execution is supported -->
<body onload="is(document.readyState, 'complete'); is(gNumChanges, 1); finish()">
<script>
gNumChanges = 0;
document.addEventListener('readystatechange', function() {
gNumChanges++;
}, true);
is(document.readyState, "interactive");
</script>
</body>
</html>

View file

@ -1,3 +1,4 @@
[document-readyState.html]
type: testharness
expected: TIMEOUT
[readystatechange event is fired each time document.readyState changes]
expected: FAIL

View file

@ -9,9 +9,6 @@
[Document interface: attribute cookie]
expected: FAIL
[Document interface: attribute readyState]
expected: FAIL
[Document interface: attribute dir]
expected: FAIL
@ -72,9 +69,6 @@
[Document interface: attribute commands]
expected: FAIL
[Document interface: attribute onreadystatechange]
expected: FAIL
[Document interface: attribute fgColor]
expected: FAIL
@ -1110,9 +1104,6 @@
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "cookie" with the proper type (35)]
expected: FAIL
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "readyState" with the proper type (37)]
expected: FAIL
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "dir" with the proper type (40)]
expected: FAIL
@ -1206,9 +1197,6 @@
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "commands" with the proper type (68)]
expected: FAIL
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "onreadystatechange" with the proper type (69)]
expected: FAIL
[Document interface: document.implementation.createDocument(null, "", null) must inherit property "fgColor" with the proper type (70)]
expected: FAIL