blob: 8d43b0a3301ef65b77cb823476d828a2d47e575a [file] [log] [blame]
/*
* Copyright 2011 Google 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.google.gwt.core.client;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.ScriptInjector.FromString;
import com.google.gwt.junit.client.GWTTestCase;
/**
* Tests for {@link ScriptInjector}
*/
public class ScriptInjectorTest extends GWTTestCase {
private static boolean browserChecked = false;
private static final int CHECK_DELAY = 100;
private static final int TEST_DELAY = 10000;
@Override
public String getModuleName() {
return "com.google.gwt.core.Core";
}
/**
* Install a script in the same window as GWT.
*/
public void testInjectDirectThisWindow() {
delayTestFinish(TEST_DELAY);
String scriptBody = "__ti1_var__ = 1;";
assertFalse(nativeTest1Worked());
new FromString(scriptBody).inject();
boolean worked = nativeTest1Worked();
JavaScriptObject scriptElement = findScriptTextInThisWindow(scriptBody);
cleanupThisWindow("__ti1_var__", scriptElement);
assertFalse("cleanup failed", nativeTest1Worked());
assertTrue("__ti1_var not set in this window", worked);
assertNull("script element 1 not removed by injection", scriptElement);
finishTest();
}
/**
* Install a script in the top window.
*/
public void testInjectDirectTopWindow() {
String scriptBody = "__ti2_var__ = 2;";
assertFalse(nativeTest2Worked());
ScriptInjector.fromString(scriptBody).setWindow(ScriptInjector.TOP_WINDOW).inject();
boolean worked = nativeTest2Worked();
JavaScriptObject scriptElement = findScriptTextInTopWindow(scriptBody);
cleanupTopWindow("__ti2_var__", scriptElement);
assertTrue("__ti2_var not set in top window", worked);
assertNull("script element 2 not removed by injection", scriptElement);
}
/**
* Install a script in the same window as GWT, turn off the tag removal.
*/
public void testInjectDirectWithoutRemoveTag() {
assertFalse(nativeTest3Worked());
String scriptBody = "__ti3_var__ = 3;";
new FromString(scriptBody).setRemoveTag(false).inject();
boolean worked = nativeTest3Worked();
JavaScriptObject scriptElement = findScriptTextInThisWindow(scriptBody);
// Since we are not in the top window, there is no nonce.
assertNull(nativeGetScriptNonce(scriptElement));
cleanupThisWindow("__ti3_var__", scriptElement);
assertFalse("cleanup failed", nativeTest3Worked());
assertTrue(worked);
assertNotNull("script element 3 should have been left in DOM", scriptElement);
}
/**
* Inject an absolute URL on this window.
*/
public void testInjectUrlAbsolute() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = GWT.getModuleBaseForStaticFiles() + "script_injector_test_absolute.js";
assertFalse(nativeInjectUrlAbsoluteWorked());
ScriptInjector.fromUrl(scriptUrl).setCallback(new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
assertTrue(nativeInjectUrlAbsoluteWorked());
finishTest();
}
}).inject();
}
/**
* Inject an absolute URL on the top level window.
*/
public void testInjectUrlAbsoluteTop() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = GWT.getModuleBaseForStaticFiles() + "script_injector_test_absolute_top.js";
assertFalse(nativeAbsoluteTopUrlIsLoaded());
ScriptInjector.fromUrl(scriptUrl).setWindow(ScriptInjector.TOP_WINDOW).setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
assertTrue(nativeAbsoluteTopUrlIsLoaded());
finishTest();
}
}).inject();
}
/**
* This script injection should fail and fire the onFailure callback.
*
* <p>Note, the onerror mechanism used to trigger the failure event is a modern browser feature.
*
* <p>On IE, the script.onerror tag has been documented, but busted for <a href=
* "http://stackoverflow.com/questions/2027849/how-to-trigger-script-onerror-in-internet-explorer/2032014#2032014"
* >aeons</a>.
*/
public void testInjectUrlFail() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "uNkNoWn_sCrIpT_404.js";
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl).setCallback(new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
finishTest();
}
@Override
public void onSuccess(Void result) {
fail("Injection unexpectedly succeeded.");
}
}).inject();
assertNotNull(injectedElement);
}
/**
* Install a script in the same window as GWT by URL
*/
public void testInjectUrlThisWindow() {
this.delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test4.js";
assertFalse(nativeTest4Worked());
final JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl).setRemoveTag(false).inject();
// We'll check using a callback in another test. This test will poll to see
// that the script had an effect.
Scheduler.get()
.scheduleFixedDelay(
new RepeatingCommand() {
int numLoops = 0;
@Override
public boolean execute() {
numLoops++;
boolean worked = nativeTest4Worked();
if (!worked && (numLoops * CHECK_DELAY < TEST_DELAY)) {
return true;
}
JavaScriptObject scriptElement = findScriptUrlInThisWindow(scriptUrl);
// Since we are not in the top window, there is no nonce.
assertNull(nativeGetScriptNonce(scriptElement));
cleanupThisWindow("__ti4_var__", scriptElement);
assertFalse("cleanup failed", nativeTest4Worked());
assertTrue("__ti4_var not set in this window", worked);
assertNotNull("script element 4 not found", scriptElement);
assertEquals(injectedElement, scriptElement);
finishTest();
// never reached
return false;
}
},
CHECK_DELAY);
assertNotNull(injectedElement);
}
/**
* Install a script in the same window as GWT by URL
*/
public void testInjectUrlThisWindowCallback() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test5.js";
assertFalse(nativeTest5Worked());
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl)
.setRemoveTag(false)
.setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
boolean worked = nativeTest5Worked();
JavaScriptObject scriptElement = findScriptUrlInThisWindow(scriptUrl);
// Since we are not in the top window, there is no nonce.
assertNull(nativeGetScriptNonce(scriptElement));
cleanupThisWindow("__ti5_var__", scriptElement);
assertFalse("cleanup failed", nativeTest5Worked());
assertTrue("__ti5_var not set in this window", worked);
assertNotNull("script element 5 not found", scriptElement);
finishTest();
}
})
.inject();
assertNotNull(injectedElement);
}
/**
* Install a script in the top window by URL
*/
public void testInjectUrlTopWindow() {
final String scriptUrl = "script_injector_test6.js";
assertFalse(nativeTest6Worked());
JavaScriptObject injectedElement = ScriptInjector.fromUrl(scriptUrl).setRemoveTag(false)
.setWindow(ScriptInjector.TOP_WINDOW).inject();
// We'll check using a callback in another test. This test will poll to see
// that the script had an effect.
Scheduler.get()
.scheduleFixedDelay(
new RepeatingCommand() {
int numLoops = 0;
@Override
public boolean execute() {
numLoops++;
boolean worked = nativeTest6Worked();
if (!worked && (numLoops * CHECK_DELAY < TEST_DELAY)) {
return true;
}
JavaScriptObject scriptElement = findScriptUrlInTopWindow(scriptUrl);
assertDefaultNonce(scriptElement);
cleanupTopWindow("__ti6_var__", scriptElement);
assertFalse("cleanup failed", nativeTest6Worked());
assertTrue("__ti6_var not set in top window", worked);
assertNotNull("script element 6 not found", scriptElement);
finishTest();
// never reached
return false;
}
},
CHECK_DELAY);
assertNotNull(injectedElement);
}
/**
* Install a script in the top window by URL
*/
public void testInjectUrlTopWindowCallback() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test7.js";
assertFalse(nativeTest7Worked());
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl)
.setRemoveTag(false)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
boolean worked = nativeTest7Worked();
JavaScriptObject scriptElement = findScriptUrlInTopWindow(scriptUrl);
assertDefaultNonce(scriptElement);
cleanupTopWindow("__ti7_var__", scriptElement);
assertFalse("cleanup failed", nativeTest7Worked());
assertTrue("__ti7_var not set in top window", worked);
assertNotNull("script element 7 not found", scriptElement);
finishTest();
}
})
.inject();
assertNotNull(injectedElement);
}
/**
* Tests encoding of the injected script (UTF-8)
*/
public void testInjectUrlUtf8() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test_utf8.js";
assertEquals("", nativeGetTestUtf8Var());
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl)
.setRemoveTag(false)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
String testVar = nativeGetTestUtf8Var();
JavaScriptObject scriptElement = findScriptUrlInTopWindow(scriptUrl);
assertDefaultNonce(scriptElement);
cleanupTopWindow("__ti_utf8_var__", scriptElement);
assertEquals("cleanup failed", "", nativeGetTestUtf8Var());
assertEquals("__ti_utf8_var not set in top window", "à", testVar);
assertNotNull("script element not found", scriptElement);
finishTest();
}
})
.inject();
assertNotNull(injectedElement);
}
public void testInjectUrlTopWindowNoncePropegated() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test9.js";
final String nonceScriptUrl = "script_injector_test9_nonce.js";
final String nonce = "IAlwaysGetTheShemp";
final JavaScriptObject noncedScript =
nativeAddScriptWithNonceToWindow(nativeTopWindow(), nonce, nonceScriptUrl);
assertEquals(nonce, nativeGetScriptNonce(noncedScript));
assertFalse(nativeTest9Worked());
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl)
.setRemoveTag(false)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
boolean worked = nativeTest9Worked();
JavaScriptObject scriptElement = findScriptUrlInTopWindow(scriptUrl);
assertEquals(nonce, nativeGetScriptNonce(scriptElement));
cleanupTopWindow("__ti9_var__", scriptElement);
cleanupTopWindow("__ti9_nonce_var__", noncedScript);
assertFalse("cleanup failed", nativeTest9Worked());
assertTrue("__ti9_var not set in top window", worked);
assertNotNull("script element 9 not found", scriptElement);
finishTest();
}
})
.inject();
assertNotNull(injectedElement);
}
public void testInjectUrlThisWindowNoncePropegated() {
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test10.js";
final String nonceScriptUrl = "script_injector_test10_nonce.js";
final String nonce = "ANearbyRoosterIsInHighDef";
final JavaScriptObject noncedScript =
nativeAddScriptWithNonceToWindow(nativeThisWindow(), nonce, nonceScriptUrl);
assertEquals(nonce, nativeGetScriptNonce(noncedScript));
assertFalse(nativeTest10Worked());
JavaScriptObject injectedElement =
ScriptInjector.fromUrl(scriptUrl)
.setRemoveTag(false)
.setCallback(
new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
fail("Injection failed: " + reason.toString());
}
@Override
public void onSuccess(Void result) {
boolean worked = nativeTest10Worked();
JavaScriptObject scriptElement = findScriptUrlInThisWindow(scriptUrl);
assertEquals(nonce, nativeGetScriptNonce(scriptElement));
cleanupThisWindow("__ti10_var__", scriptElement);
cleanupThisWindow("__ti10_nonce_var__", noncedScript);
assertFalse("cleanup failed", nativeTest10Worked());
assertTrue("__ti10_var not set in top window", worked);
assertNotNull("script element 10 not found", scriptElement);
finishTest();
}
})
.inject();
assertNotNull(injectedElement);
}
private void cleanupThisWindow(String property, JavaScriptObject scriptElement) {
cleanupWindow(nativeThisWindow(), property, scriptElement);
}
private void cleanupTopWindow(String property, JavaScriptObject scriptElement) {
cleanupWindow(nativeTopWindow(), property, scriptElement);
}
private native void cleanupWindow(JavaScriptObject wnd, String property,
JavaScriptObject scriptElement) /*-{
delete wnd[property];
if (scriptElement) {
scriptElement.parentNode.removeChild(scriptElement);
}
}-*/;
private void assertDefaultNonce(JavaScriptObject scriptElement) {
assertEquals(
"Expected script to have the default nonce value",
"gwt-nonce",
nativeGetScriptNonce(scriptElement));
}
private native JavaScriptObject nativeAddScriptWithNonceToWindow(
JavaScriptObject wnd, String nonce, String scriptUrl) /*-{
var scriptToAdd = wnd.document.createElement('script');
scriptToAdd.setAttribute('nonce', nonce);
scriptToAdd.src = scriptUrl;
var head = wnd.document.head || wnd.document.getElementsByTagName("head")[0];
head.insertBefore(scriptToAdd, head.firstChild);
return scriptToAdd;
}-*/;
private JavaScriptObject findScriptTextInThisWindow(String text) {
return nativeFindScriptText(nativeThisWindow(), text);
}
private JavaScriptObject findScriptTextInTopWindow(String text) {
return nativeFindScriptText(nativeTopWindow(), text);
}
private JavaScriptObject findScriptUrlInThisWindow(String url) {
return nativeFindScriptUrl(nativeThisWindow(), url);
}
private JavaScriptObject findScriptUrlInTopWindow(String url) {
return nativeFindScriptUrl(nativeTopWindow(), url);
}
private native boolean nativeAbsoluteTopUrlIsLoaded() /*-{
return !!$wnd["__tiabsolutetop_var__"] && $wnd["__tiabsolutetop_var__"] == 102;
}-*/;
private native JavaScriptObject nativeFindScriptText(JavaScriptObject wnd, String text) /*-{
var scripts = wnd.document.getElementsByTagName("script");
for ( var i = 0; i < scripts.length; ++i) {
if (scripts[i].text.match("^" + text)) {
return scripts[i];
}
}
return null;
}-*/;
/**
* Won't work for all urls, uses a regular expression match
*/
private native JavaScriptObject nativeFindScriptUrl(JavaScriptObject wnd, String url) /*-{
var scripts = wnd.document.getElementsByTagName("script");
for ( var i = 0; i < scripts.length; ++i) {
if (scripts[i].src.match(url)) {
return scripts[i];
}
}
return null;
}-*/;
private native String nativeGetScriptNonce(JavaScriptObject scriptElement) /*-{
return scriptElement['nonce'] || scriptElement.getAttribute('nonce');
}-*/;
private native boolean nativeInjectUrlAbsoluteWorked() /*-{
return !!window["__tiabsolute_var__"] && window["__tiabsolute_var__"] == 101;
}-*/;
private native boolean nativeTest1Worked() /*-{
return !!window["__ti1_var__"] && window["__ti1_var__"] == 1;
}-*/;
private native boolean nativeTest2Worked() /*-{
return !!$wnd["__ti2_var__"] && $wnd["__ti2_var__"] == 2;
}-*/;
private native boolean nativeTest3Worked() /*-{
return !!window["__ti3_var__"] && window["__ti3_var__"] == 3;
}-*/;
private native boolean nativeTest4Worked() /*-{
return !!window["__ti4_var__"] && window["__ti4_var__"] == 4;
}-*/;
private native boolean nativeTest5Worked() /*-{
return !!window["__ti5_var__"] && window["__ti5_var__"] == 5;
}-*/;
private native boolean nativeTest6Worked() /*-{
return !!$wnd["__ti6_var__"] && $wnd["__ti6_var__"] == 6;
}-*/;
private native boolean nativeTest7Worked() /*-{
return !!$wnd["__ti7_var__"] && $wnd["__ti7_var__"] == 7;
}-*/;
private native String nativeGetTestUtf8Var() /*-{
return $wnd["__ti_utf8_var__"] || "";
}-*/;
private native boolean nativeTest9Worked() /*-{
return !!$wnd["__ti9_var__"] && $wnd["__ti9_var__"] == 9;
}-*/;
private native boolean nativeTest10Worked() /*-{
return !!window["__ti10_var__"] && window["__ti10_var__"] == 10;
}-*/;
private native JavaScriptObject nativeThisWindow() /*-{
return window;
}-*/;
private native JavaScriptObject nativeTopWindow() /*-{
return $wnd;
}-*/;
}