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