Add a prefetch queue to AsyncFragmentLoader. User code can add split points to
the queue, and AsyncFragmentLoader will load the code for them whenever it has
nothing else to do.
Also updates the Showcase sample to prefetch code within sample categories. For
example, clicking on any of the "panels" samples causes all of the other panels
samples' code to be prefetched.
Review by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6992 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index f7c0f42..c6bd9f6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -80,7 +80,8 @@
"com.google.gwt.core.client.RunAsyncCallback",
"com.google.gwt.core.client.impl.AsyncFragmentLoader",
"com.google.gwt.core.client.impl.Impl",
- "com.google.gwt.lang.EntryMethodHolder",}));
+ "com.google.gwt.lang.EntryMethodHolder",
+ "com.google.gwt.core.client.prefetch.RunAsyncCode",}));
static final Map<String, Set<String>> traceMethods = new HashMap<String, Set<String>>();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
index f47edb6..950db33 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
@@ -16,6 +16,7 @@
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
@@ -30,7 +31,9 @@
import com.google.gwt.dev.jjs.ast.JType;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -38,7 +41,10 @@
* {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)}
* and
* {@link com.google.gwt.core.client.GWT#runAsync(Class, com.google.gwt.core.client.RunAsyncCallback)
- * by calls to a fragment loader.
+ * by calls to a fragment loader. Additionally, replaces access to
+ *
+ * @link RunAsyncCode#forSplitPoint(Class)} by an equivalent call using an
+ * integer rather than a class literal.
*/
public class ReplaceRunAsyncs {
/**
@@ -112,7 +118,7 @@
asyncCallback = x.getArgs().get(0);
break;
case 2:
- name = ((JClassLiteral) x.getArgs().get(0)).getRefType().getName();
+ name = nameFromClassLiteral((JClassLiteral) x.getArgs().get(0));
asyncCallback = x.getArgs().get(1);
break;
default:
@@ -151,11 +157,70 @@
&& method.getName().equals("runAsync");
}
}
+ private class ReplaceRunAsyncResources extends JModVisitor {
+ private Map<String, List<RunAsyncReplacement>> replacementsByName;
- public static void exec(TreeLogger logger, JProgram program) {
- logger.log(TreeLogger.TRACE,
+ public ReplaceRunAsyncResources() {
+ replacementsByName = new HashMap<String, List<RunAsyncReplacement>>();
+ for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
+ String name = replacement.getName();
+ if (name != null) {
+ List<RunAsyncReplacement> list = replacementsByName.get(name);
+ if (list == null) {
+ list = new ArrayList<RunAsyncReplacement>();
+ replacementsByName.put(name, list);
+ }
+ list.add(replacement);
+ }
+ }
+ }
+
+ @Override
+ public void endVisit(JMethodCall x, Context ctx) {
+ if (x.getTarget() == program.getIndexedMethod("RunAsyncCode.runAsyncCode")) {
+ JClassLiteral lit = (JClassLiteral) x.getArgs().get(0);
+ String name = nameFromClassLiteral(lit);
+ List<RunAsyncReplacement> matches = replacementsByName.get(name);
+ if (matches == null || matches.size() == 0) {
+ error("No runAsync call is named " + name);
+ return;
+ }
+ if (matches.size() > 1) {
+ TreeLogger branch = error("Multiple runAsync calls are named " + name);
+ for (RunAsyncReplacement match : matches) {
+ branch.log(TreeLogger.ERROR, "One call is in " + methodDescription(match.getEnclosingMethod()));
+ }
+ return;
+ }
+ Integer splitPoint = matches.get(0).getNumber();
+
+ JMethodCall newCall = new JMethodCall(x.getSourceInfo(), null,
+ program.getIndexedMethod("RunAsyncCode.forSplitPointNumber"));
+ newCall.addArg(program.getLiteralInt(splitPoint));
+ ctx.replaceMe(newCall);
+ }
+ }
+
+ private String methodDescription(JMethod method) {
+ StringBuilder desc = new StringBuilder();
+ desc.append(method.getEnclosingType().getName());
+ desc.append(".");
+ desc.append(method.getName());
+ desc.append(" (");
+ desc.append(method.getSourceInfo().getFileName());
+ desc.append(':');
+ desc.append(method.getSourceInfo().getStartLine());
+ desc.append(")");
+
+ return desc.toString();
+ }
+ }
+
+ public static void exec(TreeLogger logger, JProgram program)
+ throws UnableToCompleteException {
+ TreeLogger branch = logger.branch(TreeLogger.TRACE,
"Replacing GWT.runAsync with island loader calls");
- new ReplaceRunAsyncs(program).execImpl();
+ new ReplaceRunAsyncs(branch, program).execImpl();
}
/**
@@ -169,19 +234,38 @@
return constructorCall;
}
+ /**
+ * Convert a class literal to a runAsync name.
+ */
+ private static String nameFromClassLiteral(JClassLiteral classLiteral) {
+ return classLiteral.getRefType().getName();
+ }
+
+ private boolean errorsFound = false;
+ private final TreeLogger logger;
private JProgram program;
private Map<Integer, RunAsyncReplacement> runAsyncReplacements = new HashMap<Integer, RunAsyncReplacement>();
- private ReplaceRunAsyncs(JProgram program) {
+ private ReplaceRunAsyncs(TreeLogger logger, JProgram program) {
+ this.logger = logger;
this.program = program;
}
- private void execImpl() {
+ private TreeLogger error(String message) {
+ errorsFound = true;
+ return logger.branch(TreeLogger.ERROR, message);
+ }
+
+ private void execImpl() throws UnableToCompleteException {
AsyncCreateVisitor visitor = new AsyncCreateVisitor();
visitor.accept(program);
setNumEntriesInAsyncFragmentLoader(visitor.entryCount);
program.setRunAsyncReplacements(runAsyncReplacements);
+ new ReplaceRunAsyncResources().accept(program);
+ if (errorsFound) {
+ throw new UnableToCompleteException();
+ }
}
private JClassType getFragmentLoader(int fragmentNumber) {
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
index 880affb..87f44a6 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
@@ -15,7 +15,10 @@
*/
package com.google.gwt.sample.showcase.client;
+import static com.google.gwt.core.client.prefetch.RunAsyncCode.runAsyncCode;
+
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.prefetch.Prefetcher;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.http.client.Request;
@@ -26,6 +29,42 @@
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.sample.showcase.client.content.i18n.CwConstantsExample;
+import com.google.gwt.sample.showcase.client.content.i18n.CwConstantsWithLookupExample;
+import com.google.gwt.sample.showcase.client.content.i18n.CwDateTimeFormat;
+import com.google.gwt.sample.showcase.client.content.i18n.CwDictionaryExample;
+import com.google.gwt.sample.showcase.client.content.i18n.CwMessagesExample;
+import com.google.gwt.sample.showcase.client.content.i18n.CwNumberFormat;
+import com.google.gwt.sample.showcase.client.content.i18n.CwPluralFormsExample;
+import com.google.gwt.sample.showcase.client.content.lists.CwListBox;
+import com.google.gwt.sample.showcase.client.content.lists.CwMenuBar;
+import com.google.gwt.sample.showcase.client.content.lists.CwStackPanel;
+import com.google.gwt.sample.showcase.client.content.lists.CwSuggestBox;
+import com.google.gwt.sample.showcase.client.content.lists.CwTree;
+import com.google.gwt.sample.showcase.client.content.other.CwAnimation;
+import com.google.gwt.sample.showcase.client.content.other.CwCookies;
+import com.google.gwt.sample.showcase.client.content.panels.CwAbsolutePanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwDecoratorPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwDisclosurePanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwDockPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwFlowPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwHorizontalPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwHorizontalSplitPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwTabPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwVerticalPanel;
+import com.google.gwt.sample.showcase.client.content.panels.CwVerticalSplitPanel;
+import com.google.gwt.sample.showcase.client.content.popups.CwBasicPopup;
+import com.google.gwt.sample.showcase.client.content.popups.CwDialogBox;
+import com.google.gwt.sample.showcase.client.content.tables.CwFlexTable;
+import com.google.gwt.sample.showcase.client.content.tables.CwGrid;
+import com.google.gwt.sample.showcase.client.content.text.CwBasicText;
+import com.google.gwt.sample.showcase.client.content.text.CwRichText;
+import com.google.gwt.sample.showcase.client.content.widgets.CwBasicButton;
+import com.google.gwt.sample.showcase.client.content.widgets.CwCustomButton;
+import com.google.gwt.sample.showcase.client.content.widgets.CwDatePicker;
+import com.google.gwt.sample.showcase.client.content.widgets.CwFileUpload;
+import com.google.gwt.sample.showcase.client.content.widgets.CwHyperlink;
+import com.google.gwt.sample.showcase.client.content.widgets.CwRadioButton;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -36,7 +75,9 @@
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -51,14 +92,11 @@
* first time the {@link Widget} is added to the page.. The data in the source
* and css tabs are loaded using an RPC call to the server.
* </p>
- * <h3>CSS Style Rules</h3>
- * <ul class="css">
- * <li>.sc-ContentWidget { Applied to the entire widget }</li>
- * <li>.sc-ContentWidget-tabBar { Applied to the TabBar }</li>
- * <li>.sc-ContentWidget-deckPanel { Applied to the DeckPanel }</li>
- * <li>.sc-ContentWidget-name { Applied to the name }</li>
- * <li>.sc-ContentWidget-description { Applied to the description }</li>
- * </ul>
+ * <h3>CSS Style Rules</h3> <ul class="css"> <li>.sc-ContentWidget { Applied to
+ * the entire widget }</li> <li>.sc-ContentWidget-tabBar { Applied to the TabBar
+ * }</li> <li>.sc-ContentWidget-deckPanel { Applied to the DeckPanel }</li> <li>
+ * .sc-ContentWidget-name { Applied to the name }</li> <li>
+ * .sc-ContentWidget-description { Applied to the description }</li> </ul>
*/
public abstract class ContentWidget extends LazyPanel implements
SelectionHandler<Integer> {
@@ -83,6 +121,14 @@
*/
private static String loadingImage;
+ private static <T> List<T> list(T... elems) {
+ List<T> list = new ArrayList<T>(elems.length);
+ for (T elem : elems) {
+ list.add(elem);
+ }
+ return list;
+ }
+
/**
* The tab bar of options.
*/
@@ -327,6 +373,59 @@
}
}
+ protected void prefetchInternationalization() {
+ Prefetcher.prefetch(list(runAsyncCode(CwNumberFormat.class),
+ runAsyncCode(CwDateTimeFormat.class),
+ runAsyncCode(CwMessagesExample.class),
+ runAsyncCode(CwPluralFormsExample.class),
+ runAsyncCode(CwConstantsExample.class),
+ runAsyncCode(CwConstantsWithLookupExample.class),
+ runAsyncCode(CwDictionaryExample.class)));
+ }
+
+ protected void prefetchListsAndMenus() {
+ Prefetcher.prefetch(list(runAsyncCode(CwListBox.class),
+ runAsyncCode(CwSuggestBox.class), runAsyncCode(CwTree.class),
+ runAsyncCode(CwMenuBar.class), runAsyncCode(CwStackPanel.class)));
+ }
+
+ protected void prefetchOther() {
+ Prefetcher.prefetch(list(runAsyncCode(CwAnimation.class),
+ runAsyncCode(CwCookies.class)));
+ }
+
+ protected void prefetchPanels() {
+ Prefetcher.prefetch(list(runAsyncCode(CwDecoratorPanel.class),
+ runAsyncCode(CwFlowPanel.class), runAsyncCode(CwHorizontalPanel.class),
+ runAsyncCode(CwVerticalPanel.class),
+ runAsyncCode(CwAbsolutePanel.class), runAsyncCode(CwDockPanel.class),
+ runAsyncCode(CwDisclosurePanel.class), runAsyncCode(CwTabPanel.class),
+ runAsyncCode(CwHorizontalSplitPanel.class),
+ runAsyncCode(CwVerticalSplitPanel.class)));
+ }
+
+ protected void prefetchPopups() {
+ Prefetcher.prefetch(list(runAsyncCode(CwBasicPopup.class),
+ runAsyncCode(CwDialogBox.class)));
+ }
+
+ protected void prefetchTables() {
+ Prefetcher.prefetch(list(runAsyncCode(CwGrid.class),
+ runAsyncCode(CwFlexTable.class)));
+ }
+
+ protected void prefetchTextInput() {
+ Prefetcher.prefetch(list(runAsyncCode(CwBasicText.class),
+ runAsyncCode(CwRichText.class)));
+ }
+
+ protected void prefetchWidgets() {
+ Prefetcher.prefetch(list(runAsyncCode(CwRadioButton.class),
+ runAsyncCode(CwBasicButton.class), runAsyncCode(CwCustomButton.class),
+ runAsyncCode(CwFileUpload.class), runAsyncCode(CwDatePicker.class),
+ runAsyncCode(CwHyperlink.class)));
+ }
+
/**
* Load the contents of a remote file into the specified widget.
*
@@ -374,6 +473,12 @@
}
/**
+ * Start prefetches for this widget that are likely to show up after this
+ * widget is clicked.
+ */
+ protected abstract void setRunAsyncPrefetches();
+
+ /**
* Ensure that the demo widget has been initialized. Note that initialization
* can fail if there is a network failure.
*/
@@ -401,5 +506,7 @@
onInitializeComplete();
}
});
+
+ setRunAsyncPrefetches();
}
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
index 190821b..ad715e9 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.prefetch.Prefetcher;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.HeadElement;
import com.google.gwt.dom.client.Node;
@@ -228,6 +229,9 @@
app.getMainMenu().ensureSelectedItemVisible();
displayContentWidget(itemWidgets.get(firstItem));
}
+
+ // Always prefetch
+ Prefetcher.start();
}
/**
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsExample.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsExample.java
index 7c9e6ed..7f93c62 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsExample.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsExample.java
@@ -40,7 +40,7 @@
/**
* Example file.
*/
-@ShowcaseRaw({"ExampleConstants.java", "ExampleConstants.properties"})
+@ShowcaseRaw( {"ExampleConstants.java", "ExampleConstants.properties"})
public class CwConstantsExample extends ContentWidget {
/**
* The constants used in this Content Widget.
@@ -195,7 +195,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwConstantsExample.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -207,6 +207,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Add a tab to this example to show the messages interface.
*/
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsWithLookupExample.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsWithLookupExample.java
index 5d09515..5b4f386 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsWithLookupExample.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwConstantsWithLookupExample.java
@@ -41,7 +41,7 @@
/**
* Example file.
*/
-@ShowcaseRaw({"ColorConstants.java", "ColorConstants.properties"})
+@ShowcaseRaw( {"ColorConstants.java", "ColorConstants.properties"})
public class CwConstantsWithLookupExample extends ContentWidget {
/**
* The constants used in this Content Widget.
@@ -217,7 +217,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwConstantsWithLookupExample.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -229,6 +229,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Add a tab to this example to show the messages interface.
*/
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDateTimeFormat.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDateTimeFormat.java
index 677bcb9..6a6c7d0 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDateTimeFormat.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDateTimeFormat.java
@@ -187,7 +187,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDateTimeFormat.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -199,6 +199,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Show an error message. Pass in null to clear the error message.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDictionaryExample.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDictionaryExample.java
index 16a02bf..54a9638 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDictionaryExample.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwDictionaryExample.java
@@ -123,7 +123,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDictionaryExample.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -134,4 +134,10 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwMessagesExample.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwMessagesExample.java
index 6e58216..4708ecb 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwMessagesExample.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwMessagesExample.java
@@ -40,7 +40,7 @@
/**
* Example file.
*/
-@ShowcaseRaw({"ErrorMessages.java", "ErrorMessages.properties"})
+@ShowcaseRaw( {"ErrorMessages.java", "ErrorMessages.properties"})
public class CwMessagesExample extends ContentWidget {
/**
* The constants used in this Content Widget.
@@ -249,7 +249,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwMessagesExample.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -261,6 +261,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Add a tab to this example to show the {@link ErrorMessages} source code.
*/
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwNumberFormat.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwNumberFormat.java
index b90490b..058bc60 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwNumberFormat.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwNumberFormat.java
@@ -177,7 +177,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwNumberFormat.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -189,6 +189,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Show an error message. Pass in null to clear the error message.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwPluralFormsExample.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwPluralFormsExample.java
index c4f3696..93a6cdb 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwPluralFormsExample.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/i18n/CwPluralFormsExample.java
@@ -73,12 +73,6 @@
private CwConstants constants;
/**
- * The plural messages used in this example.
- */
- @ShowcaseData
- private PluralMessages pluralMessages = null;
-
- /**
* The {@link Label} used to display the message.
*/
@ShowcaseData
@@ -96,6 +90,12 @@
private HTML javaWidget = null;
/**
+ * The plural messages used in this example.
+ */
+ @ShowcaseData
+ private PluralMessages pluralMessages = null;
+
+ /**
* Indicates whether or not we have loaded the {@link ErrorMessages}
* properties source yet.
*/
@@ -213,7 +213,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwPluralFormsExample.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -225,6 +225,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchInternationalization();
+ }
+
/**
* Add a tab to this example to show the {@link PluralMessages} source code.
*/
@@ -250,5 +255,4 @@
// Ignore.
}
}
-
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwListBox.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwListBox.java
index ed632cc..70dfad3 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwListBox.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwListBox.java
@@ -137,7 +137,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwListBox.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -149,6 +149,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchListsAndMenus();
+ }
+
/**
* Display the options for a given category in the list box.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwMenuBar.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwMenuBar.java
index cc22c0d..fa49db1 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwMenuBar.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwMenuBar.java
@@ -32,7 +32,7 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-MenuBar", ".gwt-MenuBarPopup", "html>body .gwt-MenuBarPopup",
"* html .gwt-MenuBarPopup"})
public class CwMenuBar extends ContentWidget {
@@ -168,7 +168,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwMenuBar.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -179,4 +179,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchListsAndMenus();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwStackPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwStackPanel.java
index f14c106..201f22a 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwStackPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwStackPanel.java
@@ -42,7 +42,7 @@
/**
* Example file.
*/
-@ShowcaseStyle(value = {
+@ShowcaseStyle( {
".gwt-DecoratedStackPanel", "html>body .gwt-DecoratedStackPanel",
"* html .gwt-DecoratedStackPanel", ".cw-StackPanelHeader"})
public class CwStackPanel extends ContentWidget {
@@ -171,7 +171,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwStackPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -183,6 +183,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchListsAndMenus();
+ }
+
private void addItem(TreeItem root, ImageResource image, String label) {
root.addItem(AbstractImagePrototype.create(image).getHTML() + " " + label);
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwSuggestBox.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwSuggestBox.java
index 1d720be..56b86d5 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwSuggestBox.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwSuggestBox.java
@@ -32,10 +32,11 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-SuggestBox", ".gwt-SuggestBoxPopup",
"html>body .gwt-SuggestBoxPopup", "* html .gwt-SuggestBoxPopup"})
public class CwSuggestBox extends ContentWidget {
+
/**
* The constants used in this Content Widget.
*/
@@ -103,7 +104,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwSuggestBox.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -114,4 +115,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchListsAndMenus();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwTree.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwTree.java
index a17ae8a..2196e0e 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwTree.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/lists/CwTree.java
@@ -156,7 +156,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwTree.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -168,6 +168,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchListsAndMenus();
+ }
+
/**
* Add a new section of music created by a specific composer.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwAnimation.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwAnimation.java
index d861a59..1ed6be3 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwAnimation.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwAnimation.java
@@ -245,7 +245,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwAnimation.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -257,6 +257,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchOther();
+ }
+
/**
* Create an options panel that allows users to select a widget and reposition
* it.
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwCookies.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwCookies.java
index 882b712..9e2fe5f 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwCookies.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwCookies.java
@@ -199,7 +199,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwCookies.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -211,6 +211,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchOther();
+ }
+
/**
* Refresh the list of existing cookies.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwFrame.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwFrame.java
index 2e14568..35aa958 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwFrame.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/other/CwFrame.java
@@ -129,7 +129,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwFrame.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -140,4 +140,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwAbsolutePanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwAbsolutePanel.java
index d775dcb..d022c54 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwAbsolutePanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwAbsolutePanel.java
@@ -198,7 +198,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwAbsolutePanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -210,6 +210,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
+
/**
* Create an options panel that allows users to select a widget and reposition
* it.
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDecoratorPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDecoratorPanel.java
index 71bb6ee..34534d9 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDecoratorPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDecoratorPanel.java
@@ -33,7 +33,7 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-DecoratorPanel", "html>body .gwt-DecoratorPanel",
"* html .gwt-DecoratorPanel"})
public class CwDecoratorPanel extends ContentWidget {
@@ -116,7 +116,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDecoratorPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -127,4 +127,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDisclosurePanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDisclosurePanel.java
index 4250a27..afec53b 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDisclosurePanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDisclosurePanel.java
@@ -108,7 +108,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDisclosurePanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -120,6 +120,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
+
/**
* Create a form that contains undisclosed advanced options.
*/
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDockPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDockPanel.java
index 60677f0..b033490 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDockPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwDockPanel.java
@@ -118,7 +118,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDockPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -129,4 +129,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwFlowPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwFlowPanel.java
index dd19fa5..fae2ca8 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwFlowPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwFlowPanel.java
@@ -94,7 +94,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwFlowPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -105,4 +105,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalPanel.java
index 2f49674..77e6273 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalPanel.java
@@ -96,7 +96,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwHorizontalPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -107,4 +107,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalSplitPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalSplitPanel.java
index b989453..5c5b6a8 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalSplitPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwHorizontalSplitPanel.java
@@ -102,7 +102,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwHorizontalSplitPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -113,4 +113,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwTabPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwTabPanel.java
index e8c8234..a6a0bf6 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwTabPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwTabPanel.java
@@ -33,7 +33,7 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-DecoratedTabBar", "html>body .gwt-DecoratedTabBar",
"* html .gwt-DecoratedTabBar", ".gwt-TabPanel"})
public class CwTabPanel extends ContentWidget {
@@ -113,7 +113,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwTabPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -124,4 +124,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalPanel.java
index 01446bd..8840041 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalPanel.java
@@ -96,7 +96,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwVerticalPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -107,4 +107,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalSplitPanel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalSplitPanel.java
index 0f60296..4febaf5 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalSplitPanel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/panels/CwVerticalSplitPanel.java
@@ -102,7 +102,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwVerticalSplitPanel.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -113,4 +113,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPanels();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwBasicPopup.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwBasicPopup.java
index 2f81461..8966a5e 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwBasicPopup.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwBasicPopup.java
@@ -37,7 +37,7 @@
/**
* Example file.
*/
-@ShowcaseStyle(value = {
+@ShowcaseStyle( {
".gwt-PopupPanel", "html>body .gwt-PopupPanel", "* html .gwt-PopupPanel",
".gwt-DecoratedPopupPanel", "html>body .gwt-DecoratedPopupPanel",
"* html .gwt-DecoratedPopupPanel"})
@@ -148,7 +148,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwBasicPopup.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -159,4 +159,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPopups();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
index b13b6f2..e9ed1c1 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
@@ -137,7 +137,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDialogBox.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -149,6 +149,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchPopups();
+ }
+
/**
* Create the dialog box for this example.
*
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwFlexTable.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwFlexTable.java
index 7e4422a..d6d1b82 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwFlexTable.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwFlexTable.java
@@ -137,7 +137,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwFlexTable.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -149,6 +149,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchTables();
+ }
+
/**
* Add a row to the flex table.
*/
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwGrid.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwGrid.java
index beea730..37b9cf5 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwGrid.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/tables/CwGrid.java
@@ -98,7 +98,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwGrid.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -109,4 +109,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchTables();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
index 9829e3e..d6979c8 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
@@ -135,7 +135,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwBasicText.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -147,9 +147,14 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchTextInput();
+ }
+
/**
- * Create a TextBox example that includes the text box and an optional
- * handler that updates a Label with the currently selected text.
+ * Create a TextBox example that includes the text box and an optional handler
+ * that updates a Label with the currently selected text.
*
* @param textBox the text box to handle
* @param addSelection add handlers to update label
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwRichText.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwRichText.java
index 2e15e28..1231df4 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwRichText.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwRichText.java
@@ -30,7 +30,7 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-RichTextArea", ".hasRichTextToolbar", ".gwt-RichTextToolbar",
".cw-RichText"})
public class CwRichText extends ContentWidget {
@@ -95,7 +95,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwRichText.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -106,4 +106,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchTextInput();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwBasicButton.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwBasicButton.java
index 1217cd3..2a90e3e 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwBasicButton.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwBasicButton.java
@@ -110,7 +110,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwBasicButton.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -121,4 +121,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCheckBox.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCheckBox.java
index 1322790..3c1ba83 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCheckBox.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCheckBox.java
@@ -110,4 +110,9 @@
*/
callback.onSuccess(onInitialize());
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCustomButton.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCustomButton.java
index f8ce454..150962e 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCustomButton.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwCustomButton.java
@@ -35,7 +35,7 @@
/**
* Example file.
*/
-@ShowcaseStyle({".gwt-PushButton", ".gwt-ToggleButton"})
+@ShowcaseStyle( {".gwt-PushButton", ".gwt-ToggleButton"})
public class CwCustomButton extends ContentWidget {
/**
* The constants used in this Content Widget.
@@ -129,7 +129,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwCustomButton.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -140,4 +140,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwDatePicker.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwDatePicker.java
index 5428f13..b81f77d 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwDatePicker.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwDatePicker.java
@@ -38,10 +38,11 @@
/**
* Example file.
*/
-@ShowcaseStyle({
+@ShowcaseStyle( {
".gwt-DatePicker", ".datePicker", "td.datePickerMonth", ".gwt-DateBox",
".dateBox"})
public class CwDatePicker extends ContentWidget {
+
/**
* The constants used in this Content Widget.
*/
@@ -120,7 +121,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwDatePicker.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -131,4 +132,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwFileUpload.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwFileUpload.java
index f6b84f7..ffd7a1a 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwFileUpload.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwFileUpload.java
@@ -120,7 +120,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwFileUpload.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -131,4 +131,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwHyperlink.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwHyperlink.java
index 2ed3d5a..1500689 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwHyperlink.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwHyperlink.java
@@ -102,7 +102,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwHyperlink.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -114,6 +114,11 @@
});
}
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
+
/**
* Get a {@link Hyperlink} to a section based on the name of the
* {@link ContentWidget} example.
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwRadioButton.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwRadioButton.java
index c59aa79..13bfe36 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwRadioButton.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/widgets/CwRadioButton.java
@@ -121,7 +121,7 @@
@Override
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
- GWT.runAsync(new RunAsyncCallback() {
+ GWT.runAsync(CwRadioButton.class, new RunAsyncCallback() {
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -132,4 +132,9 @@
}
});
}
+
+ @Override
+ protected void setRunAsyncPrefetches() {
+ prefetchWidgets();
+ }
}
diff --git a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
index 7c02f04..e5070bf 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -20,11 +20,10 @@
import com.google.gwt.xhr.client.XMLHttpRequest;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
/**
* <p>
@@ -167,11 +166,12 @@
}
/**
- * Handles a failure to download a fragment in the initial sequence.
+ * Internal load error handler. This calls all user-provided error handlers
+ * and cancels all pending downloads.
*/
- private class InitialFragmentDownloadFailed implements LoadErrorHandler {
+ private class ResetAfterDownloadFailure implements LoadErrorHandler {
public void loadFailed(Throwable reason) {
- initialFragmentsLoading = false;
+ assert fragmentLoading >= 0;
// Cancel all pending downloads.
@@ -181,26 +181,21 @@
*/
List<LoadErrorHandler> handlersToRun = new ArrayList<LoadErrorHandler>();
- // add handlers that are waiting pending the initials download
- assert waitingForInitialFragments.size() == waitingForInitialFragmentsErrorHandlers.size();
- while (waitingForInitialFragments.size() > 0) {
- handlersToRun.add(waitingForInitialFragmentsErrorHandlers.remove());
- waitingForInitialFragments.remove();
- }
-
/*
- * Call clear() here so that waitingForInitialFragments makes all of its
- * space available for later requests.
+ * Call clear() here so that requestedExclusives makes all of its space
+ * available for later requests.
*/
- waitingForInitialFragments.clear();
+ requestedExclusives.clear();
- // add handlers for pending initial fragment downloads
- handlersToRun.addAll(initialFragmentErrorHandlers.values());
- initialFragmentErrorHandlers.clear();
+ // add handlers for pending downloads
+ handlersToRun.addAll(pendingDownloadErrorHandlers.values());
+ pendingDownloadErrorHandlers.clear();
+
+ fragmentLoading = -1;
/*
- * If an exception is thrown while canceling any of them, remember and
- * throw the last one.
+ * Run the handlers. If an exception is thrown while canceling any of
+ * them, remember and throw the last one.
*/
RuntimeException lastException = null;
@@ -311,12 +306,13 @@
private native void gwtInstallCode(String text) /*-{
__gwtInstallCode(text);
}-*/;
-
+
/**
* Call the linker-supplied __gwtStartLoadingFragment function. It should
- * either start the download and return null or undefined, or it should return
- * a URL that should be downloaded to get the code. If it starts the download
- * itself, it can synchronously load it, e.g. from cache, if that makes sense.
+ * either start the download and return null or undefined, or it should
+ * return a URL that should be downloaded to get the code. If it starts the
+ * download itself, it can synchronously load it, e.g. from cache, if that
+ * makes sense.
*/
private native String gwtStartLoadingFragment(int fragment,
LoadErrorHandler loadErrorHandler) /*-{
@@ -357,17 +353,9 @@
}
/**
- * Error handlers for failure to download an initial fragment.
- *
- * TODO(spoon) make it a lightweight integer map
+ * The fragment currently loading, or -1 if there aren't any.
*/
- private Map<Integer, LoadErrorHandler> initialFragmentErrorHandlers = new HashMap<Integer, LoadErrorHandler>();
-
- /**
- * Indicates that the next fragment in {@link #remainingInitialFragments} is
- * currently downloading.
- */
- private boolean initialFragmentsLoading = false;
+ private int fragmentLoading = -1;
/**
* The sequence of fragments to load initially, before anything else can be
@@ -379,7 +367,12 @@
*/
private final int[] initialLoadSequence;
- private LoadingStrategy loadingStrategy = new XhrLoadingStrategy();
+ /**
+ * This array indicates which fragments have been successfully loaded.
+ */
+ private final boolean[] isLoaded;
+
+ private final LoadingStrategy loadingStrategy;
private final Logger logger;
@@ -390,25 +383,37 @@
private final int numEntries;
/**
+ * Externally provided handlers for all outstanding and queued download
+ * requests.
+ *
+ * TODO(spoon) make it a lightweight integer map
+ */
+ private Map<Integer, LoadErrorHandler> pendingDownloadErrorHandlers = new HashMap<Integer, LoadErrorHandler>();
+
+ /**
+ * Whether prefetching is currently enabled.
+ */
+ private boolean prefetching = false;
+
+ /**
+ * This queue has fragments that have been requested to be prefetched. If it's
+ * <code>null</code>, that indicates no prefetch requests, which should cause
+ * all of this class's prefetching code to drop out of the compiled output.
+ */
+ private BoundedIntQueue prefetchQueue = null;
+
+ /**
* Base fragments that remain to be downloaded. It is lazily initialized in
- * the first call to {@link #startLoadingNextInitial()}. It does include the
+ * the first call to {@link #startLoadingNextFragment()}. It does include the
* leftovers fragment.
*/
private BoundedIntQueue remainingInitialFragments = null;
/**
- * Split points that have been reached, but that cannot be downloaded until
- * the initial fragments finish downloading. TODO(spoon) use something lighter
- * than a LinkedList
+ * Exclusive fragments that have been requested but that are not yet
+ * downloading.
*/
- private final BoundedIntQueue waitingForInitialFragments;
-
- /**
- * Error handlers for the above queue.
- *
- * TODO(spoon) change this to a lightweight JS collection
- */
- private Queue<LoadErrorHandler> waitingForInitialFragmentsErrorHandlers = new LinkedList<LoadErrorHandler>();
+ private final BoundedIntQueue requestedExclusives;
public AsyncFragmentLoader(int numEntries, int[] initialLoadSequence,
LoadingStrategy loadingStrategy, Logger logger) {
@@ -416,7 +421,8 @@
this.initialLoadSequence = initialLoadSequence;
this.loadingStrategy = loadingStrategy;
this.logger = logger;
- waitingForInitialFragments = new BoundedIntQueue(numEntries + 1);
+ requestedExclusives = new BoundedIntQueue(numEntries + 1);
+ isLoaded = new boolean[numEntries + 1];
}
/**
@@ -424,57 +430,41 @@
*/
public void fragmentHasLoaded(int fragment) {
logFragmentLoaded(fragment);
+ pendingDownloadErrorHandlers.remove(fragment);
if (isInitial(fragment)) {
assert (fragment == remainingInitialFragments.peek());
remainingInitialFragments.remove();
- initialFragmentErrorHandlers.remove(fragment);
-
- startLoadingNextInitial();
}
+
+ assert (fragment == fragmentLoading);
+ fragmentLoading = -1;
+
+ assert !isLoaded[fragment];
+ isLoaded[fragment] = true;
+
+ startLoadingNextFragment();
}
/**
- * Loads the specified split point.
+ * Requests a load of the code for the specified split point. If the load
+ * fails, <code>loadErrorHandler</code> will be invoked. If it succeeds, then
+ * the code will be installed, and the code is expected to invoke its own
+ * on-success hooks, including a call to either
+ * {@link #leftoversFragmentHasLoaded()} or {@link #fragmentHasLoaded(int)}.
*
* @param splitPoint the split point whose code needs to be loaded
*/
public void inject(int splitPoint, LoadErrorHandler loadErrorHandler) {
-
- if (haveInitialFragmentsLoaded()) {
- /*
- * The initial fragments has loaded. Immediately start loading the
- * requested code.
- */
- logDownloadStart(splitPoint);
- startLoadingFragment(splitPoint, loadErrorHandler);
- return;
+ pendingDownloadErrorHandlers.put(splitPoint, loadErrorHandler);
+ if (!isInitial(splitPoint)) {
+ requestedExclusives.add(splitPoint);
}
+ startLoadingNextFragment();
+ }
- if (isInitial(splitPoint)) {
- /*
- * The loading of an initial fragment will happen via
- * startLoadingNextInitial(), so don't start it here. Do, however, record
- * the error handler.
- */
- initialFragmentErrorHandlers.put(splitPoint, loadErrorHandler);
- } else {
- /*
- * For a non-initial fragment, queue it for later loading, once the
- * initial fragments have all been loaded.
- */
-
- assert (waitingForInitialFragments.size() == waitingForInitialFragmentsErrorHandlers.size());
- waitingForInitialFragments.add(splitPoint);
- waitingForInitialFragmentsErrorHandlers.add(loadErrorHandler);
- }
-
- /*
- * Start the initial downloads if they aren't running already.
- */
- if (!initialFragmentsLoading) {
- startLoadingNextInitial();
- }
+ public boolean isAlreadyLoaded(int splitPoint) {
+ return isLoaded[splitPoint];
}
public void leftoversFragmentHasLoaded() {
@@ -488,6 +478,54 @@
logEventProgress(eventGroup, type, null, null);
}
+ /**
+ * Request that a sequence of split points be prefetched. Code for the split
+ * points in <code>splitPoints</code> will be downloaded and installed
+ * whenever there is nothing else to download. Each call to this method
+ * overwrites the entire prefetch queue with the newly specified one.
+ */
+ public void setPrefetchQueue(Collection<? extends Integer> splitPoints) {
+ if (prefetchQueue == null) {
+ prefetchQueue = new BoundedIntQueue(numEntries);
+ }
+ prefetchQueue.clear();
+ for (Integer sp : splitPoints) {
+ prefetchQueue.add(sp);
+ }
+ startLoadingNextFragment();
+ }
+
+ public void startPrefetching() {
+ prefetching = true;
+ startLoadingNextFragment();
+ }
+
+ public void stopPrefetching() {
+ prefetching = false;
+ }
+
+ private boolean anyPrefetchesRequested() {
+ return prefetching && prefetchQueue != null && prefetchQueue.size() > 0;
+ }
+
+ /**
+ * Clear out any inject and prefetch requests that are already loaded. Only
+ * remove items from the head of each queue; any stale entries later in the
+ * queue will be removed later.
+ */
+ private void clearRequestsAlreadyLoaded() {
+ while (requestedExclusives.size() > 0
+ && isLoaded[requestedExclusives.peek()]) {
+ pendingDownloadErrorHandlers.remove(requestedExclusives.remove());
+ }
+
+ if (prefetchQueue != null) {
+ while (prefetchQueue.size() > 0 && isLoaded[prefetchQueue.peek()]) {
+ prefetchQueue.remove();
+ }
+ }
+ }
+
private String downloadGroup(int fragment) {
return (fragment == leftoversFragment()) ? LwmLabels.LEFTOVERS_DOWNLOAD
: LwmLabels.downloadGroupForExclusive(fragment);
@@ -501,6 +539,20 @@
&& remainingInitialFragments.size() == 0;
}
+ /**
+ * Initialize {@link #remainingInitialFragments} if it isn't already.
+ */
+ private void initializeRemainingInitialFragments() {
+ if (remainingInitialFragments == null) {
+ remainingInitialFragments = new BoundedIntQueue(
+ initialLoadSequence.length + 1);
+ for (int sp : initialLoadSequence) {
+ remainingInitialFragments.add(sp);
+ }
+ remainingInitialFragments.add(leftoversFragment());
+ }
+ }
+
private boolean isInitial(int splitPoint) {
if (splitPoint == leftoversFragment()) {
return true;
@@ -536,57 +588,54 @@
logEventProgress(logGroup, LwmLabels.END, fragment, null);
}
- private void startLoadingFragment(int fragment,
- final LoadErrorHandler loadErrorHandler) {
- loadingStrategy.startLoadingFragment(fragment, loadErrorHandler);
+ private void startLoadingFragment(int fragment) {
+ assert (fragmentLoading < 0);
+ fragmentLoading = fragment;
+ logDownloadStart(fragment);
+ loadingStrategy.startLoadingFragment(fragment,
+ new ResetAfterDownloadFailure());
}
/**
- * Start downloading the next fragment in the initial sequence, if there are
- * any left.
+ * Start downloading the next fragment queued up, if there are any.
*/
- private void startLoadingNextInitial() {
- if (remainingInitialFragments == null) {
- // first call, so initialize remainingInitialFragments
- remainingInitialFragments = new BoundedIntQueue(
- initialLoadSequence.length + 1);
- for (int sp : initialLoadSequence) {
- remainingInitialFragments.add(sp);
- }
- remainingInitialFragments.add(leftoversFragment());
+ private void startLoadingNextFragment() {
+ if (fragmentLoading >= 0) {
+ // Already loading something
+ return;
}
- if (initialFragmentErrorHandlers.isEmpty()
- && waitingForInitialFragmentsErrorHandlers.isEmpty()
- && remainingInitialFragments.size() > 1) {
+ initializeRemainingInitialFragments();
+ clearRequestsAlreadyLoaded();
+
+ if (pendingDownloadErrorHandlers.isEmpty() && !anyPrefetchesRequested()) {
/*
- * No further requests are pending, and more than the leftovers fragment
- * is left outstanding. Stop loading stuff for now.
+ * Don't load anything if there aren't any requests outstanding.
*/
- initialFragmentsLoading = false;
return;
}
+ // Check if an initial needs downloading
if (remainingInitialFragments.size() > 0) {
- // start loading the next initial fragment
- initialFragmentsLoading = true;
- int nextSplitPoint = remainingInitialFragments.peek();
- logDownloadStart(nextSplitPoint);
- startLoadingFragment(nextSplitPoint, new InitialFragmentDownloadFailed());
+ startLoadingFragment(remainingInitialFragments.peek());
return;
}
- // all initials are finished
- initialFragmentsLoading = false;
assert (haveInitialFragmentsLoaded());
- // start loading any pending fragments
- assert (waitingForInitialFragments.size() == waitingForInitialFragmentsErrorHandlers.size());
- while (waitingForInitialFragments.size() > 0) {
- int nextSplitPoint = waitingForInitialFragments.remove();
- LoadErrorHandler handler = waitingForInitialFragmentsErrorHandlers.remove();
- logDownloadStart(nextSplitPoint);
- startLoadingFragment(nextSplitPoint, handler);
+ // Check if an exclusive is pending
+ if (requestedExclusives.size() > 0) {
+ startLoadingFragment(requestedExclusives.remove());
+ return;
}
+
+ // Check the prefetch queue
+ if (anyPrefetchesRequested()) {
+ startLoadingFragment(prefetchQueue.remove());
+ return;
+ }
+
+ // Nothing needed downloading after all?!
+ assert false;
}
}
diff --git a/user/src/com/google/gwt/core/client/prefetch/PrefetchableResource.java b/user/src/com/google/gwt/core/client/prefetch/PrefetchableResource.java
new file mode 100644
index 0000000..0360009
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/prefetch/PrefetchableResource.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 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.prefetch;
+
+/**
+ * A resource that can be prefetched via the {@link Prefetcher} class.
+ */
+public interface PrefetchableResource {
+}
diff --git a/user/src/com/google/gwt/core/client/prefetch/Prefetcher.java b/user/src/com/google/gwt/core/client/prefetch/Prefetcher.java
new file mode 100644
index 0000000..ed17450
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/prefetch/Prefetcher.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2009 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.prefetch;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class allows requesting the download of resources before they are
+ * strictly needed. See the classes that implement {@link PrefetchableResource}.
+ * Currently, the only supported resource type is {@link RunAsyncCode}.
+ */
+public class Prefetcher {
+ /**
+ * Specify which resources should be prefetched.
+ */
+ public static void prefetch(Iterable<? extends PrefetchableResource> resources) {
+ if (!GWT.isScript()) {
+ // Nothing to do in development mode
+ return;
+ }
+
+ List<Integer> runAsyncSplitPoints = new ArrayList<Integer>();
+
+ for (PrefetchableResource resource : resources) {
+ if (resource instanceof RunAsyncCode) {
+ RunAsyncCode resourceRunAsync = (RunAsyncCode) resource;
+ int splitPoint = resourceRunAsync.getSplitPoint();
+ if (splitPoint >= 0) { // Skip placeholders, which have a -1 split point
+ runAsyncSplitPoints.add(splitPoint);
+ }
+ continue;
+ }
+
+ throw new IllegalArgumentException("Unknown resource type: "
+ + resource.getClass());
+ }
+
+ AsyncFragmentLoader.BROWSER_LOADER.setPrefetchQueue(runAsyncSplitPoints);
+ }
+
+ /**
+ * Helper method to call {@link #prefetch(Iterable)} with a single resource.
+ */
+ public static void prefetch(PrefetchableResource resource) {
+ prefetch(Arrays.asList(resource));
+ }
+
+ /**
+ * Start prefetching.
+ */
+ public static void start() {
+ if (!GWT.isScript()) {
+ // Nothing to do in development mode
+ return;
+ }
+
+ AsyncFragmentLoader.BROWSER_LOADER.startPrefetching();
+ }
+
+ /**
+ * Stop prefetching.
+ */
+ public static void stop() {
+ if (!GWT.isScript()) {
+ // Nothing to do in development mode
+ return;
+ }
+
+ AsyncFragmentLoader.BROWSER_LOADER.stopPrefetching();
+ }
+}
diff --git a/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java b/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java
new file mode 100644
index 0000000..7290d96
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/prefetch/RunAsyncCode.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2009 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.prefetch;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader;
+
+/**
+ * A request to load the code for a
+ * {@link com.google.gwt.core.client.GWT#runAsync(Class, com.google.gwt.core.client.RunAsyncCallback)}
+ * split point.
+ */
+public class RunAsyncCode implements PrefetchableResource {
+ /**
+ * Create an instance for the split point named with the given class. The
+ * provided class must be a class literal.
+ */
+ public static RunAsyncCode runAsyncCode(Class<?> splitPoint) {
+ // This is a place holder for development mode.
+ return forSplitPointNumber(-1);
+ }
+
+ /**
+ * Not for direct use by application code. Calls to this method are created by
+ * the compiler.
+ */
+ static RunAsyncCode forSplitPointNumber(int splitPoint) {
+ return new RunAsyncCode(splitPoint);
+ }
+
+ private final int splitPoint;
+
+ private RunAsyncCode(int splitPoint) {
+ this.splitPoint = splitPoint;
+ }
+
+ public int getSplitPoint() {
+ return splitPoint;
+ }
+
+ /**
+ * Ask whether this code has already been loaded.
+ */
+ public boolean isLoaded() {
+ if (GWT.isScript()) {
+ return true;
+ }
+ return AsyncFragmentLoader.BROWSER_LOADER.isAlreadyLoaded(splitPoint);
+ }
+}
diff --git a/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java b/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
index 28cba81..145bb8c 100644
--- a/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
+++ b/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
@@ -21,6 +21,8 @@
import junit.framework.TestCase;
+import static java.util.Arrays.asList;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@@ -28,7 +30,10 @@
import java.util.Map;
import java.util.Queue;
-/** Tests the fragment loader. */
+/**
+ * Tests that the fragment loader requests the right fragments and logs the
+ * correct lightweight metrics under a variety of request patterns.
+ */
public class AsyncFragmentLoaderTest extends TestCase {
private static class MockErrorHandler implements LoadErrorHandler {
private boolean wasCalled = false;
@@ -179,7 +184,7 @@
public void testDownloadFailures() {
MockLoadStrategy reqs = new MockLoadStrategy();
MockProgressLogger progress = new MockProgressLogger();
- int numEntries = 6;
+ int numEntries = 10;
AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
1, 2, 3}, reqs, progress);
@@ -237,7 +242,6 @@
loadFailed(reqs, numEntries); // leftovers fails!
assertFalse(error3try2.getWasCalled()); // 3 should have succeeded
assertTrue(error5try1.getWasCalled()); // 5 failed
- reqs.errorHandlers.get(numEntries);
// now try 5 again, and have everything succeed
MockErrorHandler error5try2 = new MockErrorHandler();
@@ -255,6 +259,110 @@
assertFalse(error5try2.getWasCalled());
progress.assertEvent("download5", END, 5);
+ // try 6 but have it fail
+ MockErrorHandler error6try1 = new MockErrorHandler();
+ loader.inject(6, error6try1);
+ reqs.assertFragmentsRequested(6);
+ progress.assertEvent("download6", BEGIN, 6);
+
+ loadFailed(reqs, 6);
+ assertTrue(error6try1.getWasCalled());
+
+ // try 7 and have it succeed
+ loader.inject(7, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(7);
+ progress.assertEvent("download7", BEGIN, 7);
+
+ loader.fragmentHasLoaded(7);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download7", END, 7);
+
+ // try 6 again and have it succeed this time
+ MockErrorHandler error6try2 = new MockErrorHandler();
+ loader.inject(6, error6try2);
+ reqs.assertFragmentsRequested(6);
+ progress.assertEvent("download6", BEGIN, 6);
+
+ loader.fragmentHasLoaded(6);
+ reqs.assertFragmentsRequested();
+ assertFalse(error6try2.getWasCalled());
+ progress.assertEvent("download6", END, 6);
+
+ progress.assertNoEvents();
+ }
+
+ public void testExclusivesLoadSequentially1() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 6;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
+ new int[] {}, reqs, progress);
+
+ // Load fragment 1
+ loader.inject(1, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries); // leftovers
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+ loader.fragmentHasLoaded(numEntries);
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download1", BEGIN, 1);
+ loader.fragmentHasLoaded(1);
+ progress.assertEvent("download1", END, 1);
+ progress.assertNoEvents();
+
+ // Request 2 and 3 immediately
+ loader.inject(2, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("download2", BEGIN, 2);
+ loader.inject(3, NULL_ERROR_HANDLER);
+ progress.assertNoEvents(); // waiting on 2 to finish
+
+ // 2 loads, 3 should be requested
+ loader.fragmentHasLoaded(2);
+ reqs.assertFragmentsRequested(3);
+ progress.assertEvent("download2", END, 2);
+ progress.assertEvent("download3", BEGIN, 3);
+ progress.assertNoEvents();
+
+ // 3 loads
+ loader.fragmentHasLoaded(3);
+ progress.assertEvent("download3", END, 3);
+ progress.assertNoEvents();
+ }
+
+ public void testExclusivesLoadSequentially2() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 6;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
+ new int[] {}, reqs, progress);
+
+ // Request 1
+ loader.inject(1, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+
+ // Request 2, resulting in two fragments being queued behind the leftovers
+ // download
+ loader.inject(2, NULL_ERROR_HANDLER);
+ progress.assertNoEvents();
+
+ // Leftovers arrives, but only fragment 1 should initially be requested
+ loader.leftoversFragmentHasLoaded();
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download1", BEGIN, 1);
+ progress.assertNoEvents();
+
+ // fragment 1 arrives, 2 requested
+ loader.fragmentHasLoaded(1);
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("download1", END, 1);
+ progress.assertEvent("download2", BEGIN, 2);
+
+ // fragment 2 arrives, all done
+ loader.fragmentHasLoaded(2);
+ progress.assertEvent("download2", END, 2);
progress.assertNoEvents();
}
@@ -290,18 +398,18 @@
progress.assertEvent("download3", BEGIN, 3);
loader.fragmentHasLoaded(3);
- // last initial, so it should now request the leftovers
- reqs.assertFragmentsRequested(numEntries);
- progress.assertEvent("download3", END, 3);
- progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
-
- loader.fragmentHasLoaded(numEntries);
reqs.assertFragmentsRequested();
- progress.assertEvent(LEFTOVERS_DOWNLOAD, END, numEntries);
+ progress.assertEvent("download3", END, 3);
+ progress.assertNoEvents();
// check that exclusives now load
loader.inject(5, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
+
+ loader.fragmentHasLoaded(numEntries);
reqs.assertFragmentsRequested(5);
+ progress.assertEvent(LEFTOVERS_DOWNLOAD, END, numEntries);
progress.assertEvent("download5", BEGIN, 5);
loader.fragmentHasLoaded(5);
@@ -338,6 +446,228 @@
}
}
+ public void testPrefetch() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 20;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
+ 1, 2, 3}, reqs, progress);
+ loader.startPrefetching();
+ // request a prefetch of something in the initial load sequence
+ loader.setPrefetchQueue(asList(2));
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("download1", BEGIN, 1);
+
+ loader.fragmentHasLoaded(1);
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("download1", END, 1);
+ progress.assertEvent("download2", BEGIN, 2);
+
+ loader.fragmentHasLoaded(2);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download2", END, 2);
+ progress.assertNoEvents();
+ // request a prefetch of an exclusive
+ loader.setPrefetchQueue(asList(4));
+ reqs.assertFragmentsRequested(3);
+ progress.assertEvent("download3", BEGIN, 3);
+
+ loader.fragmentHasLoaded(3);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent("download3", END, 3);
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+
+ loader.leftoversFragmentHasLoaded();
+ reqs.assertFragmentsRequested(4);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download4", BEGIN, 4);
+
+ loader.fragmentHasLoaded(4);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download4", END, 4);
+ progress.assertNoEvents();
+ // request a prefetch, but check that an inject call takes priority
+ loader.setPrefetchQueue(asList(5,6));
+ reqs.assertFragmentsRequested(5);
+ progress.assertEvent("download5", BEGIN, 5);
+
+ loader.inject(7, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+
+ loader.fragmentHasLoaded(5);
+ reqs.assertFragmentsRequested(7);
+ progress.assertEvent("download5", END, 5);
+ progress.assertEvent("download7", BEGIN, 7);
+
+ loader.fragmentHasLoaded(7);
+ reqs.assertFragmentsRequested(6);
+ progress.assertEvent("download7", END, 7);
+ progress.assertEvent("download6", BEGIN, 6);
+
+ loader.fragmentHasLoaded(6);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download6", END, 6);
+ progress.assertNoEvents();
+ // request prefetches, then request different prefetches
+ loader.setPrefetchQueue(asList(8,9));
+ reqs.assertFragmentsRequested(8);
+ progress.assertEvent("download8", BEGIN, 8);
+ loader.setPrefetchQueue(asList(10));
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+
+ loader.fragmentHasLoaded(8);
+ reqs.assertFragmentsRequested(10);
+ progress.assertEvent("download8", END, 8);
+ progress.assertEvent("download10", BEGIN, 10);
+
+ loader.fragmentHasLoaded(10);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download10", END, 10);
+ progress.assertNoEvents();
+ // request prefetches that have already been loaded
+ loader.setPrefetchQueue(asList(1, 3, 7, 10));
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+ }
+
+ /**
+ * Prefetch initial split points out of order.
+ */
+ public void testPrefetchInitialsOutOfOrder() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 20;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
+ 1, 2, 3}, reqs, progress);
+ loader.startPrefetching();
+ // request a prefetch of something in the initial load sequence
+ loader.setPrefetchQueue(asList(3, 2, 1));
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("download1", BEGIN, 1);
+
+ loader.fragmentHasLoaded(1);
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("download1", END, 1);
+ progress.assertEvent("download2", BEGIN, 2);
+
+ loader.fragmentHasLoaded(2);
+ reqs.assertFragmentsRequested(3);
+ progress.assertEvent("download2", END, 2);
+ progress.assertEvent("download3", BEGIN, 3);
+
+ loader.fragmentHasLoaded(3);
+ progress.assertEvent("download3", END, 3);
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+
+ // check that the loader is in a sane state by downloading an exclusive
+ loader.inject(5, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+
+ loader.leftoversFragmentHasLoaded();
+ reqs.assertFragmentsRequested(5);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download5", BEGIN, 5);
+
+ loader.fragmentHasLoaded(5);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download5", END, 5);
+ progress.assertNoEvents();
+ }
+
+ /**
+ * Test that no prefetching happens if prefetching is turned off.
+ */
+ public void testPrefetchingDisabled() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 20;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
+ new int[] {}, reqs, progress);
+ loader.stopPrefetching();
+ // Prefetch 1, but leave prefetching off
+ loader.setPrefetchQueue(asList(1));
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+
+ // Inject 2, which should lead to leftovers and 2 loading
+ loader.inject(2, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+
+ loader.leftoversFragmentHasLoaded();
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download2", BEGIN, 2);
+
+ loader.fragmentHasLoaded(2);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download2", END, 2);
+ progress.assertNoEvents();
+
+ // Enable prefetching; now 1 should load
+ loader.startPrefetching();
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("download1", BEGIN, 1);
+
+ loader.fragmentHasLoaded(1);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download1", END, 1);
+ progress.assertNoEvents();
+ }
+
+ /**
+ * Test prefetching an item and then injecting it while the prefetch is in
+ * progress.
+ */
+ public void testPrefetchThenInjectOfSame() {
+ MockLoadStrategy reqs = new MockLoadStrategy();
+ MockProgressLogger progress = new MockProgressLogger();
+ int numEntries = 20;
+ AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
+ new int[] {}, reqs, progress);
+ loader.startPrefetching();
+
+ // Load the leftovers and one fragment
+ loader.inject(1, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested(numEntries);
+ progress.assertEvent("leftoversDownload", BEGIN, numEntries);
+
+ loader.leftoversFragmentHasLoaded();
+ reqs.assertFragmentsRequested(1);
+ progress.assertEvent("leftoversDownload", END, numEntries);
+ progress.assertEvent("download1", BEGIN, 1);
+
+ loader.fragmentHasLoaded(1);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download1", END, 1);
+ progress.assertNoEvents();
+ // Start prefetching a fragment
+ loader.setPrefetchQueue(asList(2));
+ reqs.assertFragmentsRequested(2);
+ progress.assertEvent("download2", BEGIN, 2);
+
+ // Inject the same fragment and another one
+ loader.inject(2, NULL_ERROR_HANDLER);
+ loader.inject(3, NULL_ERROR_HANDLER);
+ reqs.assertFragmentsRequested();
+ progress.assertNoEvents();
+
+ // Finish the fragment loads
+ loader.fragmentHasLoaded(2);
+ reqs.assertFragmentsRequested(3);
+ progress.assertEvent("download2", END, 2);
+ progress.assertEvent("download3", BEGIN, 3);
+
+ loader.fragmentHasLoaded(3);
+ reqs.assertFragmentsRequested();
+ progress.assertEvent("download3", END, 3);
+ progress.assertNoEvents();
+ }
+
/**
* A thorough exercise of loading with an initial load sequence specified.
*/