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 java.io.Serializable;
041: import java.lang.reflect.InvocationTargetException;
042: import java.lang.reflect.Member;
043: import java.lang.reflect.Method;
044: import java.util.Collections;
045: import java.util.HashMap;
046: import java.util.Iterator;
047: import java.util.Map;
048: import java.util.WeakHashMap;
049:
050: import org.apache.commons.lang.StringUtils;
051: import org.apache.commons.logging.Log;
052: import org.apache.commons.logging.LogFactory;
053: import org.mozilla.javascript.Context;
054: import org.mozilla.javascript.ContextAction;
055: import org.mozilla.javascript.ContextFactory;
056: import org.mozilla.javascript.Function;
057: import org.mozilla.javascript.FunctionObject;
058: import org.mozilla.javascript.Script;
059: import org.mozilla.javascript.Scriptable;
060: import org.mozilla.javascript.ScriptableObject;
061:
062: import com.gargoylesoftware.htmlunit.Assert;
063: import com.gargoylesoftware.htmlunit.BrowserVersion;
064: import com.gargoylesoftware.htmlunit.ScriptException;
065: import com.gargoylesoftware.htmlunit.ScriptPreProcessor;
066: import com.gargoylesoftware.htmlunit.WebClient;
067: import com.gargoylesoftware.htmlunit.WebResponse;
068: import com.gargoylesoftware.htmlunit.WebWindow;
069: import com.gargoylesoftware.htmlunit.html.DomNode;
070: import com.gargoylesoftware.htmlunit.html.HtmlElement;
071: import com.gargoylesoftware.htmlunit.html.HtmlPage;
072: import com.gargoylesoftware.htmlunit.javascript.configuration.ClassConfiguration;
073: import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
074: import com.gargoylesoftware.htmlunit.javascript.host.Element;
075: import com.gargoylesoftware.htmlunit.javascript.host.Window;
076:
077: /**
078: * A wrapper for the <a href="http://www.mozilla.org/rhino">Rhino javascript engine</a>
079: * that provides browser specific features.<br/>
080: * Like all classes in this package, this class is not intended for direct use
081: * and may change without notice.
082: *
083: * @version $Revision: 2158 $
084: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
085: * @author <a href="mailto:chen_jun@users.sourceforge.net">Chen Jun</a>
086: * @author David K. Taylor
087: * @author Chris Erskine
088: * @author <a href="mailto:bcurren@esomnie.com">Ben Curren</a>
089: * @author David D. Kilzer
090: * @author Marc Guillemot
091: * @author Daniel Gredler
092: * @author Ahmed Ashour
093: * @see <a href="http://groups-beta.google.com/group/netscape.public.mozilla.jseng/browse_thread/thread/b4edac57329cf49f/069e9307ec89111f">
094: * Rhino and Java Browser</a>
095: */
096: public class JavaScriptEngine implements Serializable {
097:
098: private static final long serialVersionUID = -5414040051465432088L;
099: private final WebClient webClient_;
100: private static final Log ScriptEngineLog_ = LogFactory
101: .getLog(JavaScriptEngine.class);
102:
103: private static final ThreadLocal javaScriptRunning_ = new ThreadLocal();
104: /**
105: * Cache parsed scripts (only for js files, not for js code embedded in html code)
106: * The WeakHashMap allows cached scripts to be GCed when the WebResponses are not retained
107: * in the {@link com.gargoylesoftware.htmlunit.Cache} anymore.
108: */
109: private final transient Map cachedScripts_ = Collections
110: .synchronizedMap(new WeakHashMap());
111:
112: /**
113: * Key used to place the scope in which the execution of some javascript code
114: * started as thread local attribute in current context.<br/>
115: * This is needed to resolve some relative locations relatively to the page
116: * in which the script is executed and not to the page which location is changed.
117: */
118: public static final String KEY_STARTING_SCOPE = "startingScope";
119:
120: /**
121: * Initialize a context listener so we can count JS contexts and make sure we
122: * are freeing them as necessary.
123: */
124: static {
125: final HtmlUnitContextFactory contextFactory = new HtmlUnitContextFactory(
126: getScriptEngineLog());
127: ContextFactory.initGlobal(contextFactory);
128: }
129:
130: /**
131: * Create an instance for the specified webclient
132: *
133: * @param webClient The webClient that will own this engine.
134: */
135: public JavaScriptEngine(final WebClient webClient) {
136: webClient_ = webClient;
137: }
138:
139: /**
140: * Return the web client that this engine is associated with.
141: * @return The web client.
142: */
143: public final WebClient getWebClient() {
144: return webClient_;
145: }
146:
147: /**
148: * Perform initialization for the given webWindow
149: * @param webWindow the web window to initialize for
150: */
151: public void initialize(final WebWindow webWindow) {
152: Assert.notNull("webWindow", webWindow);
153:
154: final ContextAction action = new ContextAction() {
155: public Object run(final Context cx) {
156: try {
157: init(webWindow, cx);
158: } catch (final Exception e) {
159: getLog()
160: .error(
161: "Exception while initializing JavaScript for the page",
162: e);
163: throw new ScriptException(null, e); // BUG: null is not useful.
164: }
165:
166: return null;
167: }
168: };
169:
170: Context.call(action);
171: }
172:
173: /**
174: * Initializes all the JS stuff for the window
175: * @param webWindow the web window
176: * @param context the current context
177: * @throws Exception if something goes wrong
178: */
179: private void init(final WebWindow webWindow, final Context context)
180: throws Exception {
181: final WebClient webClient = webWindow.getWebClient();
182: final Map prototypes = new HashMap();
183: final Map prototypesPerJSName = new HashMap();
184: final Window window = new Window(this );
185: final JavaScriptConfiguration jsConfig = JavaScriptConfiguration
186: .getInstance(webClient.getBrowserVersion());
187: context.initStandardObjects(window);
188: StringPrimitivePrototypeBugFixer.installWorkaround(window);
189:
190: // put custom object to be called as very last prototype to call the fallback getter (if any)
191: final Scriptable fallbackCaller = new ScriptableObject() {
192: private static final long serialVersionUID = -7124423159070941606L;
193:
194: public Object get(final String name, final Scriptable start) {
195: if (start instanceof ScriptableWithFallbackGetter) {
196: return ((ScriptableWithFallbackGetter) start)
197: .getWithFallback(name);
198: }
199: return NOT_FOUND;
200: }
201:
202: public String getClassName() {
203: return "htmlUnitHelper-fallbackCaller";
204: }
205: };
206: ScriptableObject.getObjectPrototype(window).setPrototype(
207: fallbackCaller);
208:
209: final Iterator it = jsConfig.keySet().iterator();
210: while (it.hasNext()) {
211: final String jsClassName = (String) it.next();
212: final ClassConfiguration config = jsConfig
213: .getClassConfiguration(jsClassName);
214: final boolean isWindow = Window.class.getName().equals(
215: config.getLinkedClass().getName());
216: if (isWindow) {
217: configureConstantsPropertiesAndFunctions(config, window);
218: } else {
219: final Scriptable prototype = configureClass(config,
220: window);
221: if (config.isJsObject()) {
222: prototypes.put(config.getLinkedClass(), prototype);
223:
224: // for FF, place object with prototype property in Window scope
225: if (!getWebClient().getBrowserVersion().isIE()) {
226: final Scriptable obj = (Scriptable) config
227: .getLinkedClass().newInstance();
228: obj.put("prototype", obj, prototype);
229: obj.setPrototype(prototype);
230: obj.setParentScope(window);
231: ScriptableObject.defineProperty(window, config
232: .getClassName(), obj,
233: ScriptableObject.DONTENUM);
234: if (obj.getClass() == Element.class) {
235: final DomNode domNode = new HtmlElement(
236: null, "", (HtmlPage) webWindow
237: .getEnclosedPage(), null) {
238: private static final long serialVersionUID = -5614158965497997095L;
239: };
240: ((SimpleScriptable) obj)
241: .setDomNode(domNode);
242: }
243: }
244: }
245: prototypesPerJSName.put(config.getClassName(),
246: prototype);
247: }
248: }
249:
250: // once all prototypes have been build, it's possible to configure the chains
251: final Scriptable objectPrototype = ScriptableObject
252: .getObjectPrototype(window);
253: for (final Iterator iter = prototypesPerJSName.entrySet()
254: .iterator(); iter.hasNext();) {
255: final Map.Entry entry = (Map.Entry) iter.next();
256: final String name = (String) entry.getKey();
257: final ClassConfiguration config = jsConfig
258: .getClassConfiguration(name);
259: final Scriptable prototype = (Scriptable) entry.getValue();
260: if (!StringUtils.isEmpty(config.getExtendedClass())) {
261: final Scriptable parentPrototype = (Scriptable) prototypesPerJSName
262: .get(config.getExtendedClass());
263: prototype.setPrototype(parentPrototype);
264: } else {
265: prototype.setPrototype(objectPrototype);
266: }
267: }
268:
269: // eval hack (cf unit tests testEvalScopeOtherWindow and testEvalScopeLocal
270: final Class[] evalFnTypes = { String.class };
271: final Member evalFn = Window.class.getMethod("custom_eval",
272: evalFnTypes);
273: final FunctionObject jsCustomEval = new FunctionObject("eval",
274: evalFn, window);
275: window.associateValue("custom_eval", jsCustomEval);
276:
277: for (final Iterator classnames = jsConfig.keySet().iterator(); classnames
278: .hasNext();) {
279: final String jsClassName = (String) classnames.next();
280: final ClassConfiguration config = jsConfig
281: .getClassConfiguration(jsClassName);
282: final Method jsConstructor = config.getJsConstructor();
283: if (jsConstructor != null) {
284: final Scriptable prototype = (Scriptable) prototypesPerJSName
285: .get(jsClassName);
286: if (prototype != null) {
287: final FunctionObject jsCtor = new FunctionObject(
288: jsClassName, jsConstructor, window);
289: jsCtor.addAsConstructor(window, prototype);
290: }
291: }
292: }
293:
294: window.setPrototypes(prototypes);
295: window.initialize(webWindow);
296: }
297:
298: /**
299: * Configures the specified class for access via JavaScript.
300: * @param config The configuration settings for the class to be configured.
301: * @param window The scope within which to configure the class.
302: * @throws InstantiationException If the new class cannot be instantiated
303: * @throws IllegalAccessException If we don't have access to create the new instance.
304: * @throws InvocationTargetException if an exception is thrown during creation of the new object.
305: * @return the created prototype
306: */
307: private Scriptable configureClass(final ClassConfiguration config,
308: final Scriptable window) throws InstantiationException,
309: IllegalAccessException, InvocationTargetException {
310:
311: final Class jsHostClass = config.getLinkedClass();
312: final ScriptableObject prototype = (ScriptableObject) jsHostClass
313: .newInstance();
314: prototype.setParentScope(window);
315:
316: configureConstantsPropertiesAndFunctions(config, prototype);
317:
318: return prototype;
319: }
320:
321: /**
322: * Configure constants, properties and functions on the object
323: * @param config the configuration for the object
324: * @param scriptable the object to configure
325: */
326: private void configureConstantsPropertiesAndFunctions(
327: final ClassConfiguration config,
328: final ScriptableObject scriptable) {
329:
330: // the constants
331: for (final Iterator constantsIterator = config.constants()
332: .iterator(); constantsIterator.hasNext();) {
333: final String constant = (String) constantsIterator.next();
334: final Class linkedClass = config.getLinkedClass();
335: try {
336: final Object value = linkedClass.getField(constant)
337: .get(null);
338: scriptable.defineProperty(constant, value,
339: ScriptableObject.CONST);
340: } catch (final Exception e) {
341: throw Context.reportRuntimeError("Can not get field '"
342: + constant + "' for type: "
343: + config.getClassName());
344: }
345: }
346: // the properties
347: for (final Iterator propertiesIterator = config.propertyKeys()
348: .iterator(); propertiesIterator.hasNext();) {
349: final String entryKey = (String) propertiesIterator.next();
350: final Method readMethod = config
351: .getPropertyReadMethod(entryKey);
352: final Method writeMethod = config
353: .getPropertyWriteMethod(entryKey);
354: scriptable.defineProperty(entryKey, null, readMethod,
355: writeMethod, ScriptableObject.EMPTY);
356: }
357:
358: // the functions
359: for (final Iterator functionsIterator = config.functionKeys()
360: .iterator(); functionsIterator.hasNext();) {
361: final String entryKey = (String) functionsIterator.next();
362: final Method method = config.getFunctionMethod(entryKey);
363: final FunctionObject functionObject = new FunctionObject(
364: entryKey, method, scriptable);
365: scriptable.defineProperty(entryKey, functionObject,
366: ScriptableObject.EMPTY);
367: }
368: }
369:
370: /**
371: * Return the log object for this class
372: * @return The log object
373: */
374: protected Log getLog() {
375: return LogFactory.getLog(getClass());
376: }
377:
378: /**
379: * Compiles the specified javascript code in the context of a given html page.
380: *
381: * @param htmlPage The page that the code will execute within
382: * @param sourceCode The javascript code to execute.
383: * @param sourceName The name that will be displayed on error conditions.
384: * @param startLine the line at which the script source starts
385: * @return The result of executing the specified code.
386: */
387: public Script compile(final HtmlPage htmlPage, String sourceCode,
388: final String sourceName, final int startLine) {
389:
390: Assert.notNull("sourceCode", sourceCode);
391:
392: // Pre process the source code
393: sourceCode = preProcess(htmlPage, sourceCode, sourceName, null);
394:
395: // PreProcess IE Conditional Compilation if needed
396: final BrowserVersion browserVersion = htmlPage.getWebClient()
397: .getBrowserVersion();
398: if (browserVersion.isIE()
399: && browserVersion.getBrowserVersionNumeric() >= 4) {
400: final ScriptPreProcessor ieCCPreProcessor = new IEConditionalCompilationScriptPreProcessor();
401: sourceCode = ieCCPreProcessor.preProcess(htmlPage,
402: sourceCode, sourceName, null);
403: }
404:
405: // Remove HTML comments around the source if needed
406: final String sourceCodeTrimmed = sourceCode.trim();
407: if (sourceCodeTrimmed.startsWith("<!--")) {
408: sourceCode = sourceCode.replaceFirst("<!--", "// <!--");
409: }
410: // IE ignores the last line containing uncommented -->
411: if (getWebClient().getBrowserVersion().isIE()
412: && sourceCodeTrimmed.endsWith("-->")) {
413: final int lastDoubleSlash = sourceCode.lastIndexOf("//");
414: final int lastNewLine = Math.max(sourceCode
415: .lastIndexOf('\n'), sourceCode.lastIndexOf('\r'));
416: if (lastNewLine > lastDoubleSlash) {
417: sourceCode = sourceCode.substring(0, lastNewLine);
418: }
419: }
420:
421: final Scriptable scope = getScope(htmlPage, null);
422: final String source = sourceCode;
423: final ContextAction action = new HtmlUnitContextAction(scope,
424: htmlPage) {
425: public Object doRun(final Context cx) {
426: return cx.compileString(source, sourceName, startLine,
427: null);
428: }
429:
430: protected String getSourceCode(final Context cx) {
431: return source;
432: }
433: };
434:
435: return (Script) Context.call(action);
436: }
437:
438: /**
439: * Execute the specified javascript code in the context of a given html page.
440: *
441: * @param htmlPage The page that the code will execute within
442: * @param sourceCode The javascript code to execute.
443: * @param sourceName The name that will be displayed on error conditions.
444: * @param startLine the line at which the script source starts
445: * @return The result of executing the specified code.
446: */
447: public Object execute(final HtmlPage htmlPage,
448: final String sourceCode, final String sourceName,
449: final int startLine) {
450:
451: final Script script = compile(htmlPage, sourceCode, sourceName,
452: startLine);
453: return execute(htmlPage, script);
454: }
455:
456: /**
457: * Execute the specified javascript code in the context of a given html page.
458: *
459: * @param htmlPage The page that the code will execute within
460: * @param script the script to execute
461: * @return The result of executing the specified code.
462: */
463: public Object execute(final HtmlPage htmlPage, final Script script) {
464:
465: final Scriptable scope = getScope(htmlPage, null);
466:
467: final ContextAction action = new HtmlUnitContextAction(scope,
468: htmlPage) {
469: public Object doRun(final Context cx) {
470: return script.exec(cx, scope);
471: }
472:
473: protected String getSourceCode(final Context cx) {
474: return null;
475: }
476: };
477:
478: return Context.call(action);
479: }
480:
481: /**
482: * Call a JavaScript function and return the result.
483: * @param htmlPage The page
484: * @param javaScriptFunction The function to call.
485: * @param thisObject The this object for class method calls.
486: * @param args The list of arguments to pass to the function.
487: * @param htmlElement The html element that will act as the context.
488: * @return The result of the function call.
489: */
490: public Object callFunction(final HtmlPage htmlPage,
491: final Object javaScriptFunction, final Object this Object,
492: final Object[] args, final DomNode htmlElement) {
493:
494: final Scriptable scope = getScope(htmlPage, htmlElement);
495:
496: final Function function = (Function) javaScriptFunction;
497: final ContextAction action = new HtmlUnitContextAction(scope,
498: htmlPage) {
499: public Object doRun(final Context cx) {
500: return callFunction(htmlPage, function, cx, scope,
501: (Scriptable) this Object, args);
502: }
503:
504: protected String getSourceCode(final Context cx) {
505: return cx.decompileFunction(function, 2);
506: }
507: };
508: return Context.call(action);
509: }
510:
511: private Scriptable getScope(final HtmlPage htmlPage,
512: final DomNode htmlElement) {
513: final Scriptable scope;
514: if (htmlElement != null) {
515: scope = htmlElement.getScriptObject();
516: } else {
517: scope = (Window) htmlPage.getEnclosingWindow()
518: .getScriptObject();
519: }
520: return scope;
521: }
522:
523: /**
524: * Calls the given function taking care of synchronization issues.
525: * @param htmlPage the html page that caused this script to executed
526: * @param function the js function to execute
527: * @param context the context in which execution should occur
528: * @param scope the execution scope
529: * @param thisObject the 'this' object
530: * @param args the function's arguments
531: * @return the function result
532: */
533: public Object callFunction(final HtmlPage htmlPage,
534: final Function function, final Context context,
535: final Scriptable scope, final Scriptable this Object,
536: final Object[] args) {
537:
538: synchronized (htmlPage) // 2 scripts can't be executed in parallel for one page
539: {
540: return function.call(context, scope, this Object, args);
541: }
542: }
543:
544: /**
545: * Indicates if JavaScript is running in current thread. <br/>
546: * This allows code to know if there own evaluation is has been triggered by some JS code.
547: * @return <code>true</code> if JavaScript is running.
548: */
549: public boolean isScriptRunning() {
550: return Boolean.TRUE.equals(javaScriptRunning_.get());
551: }
552:
553: /**
554: * Set the number of milliseconds a script is allowed to execute before
555: * being terminated. A value of 0 or less means no timeout.
556: *
557: * @param timeout the timeout value
558: */
559: public static void setTimeout(final long timeout) {
560: HtmlUnitContextFactory.setTimeout(timeout);
561: }
562:
563: /**
564: * Returns the number of milliseconds a script is allowed to execute before
565: * being terminated. A value of 0 or less means no timeout.
566: *
567: * @return the timeout value
568: */
569: public static long getTimeout() {
570: return HtmlUnitContextFactory.getTimeout();
571: }
572:
573: /**
574: * Facility for ContextAction usage.
575: * ContextAction should be preferred because according to Rhino doc it
576: * "guarantees proper association of Context instances with the current thread and is faster".
577: */
578: private abstract class HtmlUnitContextAction implements
579: ContextAction {
580: private final Scriptable scope_;
581: private final HtmlPage htmlPage_;
582:
583: public HtmlUnitContextAction(final Scriptable scope,
584: final HtmlPage htmlPage) {
585: scope_ = scope;
586: htmlPage_ = htmlPage;
587: }
588:
589: public final Object run(final Context cx) {
590: final Boolean javaScriptAlreadyRunning = (Boolean) javaScriptRunning_
591: .get();
592: javaScriptRunning_.set(Boolean.TRUE);
593:
594: try {
595: cx.putThreadLocal(KEY_STARTING_SCOPE, scope_);
596: synchronized (htmlPage_) // 2 scripts can't be executed in parallel for one page
597: {
598: return doRun(cx);
599: }
600: } catch (final Exception e) {
601: final ScriptException scriptException = new ScriptException(
602: htmlPage_, e, getSourceCode(cx));
603: if (getWebClient().isThrowExceptionOnScriptError()) {
604: throw scriptException;
605: } else {
606: // use a ScriptException to log it because it provides good information
607: // on the source code
608: getLog().info("Caught script exception",
609: scriptException);
610: return null;
611: }
612: } catch (final TimeoutError e) {
613: if (getWebClient().isThrowExceptionOnScriptError()) {
614: throw new RuntimeException(e);
615: } else {
616: getLog().info("Caught script timeout error", e);
617: return null;
618: }
619: } finally {
620: javaScriptRunning_.set(javaScriptAlreadyRunning);
621: }
622: }
623:
624: protected abstract Object doRun(final Context cx);
625:
626: protected abstract String getSourceCode(final Context cx);
627: }
628:
629: /**
630: * Return the log object that is being used to log information about the script engine.
631: * @return The log
632: */
633: public static Log getScriptEngineLog() {
634: return ScriptEngineLog_;
635: }
636:
637: /**
638: * Pre process the specified source code in the context of the given page using the processor specified
639: * in the webclient. This method delegates to the pre processor handler specified in the
640: * <code>WebClient</code>. If no pre processor handler is defined, the original source code is returned
641: * unchanged.
642: * @param htmlPage The page
643: * @param sourceCode The code to process.
644: * @param sourceName A name for the chunk of code. This will be used in error messages.
645: * @param htmlElement The html element that will act as the context.
646: * @return The source code after being pre processed
647: * @see com.gargoylesoftware.htmlunit.ScriptPreProcessor
648: */
649: public String preProcess(final HtmlPage htmlPage,
650: final String sourceCode, final String sourceName,
651: final HtmlElement htmlElement) {
652:
653: String newSourceCode = sourceCode;
654: final ScriptPreProcessor preProcessor = getWebClient()
655: .getScriptPreProcessor();
656: if (preProcessor != null) {
657: newSourceCode = preProcessor.preProcess(htmlPage,
658: sourceCode, sourceName, htmlElement);
659: if (newSourceCode == null) {
660: newSourceCode = "";
661: }
662: }
663: return newSourceCode;
664: }
665:
666: /**
667: * Get the cached script for the given response.
668: * @param webResponse the response corresponding to the script code
669: * @return the parsed script
670: */
671: public Script getCachedScript(final WebResponse webResponse) {
672: return (Script) cachedScripts_.get(webResponse);
673: }
674:
675: /**
676: * Cache a parsed script
677: * @param webResponse the response corresponding to the script code. A weak reference to this object
678: * will be used as key for the cache.
679: * @param script the parsed script to cache
680: */
681: public void cacheScript(final WebResponse webResponse,
682: final Script script) {
683: cachedScripts_.put(webResponse, script);
684: }
685: }
|