KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > html > HtmlPage


1 /*
2  * Copyright (c) 2002, 2005 Gargoyle Software Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12  * 3. The end-user documentation included with the redistribution, if any, must
13  * include the following acknowledgment:
14  *
15  * "This product includes software developed by Gargoyle Software Inc.
16  * (http://www.GargoyleSoftware.com/)."
17  *
18  * Alternately, this acknowledgment may appear in the software itself, if
19  * and wherever such third-party acknowledgments normally appear.
20  * 4. The name "Gargoyle Software" must not be used to endorse or promote
21  * products derived from this software without prior written permission.
22  * For written permission, please contact info@GargoyleSoftware.com.
23  * 5. Products derived from this software may not be called "HtmlUnit", nor may
24  * "HtmlUnit" appear in their name, without prior written permission of
25  * Gargoyle Software Inc.
26  *
27  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30  * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  */

38 package com.gargoylesoftware.htmlunit.html;
39
40 import java.io.IOException JavaDoc;
41 import java.net.MalformedURLException JavaDoc;
42 import java.net.URL JavaDoc;
43 import java.util.ArrayList JavaDoc;
44 import java.util.Arrays JavaDoc;
45 import java.util.Collections JavaDoc;
46 import java.util.Comparator JavaDoc;
47 import java.util.HashMap JavaDoc;
48 import java.util.Iterator JavaDoc;
49 import java.util.List JavaDoc;
50 import java.util.Map JavaDoc;
51
52 import org.org.apache.commons.httpclient.util.EncodingUtil;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.commons.logging.Log;
55 import org.apache.commons.logging.LogFactory;
56 import org.mozilla.javascript.Function;
57 import org.mozilla.javascript.Scriptable;
58
59 import com.gargoylesoftware.htmlunit.Assert;
60 import com.gargoylesoftware.htmlunit.ElementNotFoundException;
61 import com.gargoylesoftware.htmlunit.Page;
62 import com.gargoylesoftware.htmlunit.ScriptEngine;
63 import com.gargoylesoftware.htmlunit.ScriptResult;
64 import com.gargoylesoftware.htmlunit.TextUtil;
65 import com.gargoylesoftware.htmlunit.WebClient;
66 import com.gargoylesoftware.htmlunit.WebRequestSettings;
67 import com.gargoylesoftware.htmlunit.WebResponse;
68 import com.gargoylesoftware.htmlunit.WebWindow;
69 import com.gargoylesoftware.htmlunit.javascript.host.Event;
70 import com.gargoylesoftware.htmlunit.javascript.host.Window;
71
72 /**
73  * A representation of an html page returned from a server. This class is the
74  * DOM Document implementation.
75  *
76  * @version $Revision: 100 $
77  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
78  * @author Alex Nikiforoff
79  * @author Noboru Sinohara
80  * @author David K. Taylor
81  * @author Andreas Hangler
82  * @author <a HREF="mailto:cse@dynabean.de">Christian Sell</a>
83  * @author Chris Erskine
84  * @author Marc Guillemot
85  */

86 public final class HtmlPage extends DomNode implements Page {
87
88     private final WebClient webClient_;
89     private final URL JavaDoc originatingUrl_;
90     private String JavaDoc originalCharset_ = null;
91     private final WebResponse webResponse_;
92     private final Map JavaDoc idMap_ = new HashMap JavaDoc();
93     private HtmlElement documentElement_ = null;
94
95     private WebWindow enclosingWindow_;
96
97     private static int FunctionWrapperCount_ = 0;
98     private final Log javascriptLog_ = LogFactory.getLog("com.gargoylesoftware.htmlunit.javascript");
99
100     private static final int TAB_INDEX_NOT_SPECIFIED = -10;
101     private static final int TAB_INDEX_OUT_OF_BOUNDS = -20;
102
103     /**
104      * This constructor should no longer be used
105      *
106      * @param webClient NOT USED
107      * @param webResponse The web response that was used to create this page
108      * @param originatingUrl The url that was used to load this page.
109      * @param webWindow The window that this page is being loaded into.
110      * @deprecated
111      */

112     public HtmlPage(
113             final WebClient webClient,
114             final URL JavaDoc originatingUrl,
115             final WebResponse webResponse,
116             final WebWindow webWindow ) {
117         this(originatingUrl, webResponse, webWindow);
118     }
119     /**
120      * Create an instance of HtmlPage
121      *
122      * @param originatingUrl The url that was used to load this page.
123      * @param webResponse The web response that was used to create this page
124      * @param webWindow The window that this page is being loaded into.
125      */

126     public HtmlPage(
127             final URL JavaDoc originatingUrl,
128             final WebResponse webResponse,
129             final WebWindow webWindow ) {
130
131         super(null);
132
133         webClient_ = webWindow.getWebClient();
134         originatingUrl_ = originatingUrl;
135         webResponse_ = webResponse;
136         setEnclosingWindow(webWindow);
137
138         final ScriptEngine engine = getWebClient().getScriptEngine();
139         if( engine != null ) {
140             engine.initialize(this);
141         }
142     }
143
144     /**
145      * @return this page
146      */

147     public HtmlPage getPage() {
148         return this;
149     }
150
151     /**
152      * Initialize this page.
153      * @throws IOException If an IO problem occurs.
154      */

155     public void initialize() throws IOException JavaDoc {
156         insertTbodyTagsAsNeeded();
157         documentElement_.setReadyState(READY_STATE_COMPLETE);
158         executeOnLoadHandlersIfNeeded();
159         executeRefreshIfNeeded();
160     }
161
162
163     /**
164      * Clean up this page.
165      * @throws IOException If an IO problem occurs.
166      */

167     public void cleanUp() throws IOException JavaDoc {
168         deregisterFramesIfNeeded();
169         // TODO: executeBodyOnUnloadHandlerIfNeeded();
170
}
171
172     /**
173      * Get the type of the current node.
174      * @return The node type
175      */

176     public short getNodeType() {
177         return DOCUMENT_NODE;
178     }
179
180     /**
181      * Get the name for the current node.
182      * @return The node name
183      */

184     public String JavaDoc getNodeName() {
185         return "#document";
186     }
187
188     /**
189      * Get the root element of this document.
190      * @return The root element
191      */

192     public HtmlElement getDocumentElement() {
193         if (documentElement_ == null) {
194             DomNode childNode = getFirstChild();
195             while (childNode != null && ! (childNode instanceof HtmlElement)) {
196                 childNode = childNode.getNextSibling();
197             }
198             documentElement_ = (HtmlElement) childNode;
199         }
200         return documentElement_;
201     }
202
203     /**
204      * Return the charset used in the page.
205      * The sources of this information are from 1).meta element which
206      * http-equiv attribute value is 'content-type', or if not from
207      * the response header.
208      * @return the value of charset.
209      */

210     public String JavaDoc getPageEncoding() {
211         if( originalCharset_ != null ) {
212             return originalCharset_ ;
213         }
214         final ArrayList JavaDoc ar = new ArrayList JavaDoc();
215         ar.add("meta");
216         final List JavaDoc list = getDocumentElement().getHtmlElementsByTagNames(ar);
217         for(int i=0; i<list.size();i++ ){
218             final HtmlMeta meta = (HtmlMeta) list.get(i);
219             final String JavaDoc httpequiv = meta.getHttpEquivAttribute();
220             if( "content-type".equals(httpequiv.toLowerCase()) ){
221                 final String JavaDoc contents = meta.getContentAttribute();
222                 final int pos = contents.toLowerCase().indexOf("charset=");
223                 if( pos>=0 ){
224                     originalCharset_ = contents.substring(pos+8);
225                     getLog().debug("Page Encoding detected: " + originalCharset_);
226                     return originalCharset_;
227                 }
228             }
229         }
230         if( originalCharset_ == null ) {
231             originalCharset_ = webResponse_.getContentCharSet();
232         }
233         return originalCharset_;
234     }
235
236     /**
237      * Create a new HTML element with the given tag name.
238      *
239      * @param tagName The tag name, preferrably in lowercase
240      * @return the new HTML element.
241      */

242     public HtmlElement createElement(final String JavaDoc tagName) {
243         final String JavaDoc tagLower = tagName.toLowerCase();
244         return HTMLParser.getFactory(tagLower).createElement(this, tagLower, null);
245     }
246
247     /**
248      * Return the HtmlAnchor with the specified name
249      *
250      * @param name The name to search by
251      * @return See above
252      * @throws com.gargoylesoftware.htmlunit.ElementNotFoundException If the anchor could not be found.
253      */

254     public HtmlAnchor getAnchorByName( final String JavaDoc name ) throws ElementNotFoundException {
255         return ( HtmlAnchor )getDocumentElement().getOneHtmlElementByAttribute( "a", "name", name );
256     }
257
258     /**
259     * Return the {@link HtmlAnchor} with the specified href
260      *
261      * @param href The string to search by
262      * @return The HtmlAnchor
263      * @throws ElementNotFoundException If the anchor could not be found.
264      */

265     public HtmlAnchor getAnchorByHref( final String JavaDoc href ) throws ElementNotFoundException {
266         return ( HtmlAnchor )getDocumentElement().getOneHtmlElementByAttribute( "a", "href", href );
267     }
268
269
270     /**
271      * Return a list of all anchors contained in this page.
272      * @return All the anchors in this page.
273      */

274     public List JavaDoc getAnchors() {
275         return getDocumentElement().getHtmlElementsByTagNames( Collections.singletonList("a") );
276     }
277
278
279     /**
280      * Return the first anchor that contains the specified text.
281      * @param text The text to search for
282      * @return The first anchor that was found.
283      * @throws ElementNotFoundException If no anchors are found with the specified text
284      */

285     public HtmlAnchor getFirstAnchorByText( final String JavaDoc text ) throws ElementNotFoundException {
286         Assert.notNull("text", text);
287
288         final Iterator JavaDoc iterator = getAnchors().iterator();
289         while( iterator.hasNext() ) {
290             final HtmlAnchor anchor = (HtmlAnchor)iterator.next();
291             if( text.equals(anchor.asText()) ) {
292                 return anchor;
293             }
294         }
295         throw new ElementNotFoundException("a", "<text>", text);
296     }
297
298
299     /**
300      * Return the first form that matches the specifed name
301      * @param name The name to search for
302      * @return The first form.
303      * @exception ElementNotFoundException If no forms match the specifed result.
304      */

305     public HtmlForm getFormByName( final String JavaDoc name ) throws ElementNotFoundException {
306         final List JavaDoc forms = getDocumentElement().getHtmlElementsByAttribute( "form", "name", name );
307         if( forms.size() == 0 ) {
308             throw new ElementNotFoundException( "form", "name", name );
309         }
310         else {
311             return ( HtmlForm )forms.get( 0 );
312         }
313     }
314
315     /**
316      * Return a list of all the forms in the page.
317      * @return All the forms.
318      * @see #getForms
319      * @deprecated Use {@link #getForms} instead.
320      */

321     public List JavaDoc getAllForms() {
322         return getForms();
323     }
324
325     /**
326      * Return a list of all the forms in the page.
327      * @return All the forms.
328      */

329     public List JavaDoc getForms() {
330         return getDocumentElement().getHtmlElementsByTagNames( Arrays.asList(new String JavaDoc[]{"form"}) );
331     }
332
333     /**
334      * Return the WebClient that originally loaded this page
335      *
336      * @return See above
337      */

338     public WebClient getWebClient() {
339         return webClient_;
340     }
341
342     /**
343      * Given a relative url (ie /foo), return a fully qualified url based on
344      * the url that was used to load this page
345      *
346      * @param relativeUrl The relative url
347      * @return See above
348      * @exception MalformedURLException If an error occurred when creating a
349      * URL object
350      */

351     public URL JavaDoc getFullyQualifiedUrl( final String JavaDoc relativeUrl )
352         throws MalformedURLException JavaDoc {
353
354         final List JavaDoc baseElements = getDocumentElement().getHtmlElementsByTagNames( Collections.singletonList("base"));
355         final URL JavaDoc baseUrl;
356         if( baseElements.isEmpty() ) {
357             baseUrl = originatingUrl_;
358         }
359         else {
360             final HtmlBase htmlBase = (HtmlBase)baseElements.get(0);
361             final String JavaDoc href = htmlBase.getHrefAttribute();
362             if (href == null || href.length() == 0) {
363                 baseUrl = originatingUrl_;
364             }
365             else {
366                 baseUrl = new URL JavaDoc(href);
367             }
368         }
369         return WebClient.expandUrl( baseUrl, relativeUrl );
370     }
371
372     /**
373      * Given a target attribute value, resolve the target using a base
374      * target for the page.
375      *
376      * @param elementTarget The target specified as an attribute of the element.
377      * @return The resolved target to use for the element.
378      */

379     public String JavaDoc getResolvedTarget( final String JavaDoc elementTarget ) {
380
381         final List JavaDoc baseElements =
382             getDocumentElement().getHtmlElementsByTagNames( Collections.singletonList("base"));
383         final String JavaDoc resolvedTarget;
384         if( baseElements.isEmpty() ) {
385             resolvedTarget = elementTarget;
386         }
387         else if( elementTarget != null && elementTarget.length() > 0 ) {
388             resolvedTarget = elementTarget;
389         }
390         else {
391             final HtmlBase htmlBase = (HtmlBase)baseElements.get(0);
392             resolvedTarget = htmlBase.getTargetAttribute();
393         }
394         return resolvedTarget;
395     }
396
397
398     /**
399      * Return the web response that was originally used to create this page.
400      *
401      * @return The web response
402      */

403     public WebResponse getWebResponse() {
404         return webResponse_;
405     }
406
407
408     /**
409      * Return a list of ids (strings) that correspond to the tabbable elements
410      * in this page. Return them in the same order specified in {@link
411      * #getTabbableElements}
412      *
413      * @return The list of id's
414      */

415     public List JavaDoc getTabbableElementIds() {
416         final List JavaDoc list = new ArrayList JavaDoc( getTabbableElements() );
417         final int listSize = list.size();
418
419         for( int i = 0; i < listSize; i++ ) {
420             list.set( i, ( ( HtmlElement )list.get( i ) ).getAttributeValue( "id" ) );
421         }
422
423         return Collections.unmodifiableList( list );
424     }
425
426
427     /**
428      * Return a list of all elements that are tabbable in the order that will
429      * be used for tabbing.<p>
430      *
431      * The rules for determing tab order are as follows:
432      * <ol>
433      * <li> Those elements that support the tabindex attribute and assign a
434      * positive value to it are navigated first. Navigation proceeds from the
435      * element with the lowest tabindex value to the element with the highest
436      * value. Values need not be sequential nor must they begin with any
437      * particular value. Elements that have identical tabindex values should
438      * be navigated in the order they appear in the character stream.
439      * <li> Those elements that do not support the tabindex attribute or
440      * support it and assign it a value of "0" are navigated next. These
441      * elements are navigated in the order they appear in the character
442      * stream.
443      * <li> Elements that are disabled do not participate in the tabbing
444      * order.
445      * </ol>
446      * Additionally, the value of tabindex must be within 0 and 32767. Any
447      * values outside this range will be ignored.<p>
448      *
449      * The following elements support the tabindex attribute: A, AREA, BUTTON,
450      * INPUT, OBJECT, SELECT, and TEXTAREA.<p>
451      *
452      * @return A list containing all the tabbable elements in proper tab order.
453      */

454     public List JavaDoc getTabbableElements() {
455
456         final List JavaDoc acceptableTagNames = Arrays.asList(
457                 new Object JavaDoc[]{"a", "area", "button", "input", "object", "select", "textarea"} );
458         final List JavaDoc tabbableElements = new ArrayList JavaDoc();
459
460         final DescendantElementsIterator iterator = getAllHtmlChildElements();
461         while( iterator.hasNext() ) {
462             final HtmlElement element = iterator.nextElement();
463             final String JavaDoc tagName = element.getTagName();
464             if( acceptableTagNames.contains( tagName ) ) {
465                 final boolean isDisabled = element.isAttributeDefined("disabled");
466
467                 if( isDisabled == false && getTabIndex(element) != TAB_INDEX_OUT_OF_BOUNDS ) {
468                     tabbableElements.add(element);
469                 }
470             }
471         }
472         Collections.sort( tabbableElements, createTabOrderComparator() );
473         return Collections.unmodifiableList( tabbableElements );
474     }
475
476     private Comparator JavaDoc createTabOrderComparator() {
477
478         return new Comparator JavaDoc() {
479             public int compare( final Object JavaDoc object1, final Object JavaDoc object2 ) {
480                 final HtmlElement element1 = ( HtmlElement )object1;
481                 final HtmlElement element2 = ( HtmlElement )object2;
482
483                 final int tabIndex1 = getTabIndex( element1 );
484                 final int tabIndex2 = getTabIndex( element2 );
485
486                 final int result;
487                 if( tabIndex1 > 0 && tabIndex2 > 0 ) {
488                     result = tabIndex1 - tabIndex2;
489                 }
490                 else if( tabIndex1 > 0 ) {
491                     result = -1;
492                 }
493                 else if( tabIndex2 > 0 ) {
494                     result = +1;
495                 }
496                 else if( tabIndex1 == tabIndex2 ) {
497                     result = 0;
498                 }
499                 else {
500                     result = tabIndex2 - tabIndex1;
501                 }
502
503                 return result;
504             }
505         };
506     }
507
508
509     private int getTabIndex( final HtmlElement element ) {
510         final String JavaDoc tabIndexAttribute = element.getAttributeValue( "tabindex" );
511         if( tabIndexAttribute == null || tabIndexAttribute.length() == 0 ) {
512             return TAB_INDEX_NOT_SPECIFIED;
513         }
514
515         final int tabIndex = Integer.parseInt( tabIndexAttribute );
516         if( tabIndex >= 0 && tabIndex <= 32767 ) {
517             return tabIndex;
518         }
519         else {
520             return TAB_INDEX_OUT_OF_BOUNDS;
521         }
522     }
523
524
525     /**
526      * Return the html element that is assigned to the specified access key. An
527      * access key (aka mnemonic key) is used for keyboard navigation of the
528      * page.<p>
529      *
530      * Only the following html elements may have accesskey's defined: A, AREA,
531      * BUTTON, INPUT, LABEL, LEGEND, and TEXTAREA.
532      *
533      * @param accessKey The key to look for
534      * @return The html element that is assigned to the specified key or null
535      * if no elements can be found that match the specified key.
536      */

537     public HtmlElement getHtmlElementByAccessKey( final char accessKey ) {
538         final List JavaDoc elements = getHtmlElementsByAccessKey(accessKey);
539         if( elements.isEmpty() ) {
540             return null;
541         }
542         else {
543             return (HtmlElement)elements.get(0);
544         }
545     }
546
547
548     /**
549      * Return all the html elements that are assigned to the specified access key. An
550      * access key (aka mnemonic key) is used for keyboard navigation of the
551      * page.<p>
552      *
553      * The html specification seems to indicate that one accesskey cannot be used
554      * for multiple elements however Internet Explorer does seem to support this.
555      * It's worth noting that Mozilla does not support multiple elements with one
556      * access key so you are making your html browser specific if you rely on this
557      * feature.<p>
558      *
559      * Only the following html elements may have accesskey's defined: A, AREA,
560      * BUTTON, INPUT, LABEL, LEGEND, and TEXTAREA.
561      *
562      * @param accessKey The key to look for
563      * @return A list of html elements that are assigned to the specified accesskey.
564      */

565     public List JavaDoc getHtmlElementsByAccessKey( final char accessKey ) {
566
567         final List JavaDoc elements = new ArrayList JavaDoc();
568
569         final String JavaDoc searchString = ( "" + accessKey ).toLowerCase();
570         final List JavaDoc acceptableTagNames = Arrays.asList(
571                 new Object JavaDoc[]{"a", "area", "button", "input", "label", "legend", "textarea"} );
572
573         final DescendantElementsIterator iterator = getAllHtmlChildElements();
574         while( iterator.hasNext() ) {
575             final HtmlElement element = iterator.nextElement();
576             if( acceptableTagNames.contains( element.getTagName() ) ) {
577                 final String JavaDoc accessKeyAttribute = element.getAttributeValue("accesskey");
578                 if( searchString.equalsIgnoreCase( accessKeyAttribute ) ) {
579                     elements.add( element );
580                 }
581             }
582         }
583
584         return elements;
585     }
586
587     /**
588      * Many html elements are "tabbable" and can have a "tabindex" attribute
589      * that determines the order in which the components are navigated when
590      * pressing the tab key. To ensure good usability for keyboard navigation,
591      * all tabbable elements should have the tabindex attribute set.<p>
592      *
593      * Assert that all tabbable elements have a valid value set for "tabindex".
594      * If they don't then throw an exception as per {@link
595      * WebClient#assertionFailed(String)}
596      */

597     public void assertAllTabIndexAttributesSet() {
598
599         final List JavaDoc acceptableTagNames = Arrays.asList(
600                 new Object JavaDoc[]{"a", "area", "button", "input", "object", "select", "textarea"} );
601         final List JavaDoc tabbableElements = getDocumentElement().getHtmlElementsByTagNames( acceptableTagNames );
602
603         final Iterator JavaDoc iterator = tabbableElements.iterator();
604         while( iterator.hasNext() ) {
605             final HtmlElement htmlElement = ( HtmlElement )iterator.next();
606             final int tabIndex = getTabIndex( htmlElement );
607             if( tabIndex == TAB_INDEX_OUT_OF_BOUNDS ) {
608                 webClient_.assertionFailed(
609                     "Illegal value for tab index: "
610                     + htmlElement.getAttributeValue( "tabindex" ) );
611             }
612             else if( tabIndex == TAB_INDEX_NOT_SPECIFIED ) {
613                 webClient_.assertionFailed( "tabindex not set for " + htmlElement );
614             }
615         }
616     }
617
618     /**
619      * Many html components can have an "accesskey" attribute which defines a
620      * hot key for keyboard navigation. Assert that all access keys (mnemonics)
621      * in this page are unique. If they aren't then throw an exception as per
622      * {@link WebClient#assertionFailed(String)}
623      */

624     public void assertAllAccessKeyAttributesUnique() {
625
626         final List JavaDoc accessKeyList = new ArrayList JavaDoc();
627
628         final DescendantElementsIterator iterator = getAllHtmlChildElements();
629         while( iterator.hasNext() ) {
630             final String JavaDoc id = iterator.nextElement().getAttributeValue("accesskey" );
631             if( id != null && id.length() != 0 ) {
632                 if( accessKeyList.contains( id ) ) {
633                     webClient_.assertionFailed( "Duplicate access key: " + id );
634                 }
635                 else {
636                     accessKeyList.add( id );
637                 }
638             }
639         }
640     }
641
642
643     /**
644      * Each html element can have an id attribute and by definition, all ids
645      * must be unique within the document. <p>
646      *
647      * Assert that all ids in this page are unique. If they aren't then throw
648      * an exception as per {@link WebClient#assertionFailed(String)}
649      */

650     public void assertAllIdAttributesUnique() {
651         final List JavaDoc idList = new ArrayList JavaDoc();
652
653         final DescendantElementsIterator iterator = getAllHtmlChildElements();
654         while( iterator.hasNext() ) {
655             final String JavaDoc id = iterator.nextElement().getAttributeValue("id");
656             if( id != null && id.length() != 0 ) {
657                 if( idList.contains( id ) ) {
658                     webClient_.assertionFailed( "Duplicate ID: " + id );
659                 }
660                 else {
661                     idList.add( id );
662                 }
663             }
664         }
665     }
666
667     /**
668      * <p>
669      * Execute the specified javascript if a javascript engine was successfully
670      * instantiated. If this javascript causes the current page to be reloaded
671      * (through location="" or form.submit()) then return the new page. Otherwise
672      * return the current page.
673      * </p>
674      * <p><b>Please note:</b> Although this method is public, it is not intended for
675      * general execution of javascript. Users of HtmlUnit should interact with the pages
676      * as a user would by clicking on buttons or links and having the javascript event
677      * handlers execute as needed..
678      * </p>
679      *
680      * @param sourceCode The javascript code to execute.
681      * @param sourceName The name for this chunk of code. This name will be displayed
682      * in any error messages.
683      * @param wrapSourceInFunction True if this snippet of code should be placed inside
684      * a javascript function. This is neccessary for intrinsic event handlers that may
685      * try to return a value.
686      * @param htmlElement The html element for which this script is being executed.
687      * This element will be the context during the javascript execution. If null,
688      * the context will default to the page.
689      * @return A ScriptResult which will contain both the current page (which may be different than
690      * the previous page and a javascript result object.
691      */

692     public ScriptResult executeJavaScriptIfPossible(
693         String JavaDoc sourceCode, final String JavaDoc sourceName, final boolean wrapSourceInFunction,
694         final HtmlElement htmlElement ) {
695
696         final ScriptEngine engine = getWebClient().getScriptEngine();
697         if( engine == null ) {
698             return new ScriptResult(null, this);
699         }
700
701         final String JavaDoc prefix = "javascript:";
702         final int prefixLength = prefix.length();
703
704         if( sourceCode.length() > prefixLength
705                 && sourceCode.substring(0, prefixLength).equalsIgnoreCase(prefix)) {
706             sourceCode = sourceCode.substring(prefixLength);
707         }
708
709         final WebWindow window = getEnclosingWindow();
710         getWebClient().pushClearFirstWindow();
711         final Object JavaDoc result;
712         if( wrapSourceInFunction ) {
713             // Something that isn't likely to collide with the name of a function in the
714
// loaded javascript
715
final String JavaDoc wrapperName = "GargoyleWrapper"+(FunctionWrapperCount_++);
716             sourceCode = "function "+wrapperName+"() {"+sourceCode+"\n}";
717             getJsLog().debug("Now build JS function\n" + sourceCode);
718             engine.execute( this, sourceCode, "Wrapper definition for "+sourceName, htmlElement );
719             result = engine.execute( this, wrapperName+"()", sourceName, htmlElement );
720         }
721         else {
722             result = engine.execute( this, sourceCode, sourceName, htmlElement );
723         }
724
725         WebWindow firstWindow = getWebClient().popFirstWindow();
726         if ( firstWindow == null) {
727             firstWindow = window;
728         }
729
730         return new ScriptResult( result, firstWindow.getEnclosedPage() );
731     }
732
733     /**
734      * Execute a Function in the given context.
735      *
736      * @param function The javascript Function to call.
737      * @param thisObject The "this" object to be used during invocation.
738      * @param args The arguments to pass into the call.
739      * @param htmlElementScope The html element for which this script is being executed.
740      * This element will be the context during the javascript execution. If null,
741      * the context will default to the page.
742      * @return A ScriptResult which will contain both the current page (which may be different than
743      * the previous page and a javascript result object.
744      */

745     public ScriptResult executeJavaScriptFunctionIfPossible(
746             final Function function,
747             final Scriptable thisObject,
748             final Object JavaDoc[] args,
749             final HtmlElement htmlElementScope) {
750
751         final WebWindow window = getEnclosingWindow();
752         getWebClient().pushClearFirstWindow();
753
754         final ScriptEngine engine = getWebClient().getScriptEngine();
755         if( engine == null ) {
756             return new ScriptResult(null, this);
757         }
758
759         final Object JavaDoc result = engine.callFunction(this, function, thisObject, args, htmlElementScope);
760
761         WebWindow firstWindow = getWebClient().popFirstWindow();
762         if ( firstWindow == null) {
763             firstWindow = window;
764         }
765         return new ScriptResult(result, firstWindow.getEnclosedPage());
766     }
767
768     /**
769      * Return the log object for this element.
770      * @return The log object for this element.
771      */

772     protected Log getJsLog() {
773         return javascriptLog_;
774     }
775
776     /**
777      * Internal use only.
778      * @param srcAttribute The source attribute from the script tag.
779      * @param charset The charset attribute from the script tag.
780      */

781     public void loadExternalJavaScriptFile( final String JavaDoc srcAttribute,
782                                             final String JavaDoc charset ) {
783         final ScriptEngine engine = getWebClient().getScriptEngine();
784         if( engine != null ) {
785             engine.execute( this, loadJavaScriptFromUrl( srcAttribute, charset ), srcAttribute, null );
786         }
787     }
788
789     /**
790      * Internal use only. Return true if a script with the specified type and language attributes
791      * is actually JavaScript.
792      * @param typeAttribute The type attribute specified in the script tag.
793      * @param languageAttribute The language attribute specified in the script tag.
794      * @return true if the script is javascript
795      */

796     public static boolean isJavaScript( final String JavaDoc typeAttribute, final String JavaDoc languageAttribute ) {
797         // Unless otherwise specified, we have to assume that any script is javascript
798
final boolean isJavaScript;
799         if( languageAttribute != null && languageAttribute.length() != 0 ) {
800             isJavaScript = TextUtil.startsWithIgnoreCase(languageAttribute, "javascript");
801         }
802         else if( typeAttribute != null && typeAttribute.length() != 0 ) {
803             isJavaScript = typeAttribute.equals( "text/javascript" );
804         }
805         else {
806             isJavaScript = true;
807         }
808
809         return isJavaScript;
810     }
811
812     private String JavaDoc loadJavaScriptFromUrl( final String JavaDoc urlString,
813                                           final String JavaDoc charset ) {
814         URL JavaDoc url = null;
815         String JavaDoc scriptEncoding = charset;
816         getPageEncoding();
817         try {
818             url = getFullyQualifiedUrl(urlString);
819             final WebRequestSettings requestSettings = new WebRequestSettings(url);
820             final WebResponse webResponse = getWebClient().loadWebResponse(requestSettings);
821             if( webResponse.getStatusCode() == 200 ) {
822                 final String JavaDoc contentType = webResponse.getContentType();
823                 final String JavaDoc contentCharset = webResponse.getContentCharSet();
824                 if( contentType.equals("text/javascript") == false
825                         && contentType.equals("application/x-javascript") == false ) {
826                     getLog().warn(
827                         "Expected content type of text/javascript or application/x-javascript for remotely "
828                         + "loaded javascript element " + url + " but got [" + webResponse.getContentType()+"]");
829                 }
830                 if( scriptEncoding == null || scriptEncoding.length() == 0 ){
831                     if( contentCharset.equals("ISO-8859-1")==false ) {
832                         scriptEncoding = contentCharset;
833                     }
834                     else if( originalCharset_.equals("ISO-8859-1")==false ){
835                         scriptEncoding = originalCharset_ ;
836                     }
837                 }
838                 if( scriptEncoding == null || scriptEncoding.length() == 0 ){
839                     scriptEncoding = "ISO-8859-1";
840                 }
841                 final byte [] data = webResponse.getResponseBody();
842                 return EncodingUtil.getString(data, 0, data.length, scriptEncoding );
843             }
844             else {
845                 getLog().error("Error loading javascript from ["+url.toExternalForm()
846                     +"] status=["+webResponse.getStatusCode()+" "
847                     +webResponse.getStatusMessage()+"]");
848             }
849         }
850         catch( final MalformedURLException JavaDoc e ) {
851             getLog().error("Unable to build url for script src tag ["+urlString+"]");
852             return "";
853         }
854         catch( final Exception JavaDoc e ) {
855             getLog().error("Error loading javascript from ["+url.toExternalForm()+"]: ", e);
856         }
857         return "";
858     }
859
860     /**
861      * Return the window that this page is sitting inside.
862      *
863      * @return The enclosing frame or null if this page isn't inside a frame.
864      */

865     public WebWindow getEnclosingWindow() {
866         return enclosingWindow_;
867     }
868
869     /**
870      * Set the window that contains this page.
871      *
872      * @param window The new frame or null if this page is being removed from a frame.
873      */

874     public void setEnclosingWindow( final WebWindow window ) {
875         enclosingWindow_ = window;
876     }
877
878     /**
879      * Return the title of this page or an empty string if the title wasn't specified.
880      *
881      * @return the title of this page or an empty string if the title wasn't specified.
882      */

883     public String JavaDoc getTitleText() {
884         final HtmlTitle titleElement = getTitleElement();
885         if (titleElement != null) {
886             return titleElement.asText();
887         }
888         return "";
889     }
890
891     /**
892      * Set the text for the title of this page. If there is not a title element
893      * on this page, then one has to be generated.
894      * @param message The new text
895      */

896     public void setTitleText(final String JavaDoc message) {
897         HtmlTitle titleElement = getTitleElement();
898         if (titleElement == null) {
899             getLog().debug("No title element, creating one");
900             final HtmlHead head = (HtmlHead) getFirstChildElement(getDocumentElement(), HtmlHead.class);
901             if (head == null) {
902                 // perhaps should we create head too?
903
throw new IllegalStateException JavaDoc("Headelement was not defined for this page");
904             }
905             titleElement = new HtmlTitle(this, Collections.EMPTY_MAP);
906             if (head.getFirstChild() != null) {
907                 head.getFirstChild().insertBefore(titleElement);
908             }
909             else {
910                 head.appendChild(titleElement);
911             }
912         }
913
914         titleElement.setNodeValue(message);
915     }
916
917     /**
918      * Get the first child of startElement that is an instance of the given class.
919      * @param startElement The parent element
920      * @param clazz The class to search for.
921      * @return <code>null</code> if no child found
922      */

923     private HtmlElement getFirstChildElement(final HtmlElement startElement, final Class JavaDoc clazz) {
924         final Iterator JavaDoc iterator = startElement.getChildElementsIterator();
925         while (iterator.hasNext()) {
926             final HtmlElement element = (HtmlElement) iterator.next();
927             if (clazz.isInstance(element)) {
928                 return element;
929             }
930         }
931
932         return null;
933     }
934
935     /**
936      * Get the title element for this page. Returns null if one is not found.
937      *
938      * @return the title element for this page or null if this is not one.
939      */

940     private HtmlTitle getTitleElement() {
941         final HtmlHead head = (HtmlHead) getFirstChildElement(getDocumentElement(), HtmlHead.class);
942         if (head != null) {
943             return (HtmlTitle) getFirstChildElement(head, HtmlTitle.class);
944         }
945
946         return null;
947     }
948
949     /**
950      * Look for and execute any appropriate onload handlers. Look for body
951      * and frame tags.
952      */

953     private void executeOnLoadHandlersIfNeeded() {
954         if (!getWebClient().isJavaScriptEnabled()) {
955             return;
956         }
957
958         // onload for the window
959
final Window jsWindow = (Window) getEnclosingWindow().getScriptObject();
960         if (jsWindow != null && jsWindow.jsxGet_onload() != null) {
961             final ScriptEngine engine = getWebClient().getScriptEngine();
962             getLog().debug("Executing onload handler for the window");
963             engine.callFunction(this, jsWindow.jsxGet_onload(), jsWindow, new Object JavaDoc[]{}, null);
964         }
965
966         // the onload of the contained frames or iframe tags
967
final List JavaDoc list = getDocumentElement().getHtmlElementsByTagNames( Arrays.asList( new String JavaDoc[]{
968             "frame", "iframe" } ));
969         for (final Iterator JavaDoc iter = list.iterator(); iter.hasNext();) {
970             final BaseFrame frame = (BaseFrame) iter.next();
971             getLog().debug("Executing onload handler for " + frame);
972             executeOneOnLoadHandler(frame);
973         }
974     }
975
976     /**
977      * Execute a single onload handler. This will either be a string which
978      * will be executed as javascript, or a javascript Function.
979      * @param element The element that contains the onload attribute.
980      */

981     private void executeOneOnLoadHandler(final HtmlElement element) {
982         getLog().debug("Executing onload handler, for " + element);
983         final Function onloadFunction = element.getEventHandler("onload");
984         if (onloadFunction != null) {
985             final ScriptEngine engine = getWebClient().getScriptEngine();
986             engine.callFunction(this, onloadFunction, element.getScriptObject(), new Object JavaDoc[]{}, element);
987         }
988     }
989
990     /**
991      * If a refresh has been specified either through a meta tag or an http
992      * response header, then perform that refresh.
993      * @throws IOException if an IO problem occurs
994      */

995     private void executeRefreshIfNeeded() throws IOException JavaDoc {
996         // If this page is not in a frame then a refresh has already happened,
997
// most likely through the javascript onload handler, so we don't do a
998
// second refresh.
999
final WebWindow window = getEnclosingWindow();
1000        if( window == null ) {
1001            return;
1002        }
1003
1004        final String JavaDoc refreshString = getRefreshStringOrNull();
1005        if( refreshString == null || refreshString.length() == 0 ) {
1006            return;
1007        }
1008
1009        final int time;
1010        final URL JavaDoc url;
1011
1012        int index = refreshString.indexOf( ";" );
1013        boolean timeOnly = ( index == -1 );
1014
1015        if( timeOnly ) {
1016            // Format: <meta http-equiv='refresh' content='10'>
1017
try {
1018                time = Integer.parseInt( refreshString );
1019            }
1020            catch( final NumberFormatException JavaDoc e ) {
1021                getLog().error( "Malformed refresh string (no ';' but not a number): " + refreshString, e );
1022                return;
1023            }
1024            url = originatingUrl_;
1025        }
1026        else {
1027            // Format: <meta http-equiv='refresh' content='10;url=http://www.blah.com'>
1028
time = Integer.parseInt( refreshString.substring( 0, index ) );
1029            index = refreshString.indexOf( "URL=", index );
1030            if( index == -1 ) {
1031                index = refreshString.indexOf( "url=", index );
1032            }
1033            if( index == -1 ) {
1034                getLog().error( "Malformed refresh string (found ';' but no 'url='): " + refreshString );
1035                return;
1036            }
1037            final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc( refreshString.substring( index + 4 ) );
1038            if( buffer.charAt( 0 ) == '"' || buffer.charAt( 0 ) == 0x27 ) {
1039                buffer.deleteCharAt( 0 );
1040            }
1041            if( buffer.charAt( buffer.length() - 1 ) == '"' || buffer.charAt( buffer.length() - 1 ) == 0x27 ) {
1042                buffer.deleteCharAt( buffer.length() - 1 );
1043            }
1044            final String JavaDoc urlString = buffer.toString();
1045            try {
1046                url = getFullyQualifiedUrl( urlString );
1047            }
1048            catch( final MalformedURLException JavaDoc e ) {
1049                getLog().error( "Malformed url in refresh string: " + refreshString, e );
1050                throw e;
1051            }
1052        }
1053
1054        getWebClient().getRefreshHandler().handleRefresh( this, url, time );
1055    }
1056
1057    /**
1058     * Return an auto-refresh string if specified. This will look in both the meta
1059     * tags (taking care of &lt;noscript&gt; if any) and inside the http response headers.
1060     * @return the auto-refresh string.
1061     */

1062    private String JavaDoc getRefreshStringOrNull() {
1063        final Iterator JavaDoc iterator
1064            = getDocumentElement().getHtmlElementsByTagNames( Collections.singletonList("meta") ).iterator();
1065        final boolean javaScriptEnabled = getWebClient().isJavaScriptEnabled();
1066        while( iterator.hasNext() ) {
1067            final HtmlMeta meta = (HtmlMeta) iterator.next();
1068            if( meta.getHttpEquivAttribute().equalsIgnoreCase("refresh")
1069                    && (!javaScriptEnabled || getFirstParent(meta, HtmlNoScript.TAG_NAME) == null)) {
1070                return meta.getContentAttribute();
1071            }
1072        }
1073
1074        return getWebResponse().getResponseHeaderValue("Refresh");
1075    }
1076
1077    /**
1078     * Gets the first parent with the given node name
1079     * @param node the node to start with
1080     * @param nodeName the name of the search node
1081     * @return <code>null</code> if no parent found with this name
1082     */

1083    private DomNode getFirstParent(final DomNode node, final String JavaDoc nodeName) {
1084        DomNode parent = node.getParentNode();
1085        while (parent != null) {
1086            if (parent.getNodeName().equals(nodeName)) {
1087                return parent;
1088            }
1089            parent = parent.getParentNode();
1090        }
1091        return null;
1092    }
1093
1094    /**
1095     * Deregister frames that are no longer in use.
1096     */

1097    public void deregisterFramesIfNeeded() {
1098        for (final Iterator JavaDoc iter = getFrames().iterator(); iter.hasNext();) {
1099            final WebWindow window = (WebWindow) iter.next();
1100            webClient_.deregisterWebWindow( window );
1101            if (window.getEnclosedPage() instanceof HtmlPage) {
1102                final HtmlPage page = (HtmlPage) window.getEnclosedPage();
1103                if (page != null) {
1104                    // seems quite silly, but for instance if the src attribute of an iframe is not
1105
// set, the error only occurs when leaving the page
1106
page.deregisterFramesIfNeeded();
1107                }
1108            }
1109        }
1110    }
1111
1112
1113    /**
1114     * Return a list containing all the frames (from frame and iframe tags) in this page.
1115     * @return a list of {@link BaseFrame.FrameWindow}
1116     */

1117    public List JavaDoc getFrames() {
1118        final List JavaDoc list = new ArrayList JavaDoc();
1119        final WebWindow enclosingWindow = getEnclosingWindow();
1120        for (final Iterator JavaDoc iter = getWebClient().getWebWindows().iterator(); iter.hasNext();)
1121        {
1122            final WebWindow window = (WebWindow) iter.next();
1123            // quite strange but for a TopLevelWindow parent == self
1124
if (enclosingWindow == window.getParentWindow()
1125                    && enclosingWindow != window) {
1126                list.add(window);
1127            }
1128        }
1129        return list;
1130    }
1131
1132    /**
1133     * Returns the first frame contained in this page with the specifed name.
1134     * @param name The name to search for
1135     * @return The first frame found.
1136     * @exception ElementNotFoundException If no frame exist in this page with the specified name.
1137     */

1138    public BaseFrame.FrameWindow getFrameByName(final String JavaDoc name) throws ElementNotFoundException {
1139        final List JavaDoc frames = getFrames();
1140        for (final Iterator JavaDoc iter = frames.iterator(); iter.hasNext();) {
1141            final BaseFrame.FrameWindow frame = (BaseFrame.FrameWindow) iter.next();
1142            if (frame.getName().equals(name)) {
1143                return frame;
1144            }
1145        }
1146
1147        throw new ElementNotFoundException("frame or iframe", "name", name);
1148    }
1149
1150    /**
1151     * Simulate pressing an access key. This may change the focus, may click buttons and may invoke
1152     * javascript.
1153     *
1154     * @param accessKey The key that will be pressed.
1155     * @return The element that has the focus after pressing this access key or null if no element
1156     * has the focus.
1157     * @throws IOException If an io error occurs during the processing of this access key. This
1158     * would only happen if the access key triggered a button which in turn caused a page load.
1159     */

1160    public HtmlElement pressAccessKey( final char accessKey ) throws IOException JavaDoc {
1161        final HtmlElement element = getHtmlElementByAccessKey(accessKey);
1162        final WebClient webClient = getWebClient();
1163        if( element != null ) {
1164            if( element instanceof FocusableElement ) {
1165                ((FocusableElement) element).focus();
1166            }
1167
1168            final Page newPage;
1169            if( element instanceof HtmlAnchor ) {
1170                newPage = ((HtmlAnchor)element).click();
1171            }
1172            else if( element instanceof HtmlArea ) {
1173                newPage = ((HtmlArea)element).click();
1174            }
1175            else if( element instanceof HtmlButton ) {
1176                newPage = ((HtmlButton)element).click();
1177            }
1178            else if( element instanceof HtmlInput ) {
1179                newPage = ((HtmlInput)element).click();
1180            }
1181            else if( element instanceof HtmlLabel ) {
1182                newPage = ((HtmlLabel)element).click();
1183            }
1184            else if( element instanceof HtmlLegend ) {
1185                newPage = ((HtmlLegend)element).click();
1186            }
1187            else if( element instanceof HtmlTextArea ) {
1188                newPage = ((HtmlTextArea)element).click();
1189            }
1190            else {
1191                newPage = this;
1192            }
1193
1194            if( newPage != this && webClient.getElementWithFocus() == element ) {
1195                // The page was reloaded therefore no element on this page will have the focus.
1196
webClient.getElementWithFocus().blur();
1197            }
1198        }
1199
1200        return webClient.getElementWithFocus();
1201    }
1202
1203
1204    /**
1205     * Move the focus to the next element in the tab order. To determine the specified tab
1206     * order, refer to {@link HtmlPage#getTabbableElements()}
1207     *
1208     * @return The element that has focus after calling this method.
1209     */

1210    public HtmlElement tabToNextElement() {
1211        final List JavaDoc elements = getTabbableElements();
1212        final FocusableElement elementWithFocus = getWebClient().getElementWithFocus();
1213        if( elements.isEmpty() ) {
1214            if (elementWithFocus != null) {
1215                elementWithFocus.blur();
1216            }
1217            return null;
1218        }
1219
1220        final HtmlElement elementToGiveFocus;
1221        if( elementWithFocus == null ) {
1222            elementToGiveFocus = (HtmlElement)elements.get(0);
1223        }
1224        else {
1225            final int index = elements.indexOf( elementWithFocus );
1226            if( index == -1 ) {
1227                // The element with focus isn't on this page
1228
elementToGiveFocus = (HtmlElement)elements.get(0);
1229            }
1230            else {
1231                if( index == elements.size() - 1 ) {
1232                    elementToGiveFocus = (HtmlElement)elements.get(0);
1233                }
1234                else {
1235                    elementToGiveFocus = (HtmlElement)elements.get(index+1);
1236                }
1237            }
1238        }
1239
1240        if( elementToGiveFocus instanceof FocusableElement ) {
1241            ((FocusableElement) elementToGiveFocus).focus();
1242        }
1243        return elementToGiveFocus;
1244    }
1245
1246
1247    /**
1248     * Move the focus to the previous element in the tab order. To determine the specified tab
1249     * order, refer to {@link HtmlPage#getTabbableElements()}
1250     *
1251     * @return The element that has focus after calling this method.
1252     */

1253    public HtmlElement tabToPreviousElement() {
1254        final List JavaDoc elements = getTabbableElements();
1255        final FocusableElement elementWithFocus = getWebClient().getElementWithFocus();
1256        if( elements.isEmpty() ) {
1257            if (elementWithFocus != null) {
1258                elementWithFocus.blur();
1259            }
1260            return null;
1261        }
1262
1263        final HtmlElement elementToGiveFocus;
1264        if( elementWithFocus == null ) {
1265            elementToGiveFocus = (HtmlElement)elements.get(elements.size()-1);
1266        }
1267        else {
1268            final int index = elements.indexOf( elementWithFocus );
1269            if( index == -1 ) {
1270                // The element with focus isn't on this page
1271
elementToGiveFocus = (HtmlElement)elements.get(elements.size()-1);
1272            }
1273            else {
1274                if( index == 0 ) {
1275                    elementToGiveFocus = (HtmlElement)elements.get(elements.size()-1);
1276                }
1277                else {
1278                    elementToGiveFocus = (HtmlElement)elements.get(index-1);
1279                }
1280            }
1281        }
1282
1283        if( elementToGiveFocus instanceof FocusableElement ) {
1284            ((FocusableElement) elementToGiveFocus).focus();
1285        }
1286        return elementToGiveFocus;
1287    }
1288
1289    /**
1290     * Return the html element with the specified id. If more than one element
1291     * has this id (not allowed by the html spec) then return the first one.
1292     *
1293     * @param id The id value to search by
1294     * @return The html element found
1295     * @exception ElementNotFoundException If no element was found that matches
1296     * the id
1297     */

1298    public HtmlElement getHtmlElementById( final String JavaDoc id )
1299        throws ElementNotFoundException {
1300
1301        final HtmlElement idElement = (HtmlElement) idMap_.get(id);
1302        if(idElement != null) {
1303            return idElement;
1304        }
1305        throw new ElementNotFoundException( "*", "id", id );
1306    }
1307
1308    /**
1309     * Add an element to the ID map.
1310     *
1311     * @param idElement the element with an ID attribute to add.
1312     */

1313    void addIdElement(final HtmlElement idElement) {
1314        final String JavaDoc id = idElement.getId();
1315        if (!StringUtils.isEmpty(id)) {
1316            idMap_.put(id, idElement);
1317        }
1318    }
1319
1320    /**
1321     * Remove an element from the ID map.
1322     *
1323     * @param idElement the element with an ID attribute to remove.
1324     */

1325    void removeIdElement(final HtmlElement idElement) {
1326        idMap_.remove(idElement.getAttributeValue("id"));
1327    }
1328
1329    private void insertTbodyTagsAsNeeded() {
1330        final Iterator JavaDoc iterator = getDocumentElement().getHtmlElementsByTagName("table").iterator();
1331        while( iterator.hasNext() ) {
1332            final HtmlTable table = (HtmlTable)iterator.next();
1333            DomNode child = table.getFirstChild();
1334            while( child != null && child instanceof HtmlElement == false ) {
1335                child = child.getNextSibling();
1336            }
1337            if( child instanceof HtmlTableRow ) {
1338                final HtmlTableBody body = new HtmlTableBody(this, Collections.EMPTY_MAP);
1339                final List JavaDoc nodesToMove = new ArrayList JavaDoc();
1340                child = table.getFirstChild();
1341                while( child != null ) {
1342                    nodesToMove.add(child);
1343                    child = child.getNextSibling();
1344                }
1345
1346                final Iterator JavaDoc movingIterator = nodesToMove.iterator();
1347                while( movingIterator.hasNext() ) {
1348                    child = (DomNode)movingIterator.next();
1349                    body.appendChild(child);
1350                }
1351                table.appendChild(body);
1352            }
1353        }
1354    }
1355
1356    /**
1357     * Executes the onchange script code for this element if this is appropriate.
1358     * This means that the element must have an onchange script, script must be enabled
1359     * and the change in the element must not have been triggered by a script.
1360     *
1361     * @param htmlElement The element that contains the onchange attribute.
1362     * @return The page that occupies this window after this method completes. It
1363     * may be this or it may be a freshly loaded page.
1364     */

1365    Page executeOnChangeHandlerIfAppropriate(final HtmlElement htmlElement) {
1366        final Function onchange = htmlElement.getEventHandler("onchange");
1367        final ScriptEngine engine = getWebClient().getScriptEngine();
1368        if (onchange != null && getWebClient().isJavaScriptEnabled()
1369                && engine != null && !engine.isScriptRunning()) {
1370
1371            final Event event = new Event(this, getScriptObject());
1372            final Object JavaDoc[] args = new Object JavaDoc[] {event};
1373
1374            final ScriptResult scriptResult =
1375                executeJavaScriptFunctionIfPossible(
1376                        onchange,
1377                        (Scriptable)htmlElement.getScriptObject(),
1378                        args,
1379                        htmlElement);
1380
1381            return scriptResult.getNewPage();
1382        }
1383
1384        return this;
1385    }
1386
1387    /**
1388     * For internal used only
1389     * @param node the node that has just been added to the document.
1390     */

1391    void notifyNodeAdded(final DomNode node) {
1392        if (node instanceof HtmlElement) {
1393            addIdElement((HtmlElement) node);
1394        }
1395        if (node instanceof BaseFrame) {
1396            ((BaseFrame) node).loadInnerPage();
1397        }
1398        if (node instanceof HtmlScript) {
1399            final HtmlScript scriptNode = (HtmlScript) node;
1400            getLog().debug("Script node added: " + scriptNode.asXml());
1401            scriptNode.executeScriptIfNeeded();
1402        }
1403    }
1404
1405    /**
1406     * For internal used only
1407     * @param node the node that has just been removed from the tree
1408     */

1409    void notifyNodeRemoved(final DomNode node) {
1410        if (node instanceof HtmlElement) {
1411            removeIdElement((HtmlElement) node);
1412        }
1413    }
1414    /**
1415     * @see DomNode#asXml()
1416     */

1417    public String JavaDoc asXml() {
1418        return getDocumentElement().asXml();
1419    }
1420}
1421
Popular Tags