KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > javascript > JavaScriptEngine


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.javascript;
39
40 import com.gargoylesoftware.htmlunit.Assert;
41 import com.gargoylesoftware.htmlunit.ScriptEngine;
42 import com.gargoylesoftware.htmlunit.ScriptException;
43 import com.gargoylesoftware.htmlunit.WebClient;
44 import com.gargoylesoftware.htmlunit.html.HtmlElement;
45 import com.gargoylesoftware.htmlunit.html.HtmlPage;
46 import com.gargoylesoftware.htmlunit.javascript.configuration.ClassConfiguration;
47 import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
48 import com.gargoylesoftware.htmlunit.javascript.host.Window;
49
50 import java.util.Iterator JavaDoc;
51 import java.util.WeakHashMap JavaDoc;
52 import java.util.Map JavaDoc;
53 import java.lang.ref.WeakReference JavaDoc;
54
55 import org.apache.commons.logging.Log;
56 import org.apache.commons.logging.LogFactory;
57 import org.mozilla.javascript.Context;
58 import org.mozilla.javascript.Function;
59 import org.mozilla.javascript.FunctionObject;
60 import org.mozilla.javascript.Scriptable;
61 import org.mozilla.javascript.ScriptableObject;
62
63 /**
64  * A wrapper for the <a HREF="http://www.mozilla.org/rhino">Rhino javascript engine</a>
65  * that provides browser specific features.
66  *
67  * @version $Revision: 100 $
68  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
69  * @author <a HREF="mailto:chen_jun@users.sourceforge.net">Chen Jun</a>
70  * @author David K. Taylor
71  * @author Chris Erskine
72  * @author <a HREF="mailto:bcurren@esomnie.com">Ben Curren</a>
73  * @author David D. Kilzer
74  * @author Marc Guillemot
75  */

76 public final class JavaScriptEngine extends ScriptEngine {
77
78     /** Information specific to the javascript engine */
79     public static final class PageInfo {
80
81         /**
82          * The javascript.getContext()
83          * This is probably wrong to hold a reference to the context: it should be
84          * entered when necessary and exited after, therefore there is some work
85          * to do in this area.
86          **/

87         private Context context_;
88
89         /** The javascript.getScope() */
90         private Window scope_;
91
92         /**
93          * Return the window scope
94          * @return the window
95          */

96         public Window getScope() {
97             return scope_;
98         }
99     }
100
101
102     /**
103      * Map where keys are {@link HtmlPage}s and values are {@link PageInfo}s
104      */

105     private final Map JavaDoc pageInfos_ = new WeakHashMap JavaDoc(89);
106
107     private static final ThreadLocal JavaDoc WEB_CLIENTS = new ThreadLocal JavaDoc();
108     
109     private static final ThreadLocal JavaDoc javaScriptRunning_ = new ThreadLocal JavaDoc();
110
111     /**
112      * Create an instance for the specified webclient
113      *
114      * @param webClient The webClient that will own this engine.
115      */

116     public JavaScriptEngine( final WebClient webClient ) {
117         super( webClient );
118     }
119
120     /**
121      * perform initialization for the given page
122      * @param page the page to initialize for
123      */

124     public void initialize(final HtmlPage page) {
125         //force allocation of the page info.
126
try {
127             getPageInfo(page);
128         }
129         catch (final RuntimeException JavaDoc e) {
130             // usefull for debugging (else catched Xerces and nested in a XNIException
131
getLog().error("Exception while initializing JavaScript for the page", e);
132         }
133     }
134
135
136     private PageInfo getPageInfo( final HtmlPage htmlPage ) {
137         Assert.notNull( "htmlPage", htmlPage );
138
139         final WeakReference JavaDoc weakReference = (WeakReference JavaDoc)pageInfos_.get(htmlPage);
140         if( weakReference != null ) {
141             final PageInfo existingPageInfo = (PageInfo)weakReference.get();
142             if( existingPageInfo != null ) {
143                 return existingPageInfo;
144             }
145         }
146         final WebClient webClient = htmlPage.getWebClient();
147         try {
148             WEB_CLIENTS.set(webClient);
149             
150             final Context context = Context.enter();
151             context.setOptimizationLevel(-1);
152             context.setErrorReporter(new StrictErrorReporter(getScriptEngineLog()));
153             final Scriptable parentScope = context.initStandardObjects(null);
154
155             final JavaScriptConfiguration jsConfig = JavaScriptConfiguration.getInstance(webClient.getBrowserVersion());
156             final Iterator JavaDoc it = jsConfig.keySet().iterator();
157             while (it.hasNext()) {
158                 final String JavaDoc jsClassName = (String JavaDoc) it.next();
159                 final ClassConfiguration config = jsConfig.getClassConfiguration(jsClassName);
160                 if (config.isJsObject()) {
161                     final Class JavaDoc jsHostClass = config.getLinkedClass();
162                     ScriptableObject.defineClass(parentScope, jsHostClass);
163                     final ScriptableObject prototype = (ScriptableObject) ScriptableObject
164                         .getClassPrototype(parentScope, jsClassName);
165   
166                     final Iterator JavaDoc propertiesIterator = config.propertyKeys().iterator();
167                     while (propertiesIterator.hasNext()) {
168                         final String JavaDoc entryKey = (String JavaDoc) propertiesIterator.next();
169                         prototype.defineProperty(entryKey, null, config.getPropertyReadMethod(entryKey),
170                             config.getPropertyWriteMethod(entryKey), 0);
171                     }
172
173                     final Iterator JavaDoc functionsIterator = config.functionKeys().iterator();
174                     while (functionsIterator.hasNext()) {
175                         final String JavaDoc entryKey = (String JavaDoc) functionsIterator.next();
176                         final FunctionObject functionObject = new FunctionObject(entryKey,
177                                 config.getFunctionMethod(entryKey), prototype);
178                         prototype.defineProperty(entryKey, functionObject, 0);
179                     }
180                         
181                 }
182             }
183             
184             ScriptableObject.defineClass(parentScope, ElementArray.class);
185             ScriptableObject.defineClass(parentScope, OptionsArray.class);
186
187             final Window window = (Window) context.newObject(
188                 parentScope, "Window", new Object JavaDoc[0]);
189
190             final PageInfo newPageInfo = new PageInfo();
191             newPageInfo.context_ = context;
192             newPageInfo.scope_ = window;
193             window.setPageInfo(newPageInfo);
194             window.initialize(htmlPage);
195
196             pageInfos_.put( htmlPage, new WeakReference JavaDoc(newPageInfo) );
197
198             return newPageInfo;
199         }
200         catch( final Exception JavaDoc e ) {
201             throw new ScriptException(e);
202         }
203 // Context.exit();
204
}
205
206
207
208     /**
209      * Return the log object for this class
210      * @return The log object
211      */

212     protected Log getLog() {
213         return LogFactory.getLog(getClass());
214     }
215
216     /**
217      * Determine the scope for the page and element.
218      * @param pageInfo The page info
219      * @param htmlElementScope The element that will be used as context or null if
220      * the page should be used as context.
221      * @return The JavaScript execution scope.
222      */

223     private Scriptable getScope(
224         final PageInfo pageInfo, final HtmlElement htmlElementScope ) {
225
226         Scriptable scope;
227         if( htmlElementScope == null ) {
228             scope = pageInfo.getScope();
229         }
230         else {
231             scope = (Scriptable)htmlElementScope.getScriptObject();
232             scope.setParentScope(pageInfo.getScope());
233         }
234         return scope;
235     }
236
237
238     /**
239      * Execute the specified javascript code in the context of a given html page.
240      *
241      * @param htmlPage The page that the code will execute within
242      * @param sourceCode The javascript code to execute.
243      * @param sourceName The name that will be displayed on error conditions
244      * @param htmlElementScope The element that will be used as context or null if
245      * the page should be used as context.
246      * @return The result of executing the specified code.
247      */

248     public Object JavaDoc execute(
249         final HtmlPage htmlPage, String JavaDoc sourceCode, final String JavaDoc sourceName, final HtmlElement htmlElementScope ) {
250
251         Assert.notNull( "sourceCode", sourceCode );
252
253         // Pre process the source code
254
sourceCode = preProcess(htmlPage, sourceCode, sourceName, htmlElementScope);
255
256         // Remove html comments around the source if needed
257
sourceCode = sourceCode.trim();
258         if( sourceCode.startsWith("<!--") ) {
259             int startIndex = 4;
260
261             final int endIndex;
262             if( sourceCode.endsWith("-->") ) {
263                 endIndex = sourceCode.length()-3;
264             }
265             else {
266                 endIndex = sourceCode.length();
267             }
268
269             // Anything on the same line as the opening comment should be ignored
270
char eachChar = sourceCode.charAt(startIndex);
271             while( startIndex < endIndex && eachChar != '\n' && eachChar != '\r' ) {
272                 eachChar = sourceCode.charAt( ++startIndex );
273             }
274
275             sourceCode = sourceCode.substring(startIndex, endIndex);
276         }
277
278         final PageInfo pageInfo = getPageInfo(htmlPage);
279
280         final int lineNumber = 1;
281         final Object JavaDoc securityDomain = null;
282
283         final Scriptable scope = getScope( pageInfo, htmlElementScope );
284
285         final Boolean JavaDoc javaScriptAlreadyRunning = (Boolean JavaDoc) javaScriptRunning_.get();
286         javaScriptRunning_.set(Boolean.TRUE);
287         try {
288             final Object JavaDoc result = pageInfo.context_.evaluateString(
289                     scope, sourceCode, sourceName, lineNumber, securityDomain );
290             return result;
291         }
292         catch (final Exception JavaDoc e ) {
293             final ScriptException scriptException = new ScriptException( e, sourceCode );
294             if (getWebClient().isThrowExceptionOnScriptError()) {
295                 throw scriptException;
296             }
297             else {
298                 // use a ScriptException to log it because it provides good information
299
// on the source code
300
getLog().info("Catched script exception", scriptException);
301                 return null;
302             }
303         }
304         finally {
305             javaScriptRunning_.set(javaScriptAlreadyRunning);
306         }
307     }
308
309
310     /**
311      * Call a JavaScript function and return the result.
312      * @param htmlPage The page
313      * @param javaScriptFunction The function to call.
314      * @param thisObject The this object for class method calls.
315      * @param args The list of arguments to pass to the function.
316      * @param htmlElementScope The html element that will act as the context.
317      * @return The result of the function call.
318      */

319     public Object JavaDoc callFunction(
320             final HtmlPage htmlPage,
321             final Object JavaDoc javaScriptFunction,
322             final Object JavaDoc thisObject,
323             final Object JavaDoc [] args,
324             final HtmlElement htmlElementScope ) {
325
326         final PageInfo pageInfo = getPageInfo(htmlPage);
327
328         final Scriptable scope = getScope( pageInfo, htmlElementScope );
329         // some js code (like onchange handlers) should not be triggered from JS code:
330
// => keep trace of JS running or not
331
final Boolean JavaDoc javaScriptAlreadyRunning = (Boolean JavaDoc) javaScriptRunning_.get();
332         javaScriptRunning_.set(Boolean.TRUE);
333         final Function function = (Function) javaScriptFunction;
334         try {
335             final Object JavaDoc result = function.call( pageInfo.context_,
336                 scope, (Scriptable) thisObject, args );
337             return result;
338         }
339         catch (final Exception JavaDoc e ) {
340             final ScriptException scriptException = new ScriptException( e,
341                     pageInfo.context_.decompileFunction(function, 2));
342             if (getWebClient().isThrowExceptionOnScriptError()) {
343                 throw scriptException;
344             }
345             else {
346                 // use a ScriptException to log it because it provides good information
347
// on the source code
348
getLog().info("Catched script exception", scriptException);
349                 return null;
350             }
351         }
352         finally {
353             javaScriptRunning_.set(javaScriptAlreadyRunning);
354         }
355     }
356
357
358     /**
359      * Return the string representation of the JavaScript object in the context of the given page.
360      * @param htmlPage The page
361      * @param javaScriptObject The object to represent at a string.
362      * @return The result string.
363      */

364     public String JavaDoc toString( final HtmlPage htmlPage, final Object JavaDoc javaScriptObject ) {
365
366         getPageInfo(htmlPage);
367
368         final String JavaDoc result = Context.toString( javaScriptObject );
369         return result;
370     }
371
372     /**
373      * Return the WebClient that is executing on the current thread. If no client
374      * can be found then return null.
375      * @return The web client.
376      */

377     public static WebClient getWebClientForCurrentThread() {
378         return (WebClient)WEB_CLIENTS.get();
379     }
380     
381     /**
382      * Indicates if JavaScript is running in current thread. <br/>
383      * This allows code to know if there own evaluation is has been triggered by some JS code.
384      * @return <code>true</code> if JavaScript is running.
385      */

386     public boolean isScriptRunning() {
387         return Boolean.TRUE.equals(javaScriptRunning_.get());
388     }
389 }
390
391
Popular Tags