View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.dom;
16  
17  import java.io.IOException;
18  
19  import org.htmlunit.SgmlPage;
20  import org.htmlunit.StringWebResponse;
21  import org.htmlunit.WebClient;
22  import org.htmlunit.WebResponse;
23  import org.htmlunit.WebWindow;
24  import org.htmlunit.corejs.javascript.Context;
25  import org.htmlunit.corejs.javascript.Function;
26  import org.htmlunit.corejs.javascript.Scriptable;
27  import org.htmlunit.corejs.javascript.VarScope;
28  import org.htmlunit.html.DomDocumentType;
29  import org.htmlunit.html.Html;
30  import org.htmlunit.html.HtmlPage;
31  import org.htmlunit.html.parser.HTMLParser;
32  import org.htmlunit.javascript.HtmlUnitScriptable;
33  import org.htmlunit.javascript.JavaScriptEngine;
34  import org.htmlunit.javascript.configuration.JsxClass;
35  import org.htmlunit.javascript.configuration.JsxConstructor;
36  import org.htmlunit.javascript.configuration.JsxFunction;
37  import org.htmlunit.javascript.host.html.HTMLDocument;
38  import org.htmlunit.javascript.host.xml.XMLDocument;
39  import org.htmlunit.util.StringUtils;
40  import org.htmlunit.util.UrlUtils;
41  import org.htmlunit.xml.XmlPage;
42  
43  /**
44   * A JavaScript object for {@code DOMImplementation}.
45   *
46   * @author Ahmed Ashour
47   * @author Frank Danek
48   * @author Ronald Brill
49   * @author Adam Afeltowicz
50   *
51   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation">MDN Documentation</a>
52   * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-core.html#ID-102161490">
53   *     W3C Dom Level 1</a>
54   */
55  @JsxClass
56  public class DOMImplementation extends HtmlUnitScriptable {
57  
58      /**
59       * JavaScript constructor.
60       */
61      @JsxConstructor
62      public void jsConstructor() {
63          // nothing to do
64      }
65  
66      /**
67       * Tests if the DOM implementation implements a specific feature.
68       * @param feature the name of the feature to test (case-insensitive)
69       * @param version the version number of the feature to test
70       * @return true if the feature is implemented in the specified version, false otherwise
71       */
72      @JsxFunction
73      public boolean hasFeature(final String feature, final String version) {
74          switch (feature) {
75              case "Core":
76              case "HTML":
77              case "XHTML":
78              case "XML":
79                  switch (version) {
80                      case "1.0":
81                      case "2.0":
82                      case "3.0":
83                          return true;
84                      default:
85                  }
86                  break;
87  
88              case "Views":
89                  switch (version) {
90                      case "1.0":
91                      case "2.0":
92                      case "3.0":
93                          return true;
94                      default:
95                  }
96                  break;
97  
98              case "StyleSheets":
99              case "KeyboardEvents":
100             case "MutationNameEvents":
101             case "TextEvents":
102             case "LS":
103             case "LS-Async":
104             case "Validation":
105             case "XPath":
106                 return true;
107 
108             case "CSS":
109                 switch (version) {
110                     case "1.0":
111                     case "2.0":
112                     case "3.0":
113                         return true;
114                     default:
115                 }
116                 break;
117 
118             case "CSS2":
119                 switch (version) {
120                     case "1.0":
121                     case "2.0":
122                     case "3.0":
123                         return true;
124                     default:
125                 }
126                 break;
127 
128             case "CSS3":
129                 switch (version) {
130                     case "1.0":
131                     case "2.0":
132                     case "3.0":
133                         return true;
134                     default:
135                 }
136                 break;
137 
138             case "Events":
139             case "HTMLEvents":
140             case "MouseEvents":
141             case "MutationEvents":
142                 switch (version) {
143                     case "1.0":
144                     case "2.0":
145                     case "3.0":
146                         return true;
147                     default:
148                 }
149                 break;
150 
151             case "UIEvents":
152                 switch (version) {
153                     case "1.0":
154                     case "2.0":
155                     case "3.0":
156                         return true;
157                     default:
158                 }
159                 break;
160 
161             case "Range":
162             case "Traversal":
163                 switch (version) {
164                     case "1.0":
165                     case "2.0":
166                     case "3.0":
167                         return true;
168                     default:
169                 }
170                 break;
171 
172             case "http://www.w3.org/TR/SVG11/feature#BasicStructure":
173             case "http://www.w3.org/TR/SVG11/feature#Shape":
174                 switch (version) {
175                     case "1.0":
176                     case "1.1":
177                     case "1.2":
178                         return true;
179                     default:
180                 }
181                 break;
182 
183             default:
184         }
185         //TODO: other features.
186         return false;
187     }
188 
189     /**
190      * Creates an {@link XMLDocument}.
191      *
192      * @param namespaceURI the URI that identifies an XML namespace
193      * @param qualifiedName the qualified name of the document to instantiate
194      * @param doctype the document types of the document
195      * @return the newly created {@link XMLDocument}
196      */
197     @JsxFunction
198     public XMLDocument createDocument(final Object namespaceURI, final String qualifiedName,
199             final DocumentType doctype) {
200         // null (and undefined) means no namespace; a String parameter converts null to 'null'
201         final String namespace;
202         if (namespaceURI == null || JavaScriptEngine.isUndefined(namespaceURI)) {
203             namespace = null;
204         }
205         else {
206             namespace = JavaScriptEngine.toString(namespaceURI);
207         }
208 
209         final XMLDocument document = new XMLDocument(getWindow().getWebWindow());
210         document.setParentScope(getParentScope());
211         document.setPrototype(getPrototype(document.getClass()));
212 
213         final XmlPage page = (XmlPage) document.getDomNodeOrDie();
214         if (doctype != null) {
215             // the doctype is in front of the root element
216             final DomDocumentType doctypeNode = (DomDocumentType) doctype.getDomNodeOrDie();
217             page.appendChild(doctypeNode);
218             page.setDocumentType(doctypeNode);
219         }
220         if (qualifiedName != null && !qualifiedName.isEmpty()) {
221             page.appendChild(page.createElementNS(
222                     StringUtils.isEmptyString(namespace) ? null : namespace, qualifiedName));
223         }
224         return document;
225     }
226 
227     /**
228      * Creates a {@link DocumentType}.
229      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType">
230      *     createDocumentType (MDN)</a>
231      *
232      * @param context the JavaScript context
233      * @param scope the scope
234      * @param thisObj the scriptable
235      * @param args the arguments passed into the method: qualifiedName, publicId and systemId
236      * @param function the function
237      * @return the newly created {@link DocumentType}
238      */
239     @JsxFunction
240     public static DocumentType createDocumentType(final Context context, final VarScope scope,
241             final Scriptable thisObj, final Object[] args, final Function function) {
242         if (args.length < 3) {
243             throw JavaScriptEngine.typeError("Failed to execute 'createDocumentType' on 'DOMImplementation': "
244                     + "3 arguments required, but only " + args.length + " present.");
245         }
246 
247         final DOMImplementation implementation = (DOMImplementation) thisObj;
248         final String qualifiedName = JavaScriptEngine.toString(args[0]);
249         final String publicId = JavaScriptEngine.toString(args[1]);
250         final String systemId = JavaScriptEngine.toString(args[2]);
251 
252         if (!isValidDocumentTypeName(qualifiedName)) {
253             throw JavaScriptEngine.asJavaScriptException(
254                     implementation.getWindow(),
255                     "createDocumentType: Provided string '" + qualifiedName + "' contains an invalid character",
256                     DOMException.INVALID_CHARACTER_ERR);
257         }
258 
259         // the node is owned by the page of the window; if it is appended to another document
260         // the page is replaced
261         final SgmlPage page = (SgmlPage) implementation.getWindow().getWebWindow().getEnclosedPage();
262         final DomDocumentType doctype = new DomDocumentType(page, qualifiedName, publicId, systemId);
263         return (DocumentType) implementation.getScriptableFor(doctype);
264     }
265 
266     // The name does not have to be a valid XML name (the empty string, '1a' or '-a' are fine), the
267     // browsers only reject ASCII whitespace and '>' (and NULL).
268     // Do not share this with the check of the processing instruction target, there the Name
269     // production is still required.
270     private static boolean isValidDocumentTypeName(final String name) {
271         final int length = name.length();
272         for (int i = 0; i < length; i++) {
273             final char c = name.charAt(i);
274             if (c == '>' || c == '\u0000'
275                     || c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r') {
276                 return false;
277             }
278         }
279         return true;
280     }
281 
282     /**
283      * Creates an {@link HTMLDocument}.
284      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createHTMLDocument">
285      *     createHTMLDocument (MDN)</a>
286      *
287      * @param titleObj the document title
288      * @return the newly created {@link HTMLDocument}
289      */
290     @JsxFunction
291     public HTMLDocument createHTMLDocument(final Object titleObj) {
292         // a similar impl is in
293         // org.htmlunit.javascript.host.dom.DOMParser.parseFromString(String, Object)
294         try {
295             final WebWindow webWindow = getWindow().getWebWindow();
296             final String html;
297             if (JavaScriptEngine.isUndefined(titleObj)) {
298                 html = Html.DOCTYPE_HTML + "<html><head></head><body></body></html>";
299             }
300             else {
301                 html = Html.DOCTYPE_HTML
302                         + "<html><head><title>"
303                         + JavaScriptEngine.toString(titleObj)
304                         + "</title></head><body></body></html>";
305             }
306             final WebResponse webResponse = new StringWebResponse(html, UrlUtils.URL_ABOUT_BLANK);
307             final HtmlPage page = new HtmlPage(webResponse, webWindow);
308             // According to spec and behavior of function in browsers new document
309             // has no location object and is not connected with any window
310             page.setEnclosingWindow(null);
311 
312             // document knows the window but is not the windows document
313             final HTMLDocument document = new HTMLDocument();
314             document.setParentScope(getTopLevelScope(getParentScope()));
315             document.setPrototype(getPrototype(document.getClass()));
316             // document.setWindow(getWindow());
317             document.setDomNode(page);
318 
319             final WebClient webClient = webWindow.getWebClient();
320             final HTMLParser htmlParser = webClient.getPageCreator().getHtmlParser();
321             htmlParser.parse(webClient, webResponse, page, false, false);
322             return page.getScriptableObject();
323         }
324         catch (final IOException e) {
325             throw JavaScriptEngine.reportRuntimeError("Parsing failed" + e.getMessage());
326         }
327     }
328 }