Extend ScriptInjector to support removing the <script> tag after
loading a script from a URL, and rewrite ScriptTagLoadingStrategy to
use ScriptInjector.
Review at http://gwt-code-reviews.appspot.com/1675803
Review by: skybrian@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11389 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/ScriptInjector.java b/user/src/com/google/gwt/core/client/ScriptInjector.java
index 4c08242..d07ad4a 100644
--- a/user/src/com/google/gwt/core/client/ScriptInjector.java
+++ b/user/src/com/google/gwt/core/client/ScriptInjector.java
@@ -119,6 +119,7 @@
*/
public static class FromUrl {
private Callback<Void, Exception> callback;
+ private boolean removeTag = false;
private final String scriptUrl;
private JavaScriptObject window;
@@ -139,8 +140,8 @@
assert doc != null;
JavaScriptObject scriptElement = nativeMakeScriptElement(doc);
assert scriptElement != null;
- if (callback != null) {
- attachListeners(scriptElement, callback);
+ if (callback != null || removeTag) {
+ attachListeners(scriptElement, callback, removeTag);
}
nativeSetSrc(scriptElement, scriptUrl);
nativeAttachToHead(doc, scriptElement);
@@ -175,6 +176,19 @@
}
/**
+ * @param removeTag If true, remove the tag after the script finishes
+ * loading. This shrinks the DOM, possibly at the expense of
+ * readability if you are debugging javaScript.
+ *
+ * Default value is {@code false}, but this may change in a future
+ * release.
+ */
+ public FromUrl setRemoveTag(boolean removeTag) {
+ this.removeTag = removeTag;
+ return this;
+ }
+
+ /**
* This call allows you to specify which DOM window object to install the
* script tag in. To install into the Top level window call
*
@@ -243,20 +257,27 @@
* @param callback callback that runs when the script is loaded and parsed.
*/
private static native void attachListeners(JavaScriptObject scriptElement,
- Callback<Void, Exception> callback) /*-{
+ Callback<Void, Exception> callback, boolean removeTag) /*-{
function clearCallbacks() {
scriptElement.onerror = scriptElement.onreadystatechange = scriptElement.onload = function() {
};
+ if (removeTag) {
+ @com.google.gwt.core.client.ScriptInjector::nativeRemove(Lcom/google/gwt/core/client/JavaScriptObject;)(scriptElement);
+ }
}
scriptElement.onload = $entry(function() {
clearCallbacks();
- callback.@com.google.gwt.core.client.Callback::onSuccess(Ljava/lang/Object;)(null);
+ if (callback) {
+ callback.@com.google.gwt.core.client.Callback::onSuccess(Ljava/lang/Object;)(null);
+ }
});
- // or possibly more portable script_tag.addEventListener('error', function(){...}, true);
+ // or possibly more portable script_tag.addEventListener('error', function(){...}, true);
scriptElement.onerror = $entry(function() {
clearCallbacks();
- var ex = @com.google.gwt.core.client.CodeDownloadException::new(Ljava/lang/String;)("onerror() called.");
- callback.@com.google.gwt.core.client.Callback::onFailure(Ljava/lang/Object;)(ex)
+ if (callback) {
+ var ex = @com.google.gwt.core.client.CodeDownloadException::new(Ljava/lang/String;)("onerror() called.");
+ callback.@com.google.gwt.core.client.Callback::onFailure(Ljava/lang/Object;)(ex);
+ }
});
scriptElement.onreadystatechange = $entry(function() {
if (scriptElement.readyState == 'complete' || scriptElement.readyState == 'loaded') {
diff --git a/user/src/com/google/gwt/core/client/impl/ScriptTagLoadingStrategy.java b/user/src/com/google/gwt/core/client/impl/ScriptTagLoadingStrategy.java
index 6b2b97e..1e88bf5 100644
--- a/user/src/com/google/gwt/core/client/impl/ScriptTagLoadingStrategy.java
+++ b/user/src/com/google/gwt/core/client/impl/ScriptTagLoadingStrategy.java
@@ -15,7 +15,8 @@
*/
package com.google.gwt.core.client.impl;
-import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.core.client.impl.AsyncFragmentLoader.HttpDownloadFailure;
/**
@@ -23,10 +24,8 @@
* therefore cross site compatible. Note that if this strategy is used, the
* deferred fragments must be wrapped in a callback called runAsyncCallbackX()
* where X is the fragment number.
- *
+ *
* This is the default strategy for the CrossSiteIframeLinker.
- *
- * TODO(unnurg): Try to use the ScriptInjector here
*/
public class ScriptTagLoadingStrategy extends LoadingStrategyBase {
@@ -37,96 +36,57 @@
protected static class ScriptTagDownloadStrategy implements DownloadStrategy {
@Override
public void tryDownload(final RequestData request) {
- int fragment = request.getFragment();
- JavaScriptObject scriptTag = createScriptTag(request.getUrl());
- setOnSuccess(fragment, onSuccess(fragment, scriptTag, request));
- setOnFailure(scriptTag, onFailure(fragment, scriptTag, request));
- installScriptTag(scriptTag);
+ setAsyncCallback(request.getFragment(), request);
+
+ ScriptInjector.fromUrl(request.getUrl()).setRemoveTag(true).setCallback(
+ new Callback<Void, Exception>() {
+ @Override
+ public void onFailure(Exception reason) {
+ cleanup(request);
+ }
+
+ @Override
+ public void onSuccess(Void result) {
+ cleanup(request);
+ }
+ }).inject();
}
}
-
- protected static void callOnLoadError(RequestData request) {
- request.onLoadError(new HttpDownloadFailure(request.getUrl(), 404,
- "Script Tag Failure - no status available"), true);
+
+ private static void asyncCallback(RequestData request, String code) {
+ boolean firstTimeCalled = clearAsyncCallback(request.getFragment());
+ if (firstTimeCalled) {
+ request.tryInstall(code);
+ }
}
- private static native boolean clearCallbacksAndRemoveTag(
- int fragment, JavaScriptObject scriptTag) /*-{
- if (scriptTag.parentNode == null) {
- // onSuccess or onFailure must have already been called.
+ private static void cleanup(RequestData request) {
+ boolean neverCalled = clearAsyncCallback(request.getFragment());
+ if (neverCalled) {
+ request.onLoadError(new HttpDownloadFailure(request.getUrl(), 404,
+ "Script Tag Failure - no status available"), true);
+ }
+ }
+
+ /**
+ * Returns true if the callback existed.
+ */
+ private static native boolean clearAsyncCallback(int fragment) /*-{
+ if (!__gwtModuleFunction['runAsyncCallback' + fragment]) {
return false;
}
- var head = document.getElementsByTagName('head').item(0);
- @com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::clearOnSuccess(I)(fragment);
- @com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::clearOnFailure(Lcom/google/gwt/core/client/JavaScriptObject;)(
- scriptTag);
- head.removeChild(scriptTag);
+ delete __gwtModuleFunction['runAsyncCallback' + fragment];
return true;
}-*/;
-
- private static native void clearOnFailure(JavaScriptObject scriptTag) /*-{
- scriptTag.onerror = scriptTag.onload = scriptTag.onreadystatechange = function(){};
- }-*/;
- private static native void clearOnSuccess(int fragment) /*-{
- delete __gwtModuleFunction['runAsyncCallback' + fragment];
- }-*/;
-
- private static native JavaScriptObject createScriptTag(String url) /*-{
- var head = document.getElementsByTagName('head').item(0);
- var scriptTag = document.createElement('script');
- scriptTag.src = url;
- return scriptTag;
- }-*/;
-
- private static native void installScriptTag(JavaScriptObject scriptTag) /*-{
- var head = document.getElementsByTagName('head').item(0);
- head.appendChild(scriptTag);
- }-*/;
-
- private static native JavaScriptObject onFailure(
- int fragment, JavaScriptObject scriptTag, RequestData request) /*-{
- return function() {
- if (@com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::clearCallbacksAndRemoveTag(ILcom/google/gwt/core/client/JavaScriptObject;)(
- fragment, scriptTag)) {
- @com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::callOnLoadError(Lcom/google/gwt/core/client/impl/LoadingStrategyBase$RequestData;)(
- request)
- }
- }
- }-*/;
-
- private static native JavaScriptObject onSuccess(int fragment,
- JavaScriptObject scriptTag, RequestData request) /*-{
- return function(code, instance) {
- if (@com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::clearCallbacksAndRemoveTag(ILcom/google/gwt/core/client/JavaScriptObject;)(
- fragment, scriptTag)) {
- request.@com.google.gwt.core.client.impl.LoadingStrategyBase.RequestData::tryInstall(Ljava/lang/String;)(
- code);
- }
- }
- }-*/;
-
- private static native void setOnFailure(JavaScriptObject script,
- JavaScriptObject callback) /*-{
- script.onerror = function() {
- callback();
- }
- script.onload = function() {
- callback();
- }
- script.onreadystatechange = function () {
- if (script.readyState == 'loaded' || script.readyState == 'complete') {
- script.onreadystatechange = function () { }
- callback();
- }
- }
- }-*/;
-
- private static native void setOnSuccess(int fragment, JavaScriptObject callback) /*-{
- __gwtModuleFunction['runAsyncCallback'+fragment] = callback;
+ private static native void setAsyncCallback(int fragment, RequestData request) /*-{
+ __gwtModuleFunction['runAsyncCallback' + fragment] = $entry(function(code, instance) {
+ @com.google.gwt.core.client.impl.ScriptTagLoadingStrategy::asyncCallback(Lcom/google/gwt/core/client/impl/LoadingStrategyBase$RequestData;Ljava/lang/String;)(
+ request, code);
+ });
}-*/;
public ScriptTagLoadingStrategy() {
super(new ScriptTagDownloadStrategy());
- }
+ }
}
diff --git a/user/test/com/google/gwt/core/client/ScriptInjectorTest.java b/user/test/com/google/gwt/core/client/ScriptInjectorTest.java
index 7596688..f245a1c 100644
--- a/user/test/com/google/gwt/core/client/ScriptInjectorTest.java
+++ b/user/test/com/google/gwt/core/client/ScriptInjectorTest.java
@@ -204,7 +204,8 @@
this.delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test4.js";
assertFalse(nativeTest4Worked());
- final JavaScriptObject injectedElement = ScriptInjector.fromUrl(scriptUrl).inject();
+ 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.
@@ -242,8 +243,8 @@
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test5.js";
assertFalse(nativeTest5Worked());
- JavaScriptObject injectedElement =
- ScriptInjector.fromUrl(scriptUrl).setCallback(new Callback<Void, Exception>() {
+ JavaScriptObject injectedElement = ScriptInjector.fromUrl(scriptUrl).setRemoveTag(false)
+ .setCallback(new Callback<Void, Exception>() {
@Override
public void onFailure(Exception reason) {
assertNotNull(reason);
@@ -272,8 +273,8 @@
public void testInjectUrlTopWindow() {
final String scriptUrl = "script_injector_test6.js";
assertFalse(nativeTest6Worked());
- JavaScriptObject injectedElement =
- ScriptInjector.fromUrl(scriptUrl).setWindow(ScriptInjector.TOP_WINDOW).inject();
+ 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() {
@@ -309,8 +310,8 @@
delayTestFinish(TEST_DELAY);
final String scriptUrl = "script_injector_test7.js";
assertFalse(nativeTest7Worked());
- JavaScriptObject injectedElement =
- ScriptInjector.fromUrl(scriptUrl).setWindow(ScriptInjector.TOP_WINDOW).setCallback(
+ JavaScriptObject injectedElement = ScriptInjector.fromUrl(scriptUrl).setRemoveTag(false)
+ .setWindow(ScriptInjector.TOP_WINDOW).setCallback(
new Callback<Void, Exception>() {
@Override