1 use webcore::value::{Reference, Value};
2 use webcore::try_from::{TryInto, TryFrom};
3 use webcore::promise::{Promise, TypedPromise};
4 use webapi::error::TypeError;
5 use webapi::event_target::{IEventTarget, EventTarget};
6 use webapi::node::{INode, Node, CloneKind};
7 use webapi::element::Element;
8 use webapi::html_element::HtmlElement;
9 use webapi::document_fragment::DocumentFragment;
10 use webapi::text_node::TextNode;
11 use webapi::location::Location;
12 use webapi::parent_node::IParentNode;
13 use webapi::non_element_parent_node::INonElementParentNode;
14 use webapi::dom_exception::{InvalidCharacterError, NamespaceError, NotSupportedError};
15 
16 /// The `Document` interface represents any web page loaded in the browser and
17 /// serves as an entry point into the web page's content, which is the DOM tree.
18 ///
19 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document)
20 // https://dom.spec.whatwg.org/#document
21 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
22 #[reference(instance_of = "Document")]
23 #[reference(subclass_of(EventTarget, Node))]
24 pub struct Document( Reference );
25 
26 error_enum_boilerplate! {
27     CreateElementNsError,
28     InvalidCharacterError,
29     NamespaceError
30 }
31 
32 impl IEventTarget for Document {}
33 impl IParentNode for Document {}
34 impl INode for Document {}
35 
36 impl INonElementParentNode for Document {}
37 
38 /// A global instance of [Document](struct.Document.html).
39 ///
40 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document)
document() -> Document41 pub fn document() -> Document {
42     unsafe { js!( return document; ).into_reference_unchecked() }.unwrap()
43 }
44 
45 impl Document {
46     /// In an HTML document, the Document.createDocumentFragment() method creates a
47     /// new empty DocumentFragment.
48     ///
49     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment)
50     // https://dom.spec.whatwg.org/#ref-for-dom-document-createdocumentfragment
create_document_fragment( &self ) -> DocumentFragment51     pub fn create_document_fragment( &self ) -> DocumentFragment {
52         unsafe {
53             js!( return @{self}.createDocumentFragment(); ).into_reference_unchecked().unwrap()
54         }
55     }
56 
57     /// In an HTML document, the Document.createElement() method creates the HTML
58     /// element specified by `tag`, or an HTMLUnknownElement if `tag` isn't
59     /// recognized. In other documents, it creates an element with a null namespace URI.
60     ///
61     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)
62     // https://dom.spec.whatwg.org/#ref-for-dom-document-createelement
create_element( &self, tag: &str ) -> Result< Element, InvalidCharacterError >63     pub fn create_element( &self, tag: &str ) -> Result< Element, InvalidCharacterError > {
64         js_try!( return @{self}.createElement( @{tag} ); ).unwrap()
65     }
66 
67     /// Creates an element with the specified namespace URI and qualified name.
68     /// To create an element without specifying a namespace URI, use the `createElement` method.
69     ///
70     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS)
71     // https://dom.spec.whatwg.org/#ref-for-dom-document-createelementns
create_element_ns( &self, namespace_uri: &str, tag: &str ) -> Result< Element, CreateElementNsError >72     pub fn create_element_ns( &self, namespace_uri: &str, tag: &str ) -> Result< Element, CreateElementNsError > {
73         js_try!(
74             return @{self}.createElementNS( @{namespace_uri}, @{tag} );
75         ).unwrap()
76     }
77 
78     /// Creates a new text node.
79     ///
80     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
81     // https://dom.spec.whatwg.org/#ref-for-dom-document-createtextnode
create_text_node( &self, text: &str ) -> TextNode82     pub fn create_text_node( &self, text: &str ) -> TextNode {
83         unsafe {
84             js!( return @{self}.createTextNode( @{text} ); ).into_reference_unchecked().unwrap()
85         }
86     }
87 
88     /// Returns a [Location](struct.Location.html) object which contains
89     /// information about the URL of the document and provides methods
90     /// for changing that URL and loading another URL.
91     ///
92     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/location)
93     // https://html.spec.whatwg.org/#the-document-object:dom-document-location
location( &self ) -> Option< Location >94     pub fn location( &self ) -> Option< Location > {
95         unsafe {
96             js!(
97                 return @{self}.location;
98             ).into_reference_unchecked()
99         }
100     }
101 
102     /// Returns the `<body>` or `<frameset>` node of the current document, or null if no such element exists.
103     ///
104     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/body)
105     // https://html.spec.whatwg.org/#the-document-object:dom-document-body
body( &self ) -> Option< HtmlElement >106     pub fn body( &self ) -> Option< HtmlElement > {
107         unsafe {
108             js!(
109                 return @{self}.body;
110             ).into_reference_unchecked()
111         }
112     }
113 
114     /// Returns the `<head>` element of the current document. If there are more than one `<head>`
115     /// elements, the first one is returned.
116     ///
117     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/head)
118     // https://html.spec.whatwg.org/#the-document-object:dom-document-head
head( &self ) -> Option< HtmlElement >119     pub fn head( &self ) -> Option< HtmlElement > {
120         unsafe {
121             js!(
122                 return @{self}.head;
123             ).into_reference_unchecked()
124         }
125     }
126 
127     /// Gets the title of the document.
128     ///
129     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/title)
130     // https://html.spec.whatwg.org/#the-document-object:document.title
title( &self ) -> String131     pub fn title( &self ) -> String {
132         js!(
133             return @{self}.title;
134         ).try_into().unwrap()
135     }
136 
137     /// Sets the title of the document.
138     ///
139     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/title)
140     // https://html.spec.whatwg.org/#the-document-object:document.title
set_title( &self, title: &str )141     pub fn set_title( &self, title: &str ) {
142         js!( @(no_return) @{self}.title = @{title}; );
143     }
144 
145     /// Returns the Element that is the root element of the document (for example, the `<html>`
146     /// element for HTML documents).
147     ///
148     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/documentElement)
149     // https://dom.spec.whatwg.org/#ref-for-dom-document-documentelement
document_element( &self ) -> Option< Element >150     pub fn document_element( &self ) -> Option< Element > {
151         js!(
152             return @{self}.documentElement;
153         ).try_into().unwrap()
154     }
155 
156     /// Returns the Element that the pointer is locked to, if it is locked to any
157     ///
158     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/pointerLockElement)
159     // https://w3c.github.io/pointerlock/#dom-documentorshadowroot-pointerlockelement
pointer_lock_element( &self ) -> Option< Element >160     pub fn pointer_lock_element( &self ) -> Option< Element > {
161         let value = js!(
162             return @{self}.pointerLockElement;
163         );
164         match value {
165             Value::Null | Value::Undefined => None,
166             Value::Reference(reference) => Some(reference.try_into().unwrap()),
167             _ => unreachable!()
168         }
169     }
170 
171     /// Exit the pointer lock on the current element
172     ///
173     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitPointerLock)
174     // https://w3c.github.io/pointerlock/#dom-document-exitpointerlock
exit_pointer_lock( &self )175     pub fn exit_pointer_lock( &self ) {
176         js!( @(no_return)
177             @{self}.exitPointerLock();
178         );
179     }
180 
181     /// Import node from another document
182     ///
183     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode)
184     // https://dom.spec.whatwg.org/#ref-for-dom-document-importnode
import_node<N: INode>( &self, n: &N, kind: CloneKind ) -> Result<Node, NotSupportedError>185     pub fn import_node<N: INode>( &self, n: &N, kind: CloneKind ) -> Result<Node, NotSupportedError> {
186         let deep = match kind {
187             CloneKind::Deep => true,
188             CloneKind::Shallow => false,
189         };
190 
191         js_try!(
192             return @{self}.importNode( @{n.as_ref()}, @{deep} );
193         ).unwrap()
194     }
195 
196     /// Check if the fullscreen API is enabled
197     ///
198     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenEnabled)
199     // https://fullscreen.spec.whatwg.org/#ref-for-dom-document-fullscreenenabled
fullscreen_enabled( &self ) -> bool200     pub fn fullscreen_enabled( &self ) -> bool {
201         match js!( return @{self}.fullscreenEnabled; ) {
202             Value::Bool(value) => value,
203             _ => false, // if the variable is not set as a bool, then assume fullscreen is not supported
204         }
205     }
206 
207     /// Get the current fullscreen element, or None if there is nothing fullscreen
208     ///
209     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/fullscreenElement)
210     // https://fullscreen.spec.whatwg.org/#ref-for-dom-document-fullscreenelement
fullscreen_element( &self ) -> Option<Element>211     pub fn fullscreen_element( &self ) -> Option<Element> {
212         Some(js!( return @{self}.fullscreenElement; )
213             .into_reference()?
214             .downcast::<Element>()?)
215     }
216 
217     /// Request the page return from fullscreen mode to a normal state
218     ///
219     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitFullscreen)
220     // https://fullscreen.spec.whatwg.org/#dom-document-exitfullscreen
221     #[cfg(feature = "experimental_features_which_may_break_on_minor_version_bumps")]
exit_fullscreen(&self) -> TypedPromise<(), TypeError>222     pub fn exit_fullscreen(&self) -> TypedPromise<(), TypeError> {
223         let promise: Promise = js!( return @{self}.exitFullscreen(); )
224             .try_into().unwrap();
225 
226         TypedPromise::new( promise )
227     }
228 }
229 
230 
231 #[cfg(all(test, feature = "web_test"))]
232 mod web_tests {
233     use super::*;
234     use webapi::node::{Node, INode, CloneKind};
235     use webapi::html_elements::TemplateElement;
236     use webapi::html_element::HtmlElement;
237 
238     #[test]
test_create_element_invalid_character()239     fn test_create_element_invalid_character() {
240         match document().create_element("-invalid tag") {
241             Err(InvalidCharacterError{..}) => (),
242             v => panic!("expected InvalidCharacterError, got {:?}", v),
243         }
244     }
245 
246     #[test]
test_create_element_ns_invalid_character()247     fn test_create_element_ns_invalid_character() {
248         match document().create_element_ns("", "-invalid tag") {
249             Err(CreateElementNsError::InvalidCharacterError(_)) => (),
250             v => panic!("expected InvalidCharacterError, got {:?}", v),
251         }
252     }
253 
254     #[test]
test_create_element_ns_namespace_error()255     fn test_create_element_ns_namespace_error() {
256         match document().create_element_ns("", "illegal_prefix:svg") {
257             Err(CreateElementNsError::NamespaceError(_)) => (),
258             v => panic!("expected NamespaceError, got {:?}", v),
259         }
260     }
261 
262     #[test]
test_import_node()263     fn test_import_node() {
264         let document = document();
265         let tpl: TemplateElement = Node::from_html("<template><span>aaabbbcccddd</span></template>")
266             .unwrap()
267             .try_into()
268             .unwrap();
269 
270         let n = document.import_node(&tpl.content(), CloneKind::Deep).unwrap();
271         let child_nodes = n.child_nodes();
272         assert_eq!(child_nodes.len(), 1);
273 
274         let span_element: HtmlElement = child_nodes.iter().next().unwrap().try_into().unwrap();
275 
276         assert_eq!(span_element.node_name(), "SPAN");
277         assert_eq!(js!( return @{span_element}.innerHTML; ), "aaabbbcccddd");
278     }
279 }
280