/*
* Copyright (c) 2002-2008 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gargoylesoftware.htmlunit.javascript;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.httpclient.NameValuePair;
import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.CollectingAlertHandler;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.MockWebConnection;
import com.gargoylesoftware.htmlunit.ScriptException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.WebTestCase;
import com.gargoylesoftware.htmlunit.html.ClickableElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlButtonInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlFrame;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlScript;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
/**
* Tests for the {@link JavaScriptEngine}.
*
* @version $Revision$
* @author Mike Bowler
* @author Noboru Sinohara
* @author Darrell DeBoer
* @author Ben Curren
* @author Marc Guillemot
* @author Chris Erskine
* @author David K. Taylor
* @author Ahmed Ashour
*/
public class JavaScriptEngineTest extends WebTestCase {
/**
* @throws Exception if the test fails
*/
@Test
public void setJavascriptEnabled_false() throws Exception {
final WebClient client = new WebClient();
client.setJavaScriptEnabled(false);
final MockWebConnection webConnection = new MockWebConnection(client);
final String content
= "
foo\n"
+ "
hello world
\n"
+ "\n"
+ "";
webConnection.setDefaultResponse(content);
client.setWebConnection(webConnection);
final HtmlPage page = (HtmlPage) client.getPage(URL_GARGOYLE);
final HtmlTextInput textInput = (HtmlTextInput) page.getHtmlElementById("textfield1");
assertEquals("foo", textInput.getValueAttribute());
}
/**
* Regression test for bug https://sf.net/tracker/?func=detail&atid=448266&aid=1609944&group_id=47038.
* @throws Exception if the test fails
*/
@Test
public void onloadJavascriptFunction() throws Exception {
final String content
= "foo\n"
+ "";
final String[] expectedAlerts = {"foo"};
createTestPageForRealBrowserIfNeeded(content, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(BrowserVersion.FIREFOX_2, content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Tries to set the value of a text input field.
* @throws Exception if the test fails
*/
@Test
public void setInputValue() throws Exception {
final String content
= "foo\n"
+ "
hello world
\n"
+ "\n"
+ "";
final List collectedAlerts = null;
final HtmlPage page = loadPage(content, collectedAlerts);
final HtmlTextInput textInput = (HtmlTextInput) page.getHtmlElementById("textfield1");
assertEquals("blue", textInput.getValueAttribute());
}
/**
* @throws Exception if the test fails
*/
@Test
public void alert() throws Exception {
final String content
= "foo\n"
+ "
hello world
\n"
+ "\n"
+ "";
final List collectedAlerts = new ArrayList();
final HtmlPage page = loadPage(content, collectedAlerts);
assertEquals("foo", page.getTitleText());
final String[] expectedAlerts = {"foo"};
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Checks that a dynamically compiled function works in the scope of its birth.
* @throws Exception if the test fails
*/
@Test
public void scopeOfNewFunction() throws Exception {
final String content
= "\n"
+ "";
final String[] expectedAlerts = {"foo"};
createTestPageForRealBrowserIfNeeded(content, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void scopeOfNestedNewFunction() throws Exception {
final String[] expectedAlerts = {"foo"};
final String content
= "\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
createTestPageForRealBrowserIfNeeded(content, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Checks that a dynamically compiled function works in the scope of its birth and not the other window.
* @throws Exception if the test fails
*/
@Test
public void scopeOfNewFunctionCalledFormOtherWindow() throws Exception {
final String firstContent
= "\n"
+ "\n"
+ "\n"
+ "\n"
+ " \n"
+ "\n"
+ "";
final String secondContent = "";
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse(secondContent);
webConnection.setResponse(URL_FIRST, firstContent);
client.setWebConnection(webConnection);
final String[] expectedAlerts = {"foo", "foo", "foo"};
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
client.getPage(URL_FIRST);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* If a reference has been hold on a page and the page is not
* anymore the one contained in "its" window, JavaScript execution should
* work... a bit
* @throws Exception if the test fails
*/
@Test
public void scopeInInactivePage() throws Exception {
final String firstContent
= "\n"
+ "\n"
+ "\n"
+ "\n"
+ " to page 2\n"
+ "
foo
\n"
+ "\n"
+ "";
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse("");
webConnection.setResponse(URL_FIRST, firstContent);
client.setWebConnection(webConnection);
final String[] expectedAlerts = {"foo"};
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final HtmlPage page = (HtmlPage) client.getPage(URL_FIRST);
final ClickableElement div = ((ClickableElement) page.getHtmlElementById("testdiv"));
page.getAnchors().get(0).click();
// ignore response, and click in the page again
div.click();
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void externalScript() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String htmlContent
= "foo\n"
+ "\n"
+ "
hello world
\n"
+ "\n"
+ "";
final String jsContent = "alert('got here');\n";
webConnection.setResponse(URL_GARGOYLE, htmlContent);
webConnection.setResponse(new URL("http://www.gargoylesoftware.com/foo.js"), jsContent,
"text/javascript");
client.setWebConnection(webConnection);
final String[] expectedAlerts = {"got here"};
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final HtmlPage page = (HtmlPage) client.getPage(URL_GARGOYLE);
final HtmlScript htmlScript = (HtmlScript) page.getHtmlElementById("script1");
assertNotNull(htmlScript);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* An odd case, if two external scripts are referenced and the <script> element
* of the first contain a comment which contain an apostrophe, then the second script
* is ignored.
* https://sourceforge.net/tracker/?func=detail&atid=448266&aid=1990198&group_id=47038
* This works fine in IE6 and FF 2.0. Remove the apostrophe from "shouldn't" to make it work here.
* @throws Exception if the test fails
*/
@Test
public void externalScriptWithApostrophesInComment() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String htmlContent
= "foo\n"
+ "\n"
+ "\n"
+ "\n"
+ "
hello world
\n"
+ "";
webConnection.setResponse(URL_FIRST, htmlContent);
webConnection.setResponse(new URL(URL_FIRST, "foo.js"), "alert('got here');", "text/javascript");
webConnection.setResponse(new URL(URL_FIRST, "foo2.js"), "alert('got here 2');", "text/javascript");
client.setWebConnection(webConnection);
final String[] expectedAlerts = {"got here", "got here 2"};
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final HtmlPage page = (HtmlPage) client.getPage(URL_FIRST);
getLog().debug(page.asXml());
assertEquals(expectedAlerts, collectedAlerts);
assertNotNull(page.getHtmlElementById("script1"));
assertNotNull(page.getHtmlElementById("script2"));
}
/**
* Test that the URL of the page containing the script is contained in the exception's message.
* @throws Exception if the test fails
*/
@Test
public void scriptErrorContainsPageUrl() throws Exception {
// embedded script
final String content1
= "\n"
+ "\n"
+ "";
try {
loadPage(content1);
}
catch (final Exception e) {
assertTrue(e.getMessage().indexOf(URL_GARGOYLE.toString()) > -1);
}
// external script
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String content2
= "foo\n"
+ "\n"
+ "";
final String jsContent = "a.foo = 213;\n";
webConnection.setResponse(URL_GARGOYLE, content2);
final URL urlScript = new URL("http://www.gargoylesoftware.com/foo.js");
webConnection.setResponse(urlScript, jsContent, "text/javascript");
client.setWebConnection(webConnection);
try {
client.getPage(URL_GARGOYLE);
}
catch (final Exception e) {
assertTrue(e.getMessage(), e.getMessage().indexOf(urlScript.toString()) > -1);
}
}
/**
* @throws Exception if the test fails
*/
@Test
public void externalScriptEncoding() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
/*
* this page has meta element , and script tag has no charset attribute
*/
final String htmlContent
= "\n"
+ "\n"
+ "foo\n"
+ "\n"
+ "\n"
+ "
hello world
\n"
+ "\n"
+ "";
/*
* this page has no meta element , and script tag has charset attribute
*/
final String htmlContent2
= "\n"
+ "foo\n"
+ "\n"
+ "\n"
+ "
hello world
\n"
+ "\n"
+ "";
/*
* the corresponding SJIS char of '\u8868' has '\' in second byte.
* if encoding is misspecificated,
* this cause 'unterminated string reteral error'
*/
final String jsContent = "alert('\u8868');\n";
webConnection.setResponse(URL_GARGOYLE, htmlContent);
webConnection.setResponse(
new URL("http://www.gargoylesoftware.com/hidden"),
htmlContent2);
webConnection.setResponse(
new URL("http://www.gargoylesoftware.com/foo.js"),
// make SJIS bytes as responsebody
new String(jsContent.getBytes("SJIS"), "8859_1"), "text/javascript");
/*
* foo2.js is same with foo.js
*/
webConnection.setResponse(
new URL("http://www.gargoylesoftware.com/foo2.js"),
// make SJIS bytes as responsebody
new String(jsContent.getBytes("SJIS"), "8859_1"),
"text/javascript");
client.setWebConnection(webConnection);
final String[] expectedAlerts = {"\u8868"};
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
/*
* detect encoding from meta tag
*/
final HtmlPage page = (HtmlPage) client.getPage(URL_GARGOYLE);
final HtmlScript htmlScript = (HtmlScript) page.getHtmlElementById("script1");
assertNotNull(htmlScript);
assertEquals(expectedAlerts, collectedAlerts);
/*
* detect encoding from charset attribute of script tag
*/
collectedAlerts.clear();
final HtmlPage page2 = (HtmlPage) client.getPage("http://www.gargoylesoftware.com/hidden");
final HtmlScript htmlScript2 = (HtmlScript) page2.getHtmlElementById("script2");
assertNotNull(htmlScript2);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Sets value on input expects a string. If you pass in a value that isn't a string
* this used to blow up.
* @throws Exception if the test fails
*/
@Test
public void setValuesThatAreNotStrings() throws Exception {
final String content
= "foo\n"
+ "
hello world
\n"
+ "\n"
+ "";
final List collectedAlerts = new ArrayList();
final HtmlPage page = loadPage(content, collectedAlerts);
assertEquals("foo", page.getTitleText());
final String[] expectedAlerts = {"1"};
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void referencingVariablesFromOneScriptToAnother_Regression() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String htmlContent
= "foo\n"
+ "\n"
+ "\n"
+ "";
final String jsContent
= "function testNestedMethod() {\n"
+ " if (testLocalVariable == null)\n"
+ " testLocalVariable = 'foo';\n"
+ "} ";
webConnection.setResponse(URL_FIRST, htmlContent);
webConnection.setResponse(new URL("http://first/test.js"), jsContent, "text/javascript");
client.setWebConnection(webConnection);
final HtmlPage page = (HtmlPage) client.getPage(URL_FIRST);
assertEquals("foo", page.getTitleText());
}
/**
* @throws Exception if the test fails
*/
@Test
public void javaScriptUrl() throws Exception {
final String htmlContent
= "\n"
+ "";
final List emptyList = Collections.emptyList();
createTestPageForRealBrowserIfNeeded(htmlContent, emptyList);
final HtmlPage page = loadPage(htmlContent);
final HtmlPage page1 = (HtmlPage) ((HtmlFrame) page.getHtmlElementById("frame1")).getEnclosedPage();
final HtmlPage page2 = (HtmlPage) ((HtmlFrame) page.getHtmlElementById("frame2")).getEnclosedPage();
assertNotNull("page1", page1);
assertNotNull("page2", page2);
assertEquals("frame1", page1.getTitleText());
assertEquals("frame2", page2.getTitleText());
}
/**
* @throws Exception if the test fails
*/
@Test
public void javaScriptWrappedInHtmlComments() throws Exception {
final String htmlContent
= "foo\n"
+ "";
final HtmlPage page = loadPage(htmlContent);
assertEquals("foo", page.getTitleText());
}
/**
* @throws Exception if the test fails
*/
@Test
public void javaScriptWrappedInHtmlComments2() throws Exception {
final String content =
"\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final String[] expectedAlerts = {"1"};
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void javaScriptWrappedInHtmlComments_commentOnOpeningLine() throws Exception {
final String htmlContent
= "foo\n"
+ "";
final HtmlPage page = loadPage(htmlContent);
assertEquals("foo", page.getTitleText());
}
/**
* Regression test for bug 1714762.
* @throws Exception if the test fails
*/
@Test
public void javaScriptWrappedInHtmlComments_commentNotClosed() throws Exception {
final String html
= "foo\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final String[] expectedAlerts = {};
createTestPageForRealBrowserIfNeeded(html, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(html, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void javaScriptWrappedInHtmlComments_allOnOneLine() throws Exception {
final String content
= "\n"
+ " \n"
+ " test\n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ "";
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
final String[] expectedAlerts = {"undefined"};
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void eventHandlerWithComment() throws Exception {
final String content = "";
final String[] expectedAlerts = {"test"};
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* When using the syntax this.something in an onclick handler, "this" must represent
* the object being clicked, not the window. Regression test.
* @throws Exception if the test fails
*/
@Test
public void thisDotInOnClick() throws Exception {
final String htmlContent
= "First\n"
+ "\n"
+ "";
final List collectedAlerts = new ArrayList();
final HtmlPage page = loadPage(htmlContent, collectedAlerts);
assertEquals("First", page.getTitleText());
((HtmlSubmitInput) page.getFormByName("form1").getInputByName("button1")).click();
final String[] expectedAlerts = {"button1"};
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void functionDefinedInExternalFile_CalledFromInlineScript() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String htmlContent
= "foo\n"
+ "\n"
+ " \n"
+ "";
final String jsContent
= "function externalMethod() {\n"
+ " alert('Got to external method');\n"
+ "} ";
webConnection.setResponse(
new URL("http://first/index.html"),
htmlContent);
webConnection.setResponse(
new URL("http://first/test.js"),
jsContent, "text/javascript");
client.setWebConnection(webConnection);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final HtmlPage page = (HtmlPage) client.getPage("http://first/index.html");
assertEquals("foo", page.getTitleText());
assertEquals(new String[] {"Got to external method"}, collectedAlerts);
}
/**
* Regression test for https://sourceforge.net/tracker/?func=detail&atid=448266&aid=1552746&group_id=47038.
* @throws Exception if the test fails
*/
@Test
public void externalScriptWithNewLineBeforeClosingScriptTag() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final String htmlContent
= "foo\n"
+ "\n"
+ "\n" // \n between opening and closing tag is important
+ "";
final String jsContent
= "function externalMethod() {\n"
+ " alert('Got to external method');\n"
+ "} \n"
+ "externalMethod();\n";
webConnection.setResponse(URL_FIRST, htmlContent);
webConnection.setDefaultResponse(jsContent, 200, "OK", "text/javascript");
client.setWebConnection(webConnection);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
client.getPage(URL_FIRST);
assertEquals(new String[] {"Got to external method"}, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void functionCaller() throws Exception {
if (notYetImplemented()) {
return;
}
final String content = "\n"
+ "";
final String[] expectedAlerts = {"true", "false", "false", "true"};
createTestPageForRealBrowserIfNeeded(content, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Test case for bug 707134. Currently I am unable to reproduce the problem.
* @throws Exception if the test fails
*/
@Test
public void functionDefinedInSameFile() throws Exception {
final String htmlContent
= "First\n"
+ "";
final List collectedAlerts = new ArrayList();
final HtmlPage page = loadPage(htmlContent, collectedAlerts);
assertEquals("First", page.getTitleText());
final HtmlForm form = page.getFormByName("form1");
final HtmlTextInput textInput = (HtmlTextInput) form.getInputByName("text1");
textInput.setValueAttribute("flintstone");
final HtmlButtonInput button = (HtmlButtonInput) form.getInputByName("button1");
assertEquals(Collections.EMPTY_LIST, collectedAlerts);
button.click();
assertEquals(new String[] {"Foo is: |flintstone|"}, collectedAlerts);
}
/**
* Test that the JavaScript engine gets called correctly for variable access.
* @throws Exception if the test fails
*/
@Test
public void javaScriptEngineCallsForVariableAccess() throws Exception {
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final String content
= "foo\n"
+ "
hello world
\n"
+ "unqualified\n"
+ "qualified\n"
+ "";
webConnection.setDefaultResponse(content);
client.setWebConnection(webConnection);
final CountingJavaScriptEngine countingJavaScriptEngine = new CountingJavaScriptEngine(client);
client.setJavaScriptEngine(countingJavaScriptEngine);
final HtmlPage page = (HtmlPage) client.getPage(URL_GARGOYLE);
assertEquals(1, countingJavaScriptEngine.getExecutionCount());
assertEquals(0, countingJavaScriptEngine.getCallCount());
((HtmlAnchor) page.getHtmlElementById("unqualified")).click();
assertEquals(1, countingJavaScriptEngine.getExecutionCount());
assertEquals(1, countingJavaScriptEngine.getCallCount());
((HtmlAnchor) page.getHtmlElementById("qualified")).click();
assertEquals(1, countingJavaScriptEngine.getExecutionCount());
assertEquals(2, countingJavaScriptEngine.getCallCount());
final String[] expectedAlerts = {"unqualified: foo", "qualified: foo"};
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Tests ActiveX related exceptions that do not require a map.
* @throws Exception if the test fails
*/
@Test
public void activeXObjectNoMap() throws Exception {
try {
loadPage(getJavaScriptContent("new ActiveXObject()"));
fail("An exception should be thrown for zero argument constructor.");
}
catch (final ScriptException e) {
// Success
}
try {
loadPage(getJavaScriptContent("new ActiveXObject(1, '2', '3')"));
fail("An exception should be thrown for a three argument constructor.");
}
catch (final ScriptException e) {
// Success
}
try {
loadPage(getJavaScriptContent("new ActiveXObject(a)"));
fail("An exception should be thrown for an undefined parameter in the constructor.");
}
catch (final ScriptException e) {
// Success
}
try {
loadPage(getJavaScriptContent("new ActiveXObject(10)"));
fail("An exception should be thrown for an integer parameter in the constructor.");
}
catch (final ScriptException e) {
// Success
}
try {
loadPage(getJavaScriptContent("new ActiveXObject('UnknownObject')"));
fail("An exception should be thrown for a null map.");
}
catch (final ScriptException e) {
// Success
}
}
/**
* Test that Java objects placed in the active x map can be instantiated and used within
* JavaScript using the IE specific ActiveXObject constructor.
* @throws Exception if the test fails
*/
@Test
public void activeXObjectWithMap() throws Exception {
final Map activexToJavaMapping = new HashMap();
activexToJavaMapping.put(
"MockActiveXObject",
"com.gargoylesoftware.htmlunit.javascript.MockActiveXObject");
activexToJavaMapping.put(
"FakeObject",
"com.gargoylesoftware.htmlunit.javascript.NoSuchObject");
activexToJavaMapping.put("BadObject", null);
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
client.setWebConnection(webConnection);
client.setActiveXObjectMap(activexToJavaMapping);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
webConnection.setDefaultResponse(getJavaScriptContent("new ActiveXObject('UnknownObject')"));
try {
client.getPage("http://www.yahoo.com");
fail("An exception should be thrown for non existent object in the map.");
}
catch (final ScriptException e) {
// Success
}
webConnection.setDefaultResponse(getJavaScriptContent("new ActiveXObject('BadObject', 'server')"));
try {
client.getPage("http://www.yahoo.com");
fail("An exception should be thrown for an invalid object in the map.");
}
catch (final ScriptException e) {
// Success
}
// Test for a non existent class in the map
webConnection.setDefaultResponse(getJavaScriptContent("new ActiveXObject('FakeObject')"));
try {
client.getPage("http://www.yahoo.com");
fail("An exception should be thrown for a non existent object in the map.");
}
catch (final ScriptException e) {
// Success
}
Assert.assertEquals("should no alerts yet", Collections.EMPTY_LIST, collectedAlerts);
// Try a valid object in the map
webConnection.setDefaultResponse(getJavaScriptContent(
"var t = new ActiveXObject('MockActiveXObject'); alert(t.MESSAGE);\n"));
client.getPage("http://www.yahoo.com");
assertEquals("The active x object did not bind to the object.",
new String[] {MockActiveXObject.MESSAGE}, collectedAlerts);
collectedAlerts.clear();
webConnection.setDefaultResponse(getJavaScriptContent(
"var t = new ActiveXObject('MockActiveXObject', 'server'); alert(t.GetMessage());\n"));
client.getPage("http://www.yahoo.com");
assertEquals("The active x object did not bind to the object.",
new String[] {new MockActiveXObject().GetMessage()}, collectedAlerts);
}
private String getJavaScriptContent(final String javascript) {
return "foo\n"
+ "
hello world
\n"
+ "\n"
+ "";
}
/**
* Check that wrong JavaScript just causes its context to fail but not the whole page.
* @throws Exception if something goes wrong
*/
@Test
public void scriptErrorIsolated() throws Exception {
final String content
= "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final String[] expectedAlerts = {"1", "3", "4"};
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse(content);
client.setWebConnection(webConnection);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
// first test with script exceptions thrown
try {
client.getPage(URL_FIRST);
fail("Should have thrown a script error");
}
catch (final Exception e) {
// nothing
}
assertEquals(new String[] {"1"}, collectedAlerts);
collectedAlerts.clear();
// and with script exception not thrown
client.setThrowExceptionOnScriptError(false);
client.getPage(URL_FIRST);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Test that prototype changes are made in the right scope.
* Problem reported by Bruce Faulnker in the dev mailing list.
* This is due to a Rhino bug:
* https://bugzilla.mozilla.org/show_bug.cgi?id=374918
* @throws Exception if something goes wrong
*/
@Test
public void prototypeScope() throws Exception {
prototypeScope("String", "'some string'");
prototypeScope("Number", "9");
prototypeScope("Date", "new Date()");
prototypeScope("Function", "function(){}");
prototypeScope("Array", "[]");
}
private void prototypeScope(final String name, final String value) throws Exception {
final String content1
= "\n"
+ "\n"
+ "";
final String content2
= "\n"
+ "\n"
+ "";
final String[] expectedAlerts = {"in page 2", "in foo"};
final WebClient client = new WebClient();
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse(content2);
webConnection.setResponse(URL_FIRST, content1);
client.setWebConnection(webConnection);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
client.getPage(URL_FIRST);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void timeout() throws Exception {
final long timeout = 2000;
final long oldTimeout = JavaScriptEngine.getTimeout();
JavaScriptEngine.setTimeout(timeout);
try {
final WebClient client = new WebClient();
client.setThrowExceptionOnScriptError(false);
final String content = "";
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse(content);
client.setWebConnection(webConnection);
final Exception[] exceptions = {null};
final Thread runner = new Thread() {
@Override
public void run() {
try {
client.getPage(URL_FIRST);
}
catch (final Exception e) {
exceptions[0] = e;
}
}
};
runner.start();
runner.join(timeout * 2);
if (runner.isAlive()) {
runner.interrupt();
fail("Script was still running after timeout");
}
assertNull(exceptions[0]);
}
finally {
JavaScriptEngine.setTimeout(oldTimeout);
}
}
private static final class CountingJavaScriptEngine extends JavaScriptEngine {
private static final long serialVersionUID = 7010508171587446215L;
private int scriptExecutionCount_ = 0;
private int scriptCallCount_ = 0;
private int scriptCompileCount_ = 0;
private int scriptExecuteScriptCount_ = 0;
/**
* Creates an instance.
* @param client the WebClient
*/
protected CountingJavaScriptEngine(final WebClient client) {
super(client);
}
/** @inheritDoc ScriptEngine#execute(HtmlPage,String,String,int) */
@Override
public Object execute(
final HtmlPage htmlPage, final String sourceCode,
final String sourceName, final int startLine) {
scriptExecutionCount_++;
return super.execute(htmlPage, sourceCode, sourceName, startLine);
}
/** @inheritDoc ScriptEngine#execute(HtmlPage,Script) */
@Override
public Object execute(final HtmlPage htmlPage, final Script script) {
scriptExecuteScriptCount_++;
return super.execute(htmlPage, script);
}
/** @inheritDoc ScriptEngine#compile(HtmlPage,String,String,int) */
@Override
public Script compile(final HtmlPage htmlPage, final String sourceCode,
final String sourceName, final int startLine) {
scriptCompileCount_++;
return super.compile(htmlPage, sourceCode, sourceName, startLine);
}
/** @inheritDoc ScriptEngine#callFunction(HtmlPage,Object,Object,Object[],HtmlElement) */
@Override
public Object callFunction(
final HtmlPage htmlPage, final Object javaScriptFunction,
final Object thisObject, final Object[] args,
final DomNode htmlElementScope) {
scriptCallCount_++;
return super.callFunction(htmlPage, javaScriptFunction, thisObject, args, htmlElementScope);
}
/** @return the number of times that this engine has called functions */
public int getCallCount() {
return scriptCallCount_;
}
/** @return the number of times that this engine has executed code */
public int getExecutionCount() {
return scriptExecutionCount_;
}
/** @return the number of times that this engine has compiled code */
public int getCompileCount() {
return scriptCompileCount_;
}
/** @return the number of times that this engine has executed a compiled script */
public int getExecuteScriptCount() {
return scriptExecuteScriptCount_;
}
}
/**
* @throws Exception if the test fails
*/
@Test
public void commentNoDoubleSlashIE() throws Exception {
final String content =
"\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final String[] expectedAlert = {"2"};
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlert, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void commentNoDoubleSlashFF() throws Exception {
final String content =
"\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final List collectedAlerts = new ArrayList();
try {
loadPage(BrowserVersion.FIREFOX_2, content, collectedAlerts);
fail();
}
catch (final ScriptException e) {
assertEquals(4, e.getFailingLineNumber());
}
}
/**
* @throws Exception if the test fails
*/
@Test
public void comment() throws Exception {
comment(BrowserVersion.INTERNET_EXPLORER_6_0);
comment(BrowserVersion.FIREFOX_2);
}
/**
* @throws Exception if the test fails
*/
private void comment(final BrowserVersion browserVersion) throws Exception {
final String content =
"\n"
+ "\n"
+ "\n"
+ "\n"
+ "";
final String[] expectedAlert = {"2", "3"};
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlert, collectedAlerts);
}
/**
* @throws Exception if the test fails
*/
@Test
public void regExpSupport() throws Exception {
final String html = "\n"
+ " \n"
+ " test\n"
+ " \n"
+ " \n"
+ " abc\n"
+ "";
final List collectedAlerts = new ArrayList();
loadPage(html, collectedAlerts);
final String[] expectedAlerts = {
"rstlne-rstlne-rstlne",
"rstlno-rstlne-rstlne",
"rstlna-rstlne-rstlne",
"rstlne-rstlne-rstlne",
"rstlni-rstlni-rstlni",
"rstlna-rstlna-rstlna" };
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Test for Rhino bug https://bugzilla.mozilla.org/show_bug.cgi?id=374918
* Once this bug is fixed, {@link PrimitivePrototypeBugFixer} can be completely removed
* as well as this unit test.
* Correct string primitive prototype resolution within HtmlUnit is tested
* by {@link #prototypeScope()}
*/
@Test
public void stringPrimitivePrototypeScopeRhino() {
if (notYetImplemented()) {
return;
}
final Context cx = ContextFactory.getGlobal().enterContext();
final Scriptable scope1 = cx.initStandardObjects();
final Scriptable scope2 = cx.initStandardObjects();
final String str2 = "function f() { String.prototype.foo = 'from 2'; \n"
+ "var s1 = new String('s1');\n"
+ "if (s1.foo != 'from 2') throw 's1 got: ' + s1.foo;\n" // works
+ "var s2 = 's2';\n"
+ "if (s2.foo != 'from 2') throw 's2 got: ' + s2.foo;\n" // fails
+ "}";
cx.evaluateString(scope2, str2, "source2", 1, null);
scope1.put("scope2", scope1, scope2);
final String str1 = "String.prototype.foo = 'from 1'; scope2.f()";
cx.evaluateString(scope1, str1, "source1", 1, null);
Context.exit();
}
/**
* Test ECMA reserved keywords... that are accepted by "normal" browsers
* @throws Exception if the test fails
*/
@Test
public void ecmaReservedKeywords() throws Exception {
final String content
= "foo\n"
+ "";
final String[] expectedAlerts = {"123"};
createTestPageForRealBrowserIfNeeded(content, expectedAlerts);
final List collectedAlerts = new ArrayList();
loadPage(content, collectedAlerts);
assertEquals(expectedAlerts, collectedAlerts);
}
/**
* Test that compiled script are cached.
* @throws Exception if the test fails
*/
@Test
public void compiledScriptCached() throws Exception {
final String content1
= "foo\n"
+ "\n"
+ "\n"
+ "to page 2\n"
+ "";
final String content2
= "page 2\n"
+ "\n"
+ "\n"
+ "";
final String script = "alert(document.title)";
final WebClient client = new WebClient();
final MockWebConnection connection = new MockWebConnection(client);
client.setWebConnection(connection);
connection.setResponse(URL_FIRST, content1);
connection.setResponse(new URL(URL_FIRST, "page2.html"), content2);
final List headersAllowingCache = new ArrayList();
headersAllowingCache.add(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
connection.setResponse(new URL(URL_FIRST, "script.js"), script,
200, "ok", "text/javascript", headersAllowingCache);
final CountingJavaScriptEngine countingJavaScriptEngine = new CountingJavaScriptEngine(client);
client.setJavaScriptEngine(countingJavaScriptEngine);
final List collectedAlerts = new ArrayList();
client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
final HtmlPage page1 = (HtmlPage) client.getPage(URL_FIRST);
assertEquals(new String[] {"foo"}, collectedAlerts);
assertEquals(1, countingJavaScriptEngine.getExecuteScriptCount());
assertEquals(1, countingJavaScriptEngine.getCompileCount());
collectedAlerts.clear();
page1.getAnchors().get(0).click();
assertEquals(new String[] {"page 2"}, collectedAlerts);
assertEquals(2, countingJavaScriptEngine.getExecuteScriptCount());
assertEquals(1, countingJavaScriptEngine.getCompileCount());
}
/**
* Test that code in script tags is executed on page load. Try different combinations
* of the script tag except for the case where a remote JavaScript page is loaded. That
* one will be tested separately.
* @throws Exception if something goes wrong
*/
@Test
public void scriptTags_AllLocalContent() throws Exception {
final String content
= "\n"
+ "foo\n"
+ "\n" // no language specified - assume JavaScript
+ "\n"
+ "\n"
+ "\n" // type is unsupported language
+ "\n"
+ "\n"
+ "
hello world
\n"
+ "";
final List collectedScripts = new ArrayList();
loadPageAndCollectScripts(content, collectedScripts);
// NO MORE: The last expected is the dummy stub that is needed to initialize the JavaScript engine
final String[] expectedScripts = {"One", "Two", "Three"};
assertEquals(expectedScripts, collectedScripts);
}
private HtmlPage loadPageAndCollectScripts(final String html, final List collectedScripts)
throws Exception {
final WebClient client = new WebClient();
client.setJavaScriptEngine(new JavaScriptEngine(client) {
private static final long serialVersionUID = -3069321085262318962L;
@Override
public Object execute(final HtmlPage htmlPage, final String sourceCode,
final String sourceName, final int startLine) {
collectedScripts.add(sourceCode);
return null;
}
@Override
public Object callFunction(
final HtmlPage htmlPage, final Object javaScriptFunction,
final Object thisObject, final Object [] args,
final DomNode htmlElement) {
return null;
}
@Override
public boolean isScriptRunning() {
return false;
}
});
final MockWebConnection webConnection = new MockWebConnection(client);
webConnection.setDefaultResponse(html);
client.setWebConnection(webConnection);
final HtmlPage page = (HtmlPage) client.getPage(new WebRequestSettings(URL_GARGOYLE, HttpMethod.POST));
return page;
}
}