001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit.javascript;
039:
040: import org.apache.commons.collections.Transformer;
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043: import org.mozilla.javascript.Context;
044: import org.mozilla.javascript.Scriptable;
045: import org.mozilla.javascript.ScriptableObject;
046:
047: import com.gargoylesoftware.htmlunit.Assert;
048: import com.gargoylesoftware.htmlunit.WebWindow;
049: import com.gargoylesoftware.htmlunit.html.DomNode;
050: import com.gargoylesoftware.htmlunit.html.HtmlElement;
051: import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
052: import com.gargoylesoftware.htmlunit.javascript.host.HTMLElement;
053: import com.gargoylesoftware.htmlunit.javascript.host.Window;
054:
055: /**
056: * A javascript object for a Location
057: *
058: * @version $Revision: 2132 $
059: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
060: * @author David K. Taylor
061: * @author Marc Guillemot
062: * @author Chris Erskine
063: * @author Daniel Gredler
064: * @author Ahmed Ashour
065: */
066: public class SimpleScriptable extends ScriptableObject {
067: private static final long serialVersionUID = 3120000176890886780L;
068:
069: private DomNode domNode_;
070:
071: /**
072: * Get a named property from the object.
073: * Normally HtmlUnit objects don't need to overwrite this method as properties are defined
074: * on the prototypes from the xml configuration. In some cases where "content" of object
075: * has priority compared to the properties consider using utility {@link #getWithPreemption(String)}.
076: * For fallback case just implement {@link ScriptableWithFallbackGetter}.
077: * {@inheritDoc}
078: */
079: public Object get(final String name, final Scriptable start) {
080: // try to get property configured on object itself
081: final Object response = super .get(name, start);
082: if (response != NOT_FOUND) {
083: return response;
084: }
085:
086: if (this == start) {
087: return getWithPreemption(name);
088: }
089: return NOT_FOUND;
090: }
091:
092: /**
093: * <p>Called by {@link #get(String, Scriptable)} to allow retrieval of the property before the prototype
094: * chain is searched.</p>
095: *
096: * <p>IMPORTANT: This method is invoked *very* often by Rhino. If you override this method, the implementation
097: * needs to be as fast as possible!</p>
098: *
099: * @param name the property name
100: * @return {@link Scriptable#NOT_FOUND} if not found
101: */
102: protected Object getWithPreemption(final String name) {
103: return NOT_FOUND;
104: }
105:
106: /**
107: * Return the javascript class name
108: * @return The javascript class name
109: */
110: public String getClassName() {
111: final String javaClassName = getClass().getName();
112: final int index = javaClassName.lastIndexOf(".");
113: if (index == -1) {
114: throw new IllegalStateException("No dot in classname: "
115: + javaClassName);
116: }
117:
118: return javaClassName.substring(index + 1);
119: }
120:
121: /**
122: * Return the DOM node that corresponds to this javascript object or throw
123: * an exception if one cannot be found.
124: * @return The DOM node
125: * @exception IllegalStateException If the DOM node could not be found.
126: */
127: public final DomNode getDomNodeOrDie() throws IllegalStateException {
128: if (domNode_ == null) {
129: final String clazz = getClass().getName();
130: throw new IllegalStateException(
131: "DomNode has not been set for this SimpleScriptable: "
132: + clazz);
133: } else {
134: return domNode_;
135: }
136: }
137:
138: /**
139: * Return the DOM node that corresponds to this javascript object
140: * or null if a node hasn't been set.
141: * @return The DOM node or null
142: */
143: public final DomNode getDomNodeOrNull() {
144: return domNode_;
145: }
146:
147: /**
148: * Set the DOM node that corresponds to this javascript object
149: * @param domNode The DOM node
150: */
151: public void setDomNode(final DomNode domNode) {
152: setDomNode(domNode, true);
153: }
154:
155: /**
156: * Set the DOM node that corresponds to this javascript object
157: * @param domNode The DOM node
158: * @param assignScriptObject If true, call <code>setScriptObject</code> on domNode
159: */
160: protected void setDomNode(final DomNode domNode,
161: final boolean assignScriptObject) {
162: Assert.notNull("domNode", domNode);
163: domNode_ = domNode;
164: if (assignScriptObject) {
165: domNode_.setScriptObject(this );
166: }
167: }
168:
169: /**
170: * Set the html element that corresponds to this javascript object
171: * @param htmlElement The html element
172: */
173: public void setHtmlElement(final HtmlElement htmlElement) {
174: setDomNode(htmlElement);
175: }
176:
177: /**
178: * Return the log that is being used for all scripting objects
179: * @return The log.
180: */
181: protected final Log getLog() {
182: return LogFactory.getLog(getClass());
183: }
184:
185: /**
186: * Return the javascript object that corresponds to the specified object.
187: * New javascript objects will be created as needed. If a javascript object
188: * cannot be created for a domNode then NOT_FOUND will be returned.
189: *
190: * @param object a {@link DomNode} or a {@link WebWindow}
191: * @return The javascript object or NOT_FOUND
192: */
193: protected SimpleScriptable getScriptableFor(final Object object) {
194: if (object instanceof WebWindow) {
195: return (SimpleScriptable) ((WebWindow) object)
196: .getScriptObject();
197: }
198:
199: final DomNode domNode = (DomNode) object;
200:
201: final Object scriptObject = domNode.getScriptObject();
202: if (scriptObject != null) {
203: return (SimpleScriptable) scriptObject;
204: } else {
205: return makeScriptableFor(domNode);
206: }
207: }
208:
209: /**
210: * Builds a new the javascript object that corresponds to the specified object.<br>
211: * @param domNode the dom node for which a JS object should be created
212: * @return The javascript object
213: */
214: public SimpleScriptable makeScriptableFor(final DomNode domNode) {
215:
216: // Get the JS class name for the specified DOM node.
217: // Walk up the inheritance chain if necessary.
218: Class javaScriptClass = null;
219: for (Class c = domNode.getClass(); javaScriptClass == null
220: && c != null; c = c.getSuperclass()) {
221: javaScriptClass = (Class) JavaScriptConfiguration
222: .getHtmlJavaScriptMapping().get(c);
223: }
224:
225: final SimpleScriptable scriptable;
226: if (javaScriptClass == null) {
227: // We don't have a specific subclass for this element so create something generic.
228: scriptable = new HTMLElement();
229: getLog().debug(
230: "No javascript class found for element <"
231: + domNode.getNodeName()
232: + ">. Using HTMLElement");
233: } else {
234: try {
235: scriptable = (SimpleScriptable) javaScriptClass
236: .newInstance();
237: } catch (final Exception e) {
238: throw Context.throwAsScriptRuntimeEx(e);
239: }
240: }
241: // parent scope needs to be set to the enclosing "window" (no simple unit test found to illustrate the
242: // necessity) if navigation has continued, the window may contain an other page as the one to which
243: // the current node belongs to.
244: // See test JavaScriptEngineTest#testScopeInInactivePage
245: if (domNode.getPage().getEnclosingWindow().getEnclosedPage() == domNode
246: .getPage()) {
247: scriptable.setParentScope(getWindow());
248: } else {
249: scriptable.setParentScope(ScriptableObject
250: .getTopLevelScope(domNode.getPage()
251: .getScriptObject()));
252: }
253:
254: scriptable.setPrototype(getPrototype(javaScriptClass));
255: scriptable.setDomNode(domNode);
256:
257: return scriptable;
258: }
259:
260: /**
261: * Get the prototype object for the given host class
262: * @param javaScriptClass the host class
263: * @return the prototype
264: */
265: protected Scriptable getPrototype(final Class javaScriptClass) {
266: return getWindow().getPrototype(javaScriptClass);
267: }
268:
269: /**
270: * Gets a transformer getting the scriptable element for an HtmlElement
271: * @return the transformer.
272: */
273: protected Transformer getTransformerScriptableFor() {
274: return new Transformer() {
275: public Object transform(final Object obj) {
276: return getScriptableFor(obj);
277: }
278: };
279: }
280:
281: /**
282: * Return the value at the specified location in the argument list. If the index is larger
283: * than the argument array then return the default value.
284: *
285: * @param index The index into the argument list.
286: * @param args The argument list.
287: * @param defaultValue The default value to return if the arg wasn't specified.
288: * @return The specified object or null
289: */
290: public static Object getObjectArg(final int index,
291: final Object[] args, final Object defaultValue) {
292: if (index >= args.length) {
293: return defaultValue;
294: } else {
295: return args[index];
296: }
297: }
298:
299: /**
300: * Return the string value at the specified location in the argument list. If the index is larger
301: * than the argument array then return the default value.
302: *
303: * @param index The index into the argument list.
304: * @param args The argument list.
305: * @param defaultValue The default value to return if the arg wasn't specified.
306: * @return The specified string or null
307: */
308: public static String getStringArg(final int index,
309: final Object[] args, final String defaultValue) {
310: return Context
311: .toString(getObjectArg(index, args, defaultValue));
312: }
313:
314: /**
315: * Return the boolean value at the specified location in the argument list. If the index is larger
316: * than the argument array then return the default value.
317: *
318: * @param index The index into the argument list.
319: * @param args The argument list.
320: * @param defaultValue The default value to be used.
321: * @return The specified boolean or the default value.
322: */
323: public static boolean getBooleanArg(final int index,
324: final Object[] args, final boolean defaultValue) {
325: final Boolean defaultBoolean = Boolean.valueOf(defaultValue);
326:
327: return Context.toBoolean(getObjectArg(index, args,
328: defaultBoolean));
329: }
330:
331: /**
332: * Return the int value at the specified location in the argument list. If the index is larger
333: * than the argument array then return the default value.
334: *
335: * @param index The index into the argument list.
336: * @param args The argument list.
337: * @param defaultValue The default value to be used.
338: * @return The specified int or the default value.
339: */
340: public static int getIntArg(final int index, final Object[] args,
341: final int defaultValue) {
342: return (int) Context.toNumber(getObjectArg(index, args,
343: new Integer(defaultValue)));
344: }
345:
346: /**
347: * Return the javascript default value of this object. This is the javascript equivalent
348: * of a toString() in java.
349: *
350: * @param hint A hint as to the format of the default value. Ignored in this case.
351: * @return The default value.
352: */
353: public Object getDefaultValue(final Class hint) {
354: if (String.class.equals(hint) || hint == null) {
355: if (getWindow().getWebWindow().getWebClient()
356: .getBrowserVersion().isIE()) {
357: return "[object]"; // the super helpful IE solution
358: } else {
359: return "[object " + getClassName() + "]"; // not fully correct as htmlunit names are not FF ones
360: }
361: } else {
362: return super .getDefaultValue(hint);
363: }
364: }
365:
366: /**
367: * Gets the window that is the top scope for this object.
368: * @return The window associated with this object.
369: * @throws RuntimeException If the window cannot be found, which should never occur.
370: */
371: protected Window getWindow() throws RuntimeException {
372: return getWindow(this );
373: }
374:
375: /**
376: * Gets the window that is the top scope for the specified object.
377: * @param s The JavaScript object whose associated window is to be returned.
378: * @return The window associated with the specified JavaScript object.
379: * @throws RuntimeException If the window cannot be found, which should never occur.
380: */
381: protected static Window getWindow(final Scriptable s)
382: throws RuntimeException {
383: final Scriptable top = ScriptableObject.getTopLevelScope(s);
384: if (top instanceof Window) {
385: return (Window) top;
386: } else {
387: throw new RuntimeException(
388: "Unable to find window associated with " + s);
389: }
390: }
391:
392: /**
393: * Gets the scriptable used at starting scope for the execution of current script.
394: * @return the scope as defined in {@link JavaScriptEngine#callFunction}
395: * or {@link JavaScriptEngine#execute}.
396: */
397: protected Scriptable getStartingScope() {
398: return (Scriptable) Context.getCurrentContext().getThreadLocal(
399: JavaScriptEngine.KEY_STARTING_SCOPE);
400: }
401: }
|