View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_ELEMENTS_BY_NAME_EMPTY;
18  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
19  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.function.Supplier;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.htmlunit.ScriptResult;
32  import org.htmlunit.StringWebResponse;
33  import org.htmlunit.WebClient;
34  import org.htmlunit.WebWindow;
35  import org.htmlunit.corejs.javascript.Context;
36  import org.htmlunit.corejs.javascript.Function;
37  import org.htmlunit.corejs.javascript.Scriptable;
38  import org.htmlunit.html.BaseFrameElement;
39  import org.htmlunit.html.DomElement;
40  import org.htmlunit.html.DomNode;
41  import org.htmlunit.html.FrameWindow;
42  import org.htmlunit.html.HtmlAttributeChangeEvent;
43  import org.htmlunit.html.HtmlElement;
44  import org.htmlunit.html.HtmlForm;
45  import org.htmlunit.html.HtmlImage;
46  import org.htmlunit.html.HtmlPage;
47  import org.htmlunit.html.HtmlScript;
48  import org.htmlunit.javascript.HtmlUnitScriptable;
49  import org.htmlunit.javascript.JavaScriptEngine;
50  import org.htmlunit.javascript.PostponedAction;
51  import org.htmlunit.javascript.configuration.JsxClass;
52  import org.htmlunit.javascript.configuration.JsxConstructor;
53  import org.htmlunit.javascript.configuration.JsxFunction;
54  import org.htmlunit.javascript.configuration.JsxGetter;
55  import org.htmlunit.javascript.host.Element;
56  import org.htmlunit.javascript.host.dom.AbstractList.EffectOnCache;
57  import org.htmlunit.javascript.host.dom.Attr;
58  import org.htmlunit.javascript.host.dom.Document;
59  import org.htmlunit.javascript.host.dom.Node;
60  import org.htmlunit.javascript.host.dom.NodeList;
61  import org.htmlunit.javascript.host.dom.Selection;
62  import org.htmlunit.javascript.host.event.Event;
63  import org.htmlunit.util.UrlUtils;
64  
65  /**
66   * A JavaScript object for {@code HTMLDocument}.
67   *
68   * @author Mike Bowler
69   * @author David K. Taylor
70   * @author Chen Jun
71   * @author Christian Sell
72   * @author Chris Erskine
73   * @author Marc Guillemot
74   * @author Daniel Gredler
75   * @author Michael Ottati
76   * @author George Murnock
77   * @author Ahmed Ashour
78   * @author Rob Di Marco
79   * @author Sudhan Moghe
80   * @author Mike Dirolf
81   * @author Ronald Brill
82   * @author Frank Danek
83   * @author Sven Strickroth
84   *
85   * @see <a href="http://msdn.microsoft.com/en-us/library/ms535862.aspx">MSDN documentation</a>
86   * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-7068919">
87   *     W3C DOM Level 1</a>
88   */
89  @JsxClass
90  public class HTMLDocument extends Document {
91  
92      private static final Log LOG = LogFactory.getLog(HTMLDocument.class);
93  
94      private enum ParsingStatus { OUTSIDE, START, IN_NAME, INSIDE, IN_STRING }
95  
96      /** The buffer that will be used for calls to document.write(). */
97      private final StringBuilder writeBuilder_ = new StringBuilder();
98      private boolean writeInCurrentDocument_ = true;
99  
100     private boolean closePostponedAction_;
101     private boolean executionExternalPostponed_;
102 
103     /**
104      * JavaScript constructor.
105      */
106     @Override
107     @JsxConstructor
108     public void jsConstructor() {
109         super.jsConstructor();
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public DomNode getDomNodeOrDie() {
117         try {
118             return super.getDomNodeOrDie();
119         }
120         catch (final IllegalStateException e) {
121             throw JavaScriptEngine.typeError("No node attached to this object");
122         }
123     }
124 
125     /**
126      * Returns the HTML page that this document is modeling.
127      * @return the HTML page that this document is modeling
128      */
129     @Override
130     public HtmlPage getPage() {
131         return (HtmlPage) getDomNodeOrDie();
132     }
133 
134     /**
135      * JavaScript function "write" may accept a variable number of arguments.
136      * @param context the JavaScript context
137      * @param scope the scope
138      * @param thisObj the scriptable
139      * @param args the arguments passed into the method
140      * @param function the function
141      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536782.aspx">MSDN documentation</a>
142      */
143     @JsxFunction
144     public static void write(final Context context, final Scriptable scope,
145             final Scriptable thisObj, final Object[] args, final Function function) {
146         final HTMLDocument thisAsDocument = getDocument(thisObj);
147         thisAsDocument.write(concatArgsAsString(args));
148     }
149 
150     /**
151      * Converts the arguments to strings and concatenate them.
152      * @param args the JavaScript arguments
153      * @return the string concatenation
154      */
155     private static String concatArgsAsString(final Object[] args) {
156         final StringBuilder builder = new StringBuilder();
157         for (final Object arg : args) {
158             builder.append(JavaScriptEngine.toString(arg));
159         }
160         return builder.toString();
161     }
162 
163     /**
164      * JavaScript function "writeln" may accept a variable number of arguments.
165      * @param context the JavaScript context
166      * @param scope the scope
167      * @param thisObj the scriptable
168      * @param args the arguments passed into the method
169      * @param function the function
170      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536783.aspx">MSDN documentation</a>
171      */
172     @JsxFunction
173     public static void writeln(final Context context, final Scriptable scope,
174             final Scriptable thisObj, final Object[] args, final Function function) {
175         final HTMLDocument thisAsDocument = getDocument(thisObj);
176         thisAsDocument.write(concatArgsAsString(args) + "\n");
177     }
178 
179     /**
180      * Returns the current document instance, using <code>thisObj</code> as a hint.
181      * @param thisObj a hint as to the current document (maybe the prototype when function is used without "this")
182      * @return the current document instance
183      */
184     private static HTMLDocument getDocument(final Scriptable thisObj) {
185         // if function is used "detached", then thisObj is the top scope (ie Window), not the real object
186         // cf unit test DocumentTest#testDocumentWrite_AssignedToVar
187         // may be the prototype too
188         // cf DocumentTest#testDocumentWrite_AssignedToVar2
189         if (thisObj instanceof HTMLDocument && thisObj.getPrototype() instanceof HTMLDocument) {
190             return (HTMLDocument) thisObj;
191         }
192         if (thisObj instanceof DocumentProxy && thisObj.getPrototype() instanceof HTMLDocument) {
193             return (HTMLDocument) ((DocumentProxy) thisObj).getDelegee();
194         }
195 
196         throw JavaScriptEngine.reportRuntimeError("Function can't be used detached from document");
197     }
198 
199     /**
200      * This a hack!!! A cleaner way is welcome.
201      * Handle a case where document.write() is simply ignored.
202      * See HTMLDocumentWrite2Test.write_fromScriptAddedWithAppendChild_external.
203      * @param executing indicates if executing or not
204      */
205     public void setExecutingDynamicExternalPosponed(final boolean executing) {
206         executionExternalPostponed_ = executing;
207     }
208 
209     /**
210      * JavaScript function "write".
211      * <p>
212      * See http://www.whatwg.org/specs/web-apps/current-work/multipage/section-dynamic.html for
213      * a good description of the semantics of open(), write(), writeln() and close().
214      *
215      * @param content the content to write
216      */
217     protected void write(final String content) {
218         // really strange: if called from an external script loaded as postponed action, write is ignored!!!
219         if (executionExternalPostponed_) {
220             if (LOG.isDebugEnabled()) {
221                 LOG.debug("skipping write for external posponed: " + content);
222             }
223             return;
224         }
225 
226         if (LOG.isDebugEnabled()) {
227             LOG.debug("write: " + content);
228         }
229 
230         final HtmlPage page = (HtmlPage) getDomNodeOrDie();
231         if (!page.isBeingParsed()) {
232             writeInCurrentDocument_ = false;
233         }
234 
235         // Add content to the content buffer.
236         writeBuilder_.append(content);
237 
238         // If open() was called; don't write to doc yet -- wait for call to close().
239         if (!writeInCurrentDocument_) {
240             LOG.debug("wrote content to buffer");
241             scheduleImplicitClose();
242             return;
243         }
244         final String bufferedContent = writeBuilder_.toString();
245         if (!canAlreadyBeParsed(bufferedContent)) {
246             LOG.debug("write: not enough content to parse it now");
247             return;
248         }
249 
250         writeBuilder_.setLength(0);
251         page.writeInParsedStream(bufferedContent);
252     }
253 
254     private void scheduleImplicitClose() {
255         if (!closePostponedAction_) {
256             closePostponedAction_ = true;
257             final HtmlPage page = (HtmlPage) getDomNodeOrDie();
258             final WebWindow enclosingWindow = page.getEnclosingWindow();
259             page.getWebClient().getJavaScriptEngine().addPostponedAction(
260                     new PostponedAction(page, "HTMLDocument.scheduleImplicitClose") {
261                         @Override
262                         public void execute() throws Exception {
263                             if (writeBuilder_.length() != 0) {
264                                 close();
265                             }
266                             closePostponedAction_ = false;
267                         }
268 
269                         @Override
270                         public boolean isStillAlive() {
271                             return !enclosingWindow.isClosed();
272                         }
273                     });
274         }
275     }
276 
277     /**
278      * Indicates if the content is a well formed HTML snippet that can already be parsed to be added to the DOM.
279      *
280      * @param content the HTML snippet
281      * @return {@code false} if it not well formed
282      */
283     static boolean canAlreadyBeParsed(final String content) {
284         // all <script> must have their </script> because the parser doesn't close automatically this tag
285         // All tags must be complete, that is from '<' to '>'.
286         ParsingStatus tagState = ParsingStatus.OUTSIDE;
287         int tagNameBeginIndex = 0;
288         int scriptTagCount = 0;
289         boolean tagIsOpen = true;
290         char stringBoundary = 0;
291         boolean stringSkipNextChar = false;
292         int index = 0;
293         char openingQuote = 0;
294         for (final char currentChar : content.toCharArray()) {
295             switch (tagState) {
296                 case OUTSIDE:
297                     if (currentChar == '<') {
298                         tagState = ParsingStatus.START;
299                         tagIsOpen = true;
300                     }
301                     else if (scriptTagCount > 0 && (currentChar == '\'' || currentChar == '"')) {
302                         tagState = ParsingStatus.IN_STRING;
303                         stringBoundary = currentChar;
304                         stringSkipNextChar = false;
305                     }
306                     break;
307                 case START:
308                     if (currentChar == '/') {
309                         tagIsOpen = false;
310                         tagNameBeginIndex = index + 1;
311                     }
312                     else {
313                         tagNameBeginIndex = index;
314                     }
315                     tagState = ParsingStatus.IN_NAME;
316                     break;
317                 case IN_NAME:
318                     if (Character.isWhitespace(currentChar) || currentChar == '>') {
319                         final String tagName = content.substring(tagNameBeginIndex, index);
320                         if ("script".equalsIgnoreCase(tagName)) {
321                             if (tagIsOpen) {
322                                 scriptTagCount++;
323                             }
324                             else if (scriptTagCount > 0) {
325                                 // Ignore extra close tags for now. Let the parser deal with them.
326                                 scriptTagCount--;
327                             }
328                         }
329                         if (currentChar == '>') {
330                             tagState = ParsingStatus.OUTSIDE;
331                         }
332                         else {
333                             tagState = ParsingStatus.INSIDE;
334                         }
335                     }
336                     else if (!Character.isLetter(currentChar)) {
337                         tagState = ParsingStatus.OUTSIDE;
338                     }
339                     break;
340                 case INSIDE:
341                     if (currentChar == openingQuote) {
342                         openingQuote = 0;
343                     }
344                     else if (openingQuote == 0) {
345                         if (currentChar == '\'' || currentChar == '"') {
346                             openingQuote = currentChar;
347                         }
348                         else if (currentChar == '>' && openingQuote == 0) {
349                             tagState = ParsingStatus.OUTSIDE;
350                         }
351                     }
352                     break;
353                 case IN_STRING:
354                     if (stringSkipNextChar) {
355                         stringSkipNextChar = false;
356                     }
357                     else {
358                         if (currentChar == stringBoundary) {
359                             tagState = ParsingStatus.OUTSIDE;
360                         }
361                         else if (currentChar == '\\') {
362                             stringSkipNextChar = true;
363                         }
364                     }
365                     break;
366                 default:
367                     // nothing
368             }
369             index++;
370         }
371         if (scriptTagCount > 0 || tagState != ParsingStatus.OUTSIDE) {
372             if (LOG.isDebugEnabled()) {
373                 final StringBuilder message = new StringBuilder()
374                     .append("canAlreadyBeParsed() retruns false for content: '")
375                     .append(StringUtils.abbreviateMiddle(content, ".", 100))
376                     .append("' (scriptTagCount: ")
377                         .append(scriptTagCount)
378                     .append(" tagState: ")
379                         .append(tagState)
380                     .append(')');
381                 LOG.debug(message.toString());
382             }
383             return false;
384         }
385 
386         return true;
387     }
388 
389     /**
390      * Gets the node that is the last one when exploring following nodes, depth-first.
391      * @param node the node to search
392      * @return the searched node
393      */
394     HtmlElement getLastHtmlElement(final HtmlElement node) {
395         final DomNode lastChild = node.getLastChild();
396         if (!(lastChild instanceof HtmlElement)
397                 || lastChild instanceof HtmlScript) {
398             return node;
399         }
400 
401         return getLastHtmlElement((HtmlElement) lastChild);
402     }
403 
404     /**
405      * JavaScript function "open".
406      * <p>
407      * See http://www.whatwg.org/specs/web-apps/current-work/multipage/section-dynamic.html for
408      * a good description of the semantics of open(), write(), writeln() and close().
409      *
410      * @param url when a new document is opened, <i>url</i> is a String that specifies a MIME type for the document.
411      *        When a new window is opened, <i>url</i> is a String that specifies the URL to render in the new window
412      * @param name the name
413      * @param features the features
414      * @param replace whether to replace in the history list or no
415      * @return a reference to the new document object.
416      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536652.aspx">MSDN documentation</a>
417      */
418     @JsxFunction
419     public HTMLDocument open(final Object url, final Object name, final Object features,
420             final Object replace) {
421         // Any open() invocations are ignored during the parsing stage, because write() and
422         // writeln() invocations will directly append content to the current insertion point.
423         final HtmlPage page = getPage();
424         if (page.isBeingParsed()) {
425             LOG.warn("Ignoring call to open() during the parsing stage.");
426             return null;
427         }
428 
429         // We're not in the parsing stage; OK to continue.
430         if (!writeInCurrentDocument_) {
431             LOG.warn("Function open() called when document is already open.");
432         }
433         writeInCurrentDocument_ = false;
434         final WebWindow ww = getWindow().getWebWindow();
435         if (ww instanceof FrameWindow
436                 && UrlUtils.ABOUT_BLANK.equals(getPage().getUrl().toExternalForm())) {
437             final URL enclosingUrl = ((FrameWindow) ww).getEnclosingPage().getUrl();
438             getPage().getWebResponse().getWebRequest().setUrl(enclosingUrl);
439         }
440         return this;
441     }
442 
443     /**
444      * {@inheritDoc}
445      */
446     @Override
447     @JsxFunction({FF, FF_ESR})
448     public void close() throws IOException {
449         if (writeInCurrentDocument_) {
450             LOG.warn("close() called when document is not open.");
451         }
452         else {
453             final HtmlPage page = getPage();
454             final URL url = page.getUrl();
455             final StringWebResponse webResponse = new StringWebResponse(writeBuilder_.toString(), url);
456             webResponse.setFromJavascript(true);
457             writeInCurrentDocument_ = true;
458             writeBuilder_.setLength(0);
459 
460             final WebClient webClient = page.getWebClient();
461             final WebWindow window = page.getEnclosingWindow();
462             // reset isAttachedToPageDuringOnload_ to trigger the onload event for chrome also
463             if (window instanceof FrameWindow) {
464                 final BaseFrameElement frame = ((FrameWindow) window).getFrameElement();
465                 final HtmlUnitScriptable scriptable = frame.getScriptableObject();
466                 if (scriptable instanceof HTMLIFrameElement) {
467                     ((HTMLIFrameElement) scriptable).onRefresh();
468                 }
469             }
470             webClient.loadWebResponseInto(webResponse, window);
471         }
472     }
473 
474     /**
475      * {@inheritDoc}
476      */
477     @JsxGetter
478     @Override
479     public Element getDocumentElement() {
480         implicitCloseIfNecessary();
481         return super.getDocumentElement();
482     }
483 
484     /**
485      * Closes the document implicitly, i.e. flushes the <code>document.write</code> buffer (IE only).
486      */
487     private void implicitCloseIfNecessary() {
488         if (!writeInCurrentDocument_) {
489             try {
490                 close();
491             }
492             catch (final IOException e) {
493                 throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
494             }
495         }
496     }
497 
498     /**
499      * {@inheritDoc}
500      */
501     @Override
502     public Node appendChild(final Object childObject) {
503         throw JavaScriptEngine.asJavaScriptException(
504                 getWindow(),
505                 "Node cannot be inserted at the specified point in the hierarchy.",
506                 org.htmlunit.javascript.host.dom.DOMException.HIERARCHY_REQUEST_ERR);
507     }
508 
509     /**
510      * Returns the element with the specified ID, or {@code null} if that element could not be found.
511      * @param id the ID to search for
512      * @return the element, or {@code null} if it could not be found
513      */
514     @JsxFunction
515     @Override
516     public HtmlUnitScriptable getElementById(final String id) {
517         implicitCloseIfNecessary();
518         final DomElement domElement = getPage().getElementById(id);
519         if (null == domElement) {
520             // Just fall through - result is already set to null
521             if (LOG.isDebugEnabled()) {
522                 LOG.debug("getElementById(" + id + "): no DOM node found with this id");
523             }
524             return null;
525         }
526 
527         final HtmlUnitScriptable jsElement = getScriptableFor(domElement);
528         if (jsElement == NOT_FOUND) {
529             if (LOG.isDebugEnabled()) {
530                 LOG.debug("getElementById(" + id
531                         + ") cannot return a result as there isn't a JavaScript object for the HTML element "
532                         + domElement.getClass().getName());
533             }
534             return null;
535         }
536         return jsElement;
537     }
538 
539     /**
540      * {@inheritDoc}
541      */
542     @Override
543     public HTMLCollection getElementsByClassName(final String className) {
544         return getDocumentElement().getElementsByClassName(className);
545     }
546 
547     /**
548      * {@inheritDoc}
549      */
550     @Override
551     public NodeList getElementsByName(final String elementName) {
552         implicitCloseIfNecessary();
553 
554         if ("null".equals(elementName)
555                 || (elementName.isEmpty()
556                     && getBrowserVersion().hasFeature(HTMLDOCUMENT_ELEMENTS_BY_NAME_EMPTY))) {
557             return NodeList.staticNodeList(getWindow(), new ArrayList<>());
558         }
559 
560         final HtmlPage page = getPage();
561         final NodeList elements = new NodeList(page, true);
562         elements.setElementsSupplier(
563                 (Supplier<List<DomNode>> & Serializable)
564                 () -> new ArrayList<>(page.getElementsByName(elementName)));
565 
566         elements.setEffectOnCacheFunction(
567                 (java.util.function.Function<HtmlAttributeChangeEvent, EffectOnCache> & Serializable)
568                 event -> {
569                     if ("name".equals(event.getName())) {
570                         return EffectOnCache.RESET;
571                     }
572                     return EffectOnCache.NONE;
573                 });
574 
575         return elements;
576     }
577 
578     /**
579      * Calls to <code>document.XYZ</code> should first look at elements named <code>XYZ</code> before
580      * using standard functions.
581      * <p>
582      * {@inheritDoc}
583      */
584     @Override
585     protected Object getWithPreemption(final String name) {
586         final HtmlPage page = (HtmlPage) getDomNodeOrNull();
587         if (page == null) {
588             final Object response = getPrototype().get(name, this);
589             if (response != NOT_FOUND) {
590                 return response;
591             }
592         }
593         return getIt(name);
594     }
595 
596     private Object getIt(final String name) {
597         final HtmlPage page = (HtmlPage) getDomNodeOrNull();
598         if (page == null) {
599             return NOT_FOUND;
600         }
601 
602         // for performance,
603         // we will calculate the elements to decide if we really have
604         // to really create a HTMLCollection or not
605         final List<DomNode> matchingElements = getItComputeElements(page, name);
606         final int size = matchingElements.size();
607         if (size == 0) {
608             return NOT_FOUND;
609         }
610         if (size == 1) {
611             final DomNode object = matchingElements.get(0);
612             if (object instanceof BaseFrameElement) {
613                 return ((BaseFrameElement) object).getEnclosedWindow().getScriptableObject();
614             }
615             return super.getScriptableFor(object);
616         }
617 
618         final HTMLCollection coll = new HTMLCollection(page, matchingElements) {
619             @Override
620             protected HtmlUnitScriptable getScriptableFor(final Object object) {
621                 if (object instanceof BaseFrameElement) {
622                     return ((BaseFrameElement) object).getEnclosedWindow().getScriptableObject();
623                 }
624                 return super.getScriptableFor(object);
625             }
626         };
627 
628         coll.setElementsSupplier(
629                 (Supplier<List<DomNode>> & Serializable)
630                 () -> getItComputeElements(page, name));
631 
632         coll.setEffectOnCacheFunction(
633                 (java.util.function.Function<HtmlAttributeChangeEvent, EffectOnCache> & Serializable)
634                 event -> {
635                     final String attributeName = event.getName();
636                     if (DomElement.NAME_ATTRIBUTE.equals(attributeName)) {
637                         return EffectOnCache.RESET;
638                     }
639 
640                     return EffectOnCache.NONE;
641                 });
642 
643         return coll;
644     }
645 
646     static List<DomNode> getItComputeElements(final HtmlPage page, final String name) {
647         final List<DomElement> elements = page.getElementsByName(name);
648         final List<DomNode> matchingElements = new ArrayList<>();
649         for (final DomElement elt : elements) {
650             if (elt instanceof HtmlForm || elt instanceof HtmlImage || elt instanceof BaseFrameElement) {
651                 matchingElements.add(elt);
652             }
653         }
654         return matchingElements;
655     }
656 
657     /**
658      * {@inheritDoc}
659      */
660     @Override
661     public HTMLElement getHead() {
662         final HtmlElement head = getPage().getHead();
663         if (head == null) {
664             return null;
665         }
666         return head.getScriptableObject();
667     }
668 
669     /**
670      * {@inheritDoc}
671      */
672     @Override
673     public String getTitle() {
674         return getPage().getTitleText();
675     }
676 
677     /**
678      * {@inheritDoc}
679      */
680     @Override
681     public void setTitle(final String title) {
682         getPage().setTitleText(title);
683     }
684 
685     /**
686      * {@inheritDoc}
687      */
688     @Override
689     public HTMLElement getActiveElement() {
690         final HtmlElement activeElement = getPage().getActiveElement();
691         if (activeElement != null) {
692             return activeElement.getScriptableObject();
693         }
694         return null;
695     }
696 
697     /**
698      * {@inheritDoc}
699      */
700     @Override
701     public boolean hasFocus() {
702         return getPage().getFocusedElement() != null;
703     }
704 
705     /**
706      * Dispatches an event into the event system (standards-conformant browsers only). See
707      * <a href="https://developer.mozilla.org/en-US/docs/DOM/element.dispatchEvent">the Gecko
708      * DOM reference</a> for more information.
709      *
710      * @param event the event to be dispatched
711      * @return {@code false} if at least one of the event handlers which handled the event
712      *         called <code>preventDefault</code>; {@code true} otherwise
713      */
714     @Override
715     @JsxFunction
716     public boolean dispatchEvent(final Event event) {
717         event.setTarget(this);
718         final ScriptResult result = fireEvent(event);
719         return !event.isAborted(result);
720     }
721 
722     /**
723      * {@inheritDoc}
724      */
725     @Override
726     public Selection getSelection() {
727         return getWindow().getSelectionImpl();
728     }
729 
730     /**
731      * Creates a new HTML attribute with the specified name.
732      *
733      * @param attributeName the name of the attribute to create
734      * @return an attribute with the specified name
735      */
736     @Override
737     public Attr createAttribute(final String attributeName) {
738         String name = attributeName;
739         if (!org.htmlunit.util.StringUtils.isEmptyOrNull(name)) {
740             name = org.htmlunit.util.StringUtils.toRootLowerCase(name);
741         }
742 
743         return super.createAttribute(name);
744     }
745 
746     /**
747      * {@inheritDoc}
748      */
749     @Override
750     public String getBaseURI() {
751         return getPage().getBaseURL().toString();
752     }
753 
754     /**
755      * {@inheritDoc}
756      */
757     @Override
758     public HtmlUnitScriptable elementFromPoint(final int x, final int y) {
759         final HtmlElement element = getPage().getElementFromPoint(x, y);
760         return element == null ? null : element.getScriptableObject();
761     }
762 }