Make StyleInjector use Scheduler API.
Patch by: bobv
Review by: bruce
http://gwt-code-reviews.appspot.com/78816
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6403 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
index 0ff623d..f231dd5 100644
--- a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
+++ b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
@@ -177,7 +177,8 @@
}-*/;
public static void scheduleIncrementalImpl(RepeatingCommand cmd) {
- INCREMENTAL_COMMANDS.push(Task.create(cmd));
+ // Push repeating commands onto the same initial queue for relative order
+ DEFERRED_COMMANDS.push(Task.create(cmd));
maybeSchedulePostEventPumpCommands();
}
diff --git a/user/src/com/google/gwt/dom/client/StyleInjector.java b/user/src/com/google/gwt/dom/client/StyleInjector.java
index 1ec5e7c..c1dabcd 100644
--- a/user/src/com/google/gwt/dom/client/StyleInjector.java
+++ b/user/src/com/google/gwt/dom/client/StyleInjector.java
@@ -19,9 +19,15 @@
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
/**
- * Used to add stylesheets to the document.
+ * Used to add stylesheets to the document. The one-argument versions of
+ * {@link #inject}, {@link #injectAtEnd}, and {@link #injectAtStart} use
+ * {@link Scheduler#scheduleFinally} to minimize the number of individual style
+ * elements created.
*/
public class StyleInjector {
@@ -120,8 +126,8 @@
* This assertion can fail if the max number of style elements exist
* before this module can inject any style elements, so STYLE_ELEMENTS
* will be empty. However, the fix would degrade performance for the
- * general case.
- * TODO(jlabanca): Can we handle this scenario efficiently?
+ * general case. TODO(jlabanca): Can we handle this scenario
+ * efficiently?
*/
assert shortestIdx != -1;
@@ -179,15 +185,106 @@
}-*/;
}
+ private static final JsArrayString toInject = JavaScriptObject.createArray().cast();
+ private static final JsArrayString toInjectAtEnd = JavaScriptObject.createArray().cast();
+ private static final JsArrayString toInjectAtStart = JavaScriptObject.createArray().cast();
+
+ private static ScheduledCommand flusher = new ScheduledCommand() {
+ public void execute() {
+ if (needsInjection) {
+ flush(null);
+ }
+ }
+ };
+
+ private static boolean needsInjection = false;
+
+ /**
+ * Add a stylesheet to the document.
+ *
+ * @param css the CSS contents of the stylesheet
+ */
+ public static void inject(String css) {
+ inject(css, false);
+ }
+
+ /**
+ * Add a stylesheet to the document.
+ *
+ * @param css the CSS contents of the stylesheet
+ * @param immediate if <code>true</code> the DOM will be updated immediately
+ * instead of just before returning to the event loop. Using this
+ * option excessively will decrease performance, especially if used
+ * with an inject-css-on-init coding pattern
+ */
+ public static void inject(String css, boolean immediate) {
+ toInject.push(css);
+ inject(immediate);
+ }
+
+ /**
+ * Add stylesheet data to the document as though it were declared after all
+ * stylesheets previously created by {@link #inject(String)}.
+ *
+ * @param css the CSS contents of the stylesheet
+ */
+ public static void injectAtEnd(String css) {
+ injectAtEnd(css, false);
+ }
+
+ /**
+ * Add stylesheet data to the document as though it were declared after all
+ * stylesheets previously created by {@link #inject(String)}.
+ *
+ * @param css the CSS contents of the stylesheet
+ * @param immediate if <code>true</code> the DOM will be updated immediately
+ * instead of just before returning to the event loop. Using this
+ * option excessively will decrease performance, especially if used
+ * with an inject-css-on-init coding pattern
+ */
+ public static void injectAtEnd(String css, boolean immediate) {
+ toInjectAtEnd.push(css);
+ inject(immediate);
+ }
+
+ /**
+ * Add stylesheet data to the document as though it were declared before all
+ * stylesheets previously created by {@link #inject(String)}.
+ *
+ * @param css the CSS contents of the stylesheet
+ */
+ public static void injectAtStart(String css) {
+ injectAtStart(css, false);
+ }
+
+ /**
+ * Add stylesheet data to the document as though it were declared before all
+ * stylesheets previously created by {@link #inject(String)}.
+ *
+ * @param css the CSS contents of the stylesheet
+ * @param immediate if <code>true</code> the DOM will be updated immediately
+ * instead of just before returning to the event loop. Using this
+ * option excessively will decrease performance, especially if used
+ * with an inject-css-on-init coding pattern
+ */
+ public static void injectAtStart(String css, boolean immediate) {
+ toInjectAtStart.unshift(css);
+ inject(immediate);
+ }
+
/**
* Add a stylesheet to the document. The StyleElement returned by this method
* is not guaranteed to be unique.
*
* @param contents the CSS contents of the stylesheet
* @return the StyleElement that contains the newly-injected CSS
+ * @deprecated The returned StyleElement cannot be implemented consistently
+ * across all browsers
*/
+ @Deprecated
public static StyleElement injectStylesheet(String contents) {
- return StyleInjectorImpl.IMPL.injectStyleSheet(contents);
+ toInject.push(contents);
+ return flush(toInject);
}
/**
@@ -197,9 +294,13 @@
*
* @param contents the CSS contents of the stylesheet
* @return the StyleElement that contains the newly-injected CSS
+ * @deprecated The returned StyleElement cannot be implemented consistently
+ * across all browsers
*/
+ @Deprecated
public static StyleElement injectStylesheetAtEnd(String contents) {
- return StyleInjectorImpl.IMPL.injectStyleSheetAtEnd(contents);
+ toInjectAtEnd.push(contents);
+ return flush(toInjectAtEnd);
}
/**
@@ -209,9 +310,13 @@
*
* @param contents the CSS contents of the stylesheet
* @return the StyleElement that contains the newly-injected CSS
+ * @deprecated The returned StyleElement cannot be implemented consistently
+ * across all browsers
*/
+ @Deprecated
public static StyleElement injectStylesheetAtStart(String contents) {
- return StyleInjectorImpl.IMPL.injectStyleSheetAtStart(contents);
+ toInjectAtStart.unshift(contents);
+ return flush(toInjectAtStart);
}
/**
@@ -224,12 +329,68 @@
* @param style a StyleElement previously-returned from
* {@link #injectStylesheet(String)}.
* @param contents the new contents of the stylesheet.
+ * @deprecated The associated StyleElement cannot be implemented consistently
+ * across all browsers
*/
+ @Deprecated
public static void setContents(StyleElement style, String contents) {
StyleInjectorImpl.IMPL.setContents(style, contents);
}
/**
+ * The <code>which</code> parameter is used to support the deprecated API.
+ */
+ private static StyleElement flush(JavaScriptObject which) {
+ StyleElement toReturn = null;
+ StyleElement maybeReturn;
+
+ if (toInjectAtStart.length() != 0) {
+ String css = toInjectAtStart.join("");
+ maybeReturn = StyleInjectorImpl.IMPL.injectStyleSheetAtStart(css);
+ if (toInjectAtStart == which) {
+ toReturn = maybeReturn;
+ }
+ toInjectAtStart.setLength(0);
+ }
+
+ if (toInject.length() != 0) {
+ String css = toInject.join("");
+ maybeReturn = StyleInjectorImpl.IMPL.injectStyleSheet(css);
+ if (toInject == which) {
+ toReturn = maybeReturn;
+ }
+ toInject.setLength(0);
+ }
+
+ if (toInjectAtEnd.length() != 0) {
+ String css = toInjectAtEnd.join("");
+ maybeReturn = StyleInjectorImpl.IMPL.injectStyleSheetAtEnd(css);
+ if (toInjectAtEnd == which) {
+ toReturn = maybeReturn;
+ }
+ toInjectAtEnd.setLength(0);
+ }
+
+ needsInjection = false;
+ return toReturn;
+ }
+
+ private static void inject(boolean immediate) {
+ if (immediate) {
+ flush(null);
+ } else {
+ schedule();
+ }
+ }
+
+ private static void schedule() {
+ if (!needsInjection) {
+ needsInjection = true;
+ Scheduler.get().scheduleFinally(flusher);
+ }
+ }
+
+ /**
* Utility class.
*/
private StyleInjector() {
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index 0027209..adf2871 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -902,7 +902,7 @@
sw.indent();
sw.println("if (!injected) {");
sw.indentln("injected = true;");
- sw.indentln(StyleInjector.class.getName() + ".injectStylesheet(getText());");
+ sw.indentln(StyleInjector.class.getName() + ".inject(getText());");
sw.indentln("return true;");
sw.println("}");
sw.println("return false;");
diff --git a/user/test/com/google/gwt/dom/client/StyleInjectorTest.java b/user/test/com/google/gwt/dom/client/StyleInjectorTest.java
index 910d09d..0edb413 100644
--- a/user/test/com/google/gwt/dom/client/StyleInjectorTest.java
+++ b/user/test/com/google/gwt/dom/client/StyleInjectorTest.java
@@ -26,13 +26,16 @@
*/
public class StyleInjectorTest extends GWTTestCase {
+ private static final int TEST_DELAY = 500;
+
@Override
public String getModuleName() {
return "com.google.gwt.dom.DOMTest";
}
- @DoNotRunWith({Platform.Htmlunit})
- public void testStyleInjector() {
+ @DoNotRunWith(value = {Platform.Htmlunit})
+ @SuppressWarnings("deprecation")
+ public void testOldMethods() {
final DivElement elt = Document.get().createDivElement();
elt.setId("styleInjectorTest");
elt.setInnerHTML("Hello StyleInjector!");
@@ -43,7 +46,7 @@
StyleInjector.injectStylesheetAtEnd("#styleInjectorTest {height: 100px;}");
// We need to allow the document to be redrawn
- delayTestFinish(500);
+ delayTestFinish(TEST_DELAY);
DeferredCommand.addCommand(new Command() {
public void execute() {
@@ -59,8 +62,9 @@
/**
* Ensure that the IE createStyleSheet compatibility code is exercised.
*/
- @DoNotRunWith({Platform.Htmlunit})
- public void testLotsOfStyles() {
+ @DoNotRunWith(value = {Platform.Htmlunit})
+ @SuppressWarnings("deprecation")
+ public void testOldMethodsWithLotsOfStyles() {
StyleElement[] elements = new StyleElement[100];
for (int i = 0, j = elements.length; i < j; i++) {
elements[i] = StyleInjector.injectStylesheet("#styleInjectorTest" + i
@@ -78,7 +82,7 @@
Document.get().getBody().appendChild(elt);
// We need to allow the document to be redrawn
- delayTestFinish(500);
+ delayTestFinish(TEST_DELAY);
DeferredCommand.addCommand(new Command() {
public void execute() {
@@ -89,4 +93,49 @@
}
});
}
+
+ @DoNotRunWith(value = {Platform.Htmlunit})
+ public void testStyleInjectorBatched() {
+ testStyleInjector("testStyleInjectorBatched", false);
+ }
+
+ @DoNotRunWith(value = {Platform.Htmlunit})
+ public void testStyleInjectorImmediate() {
+ testStyleInjector("testStyleInjectorImmediate", true);
+ }
+
+ private void testStyleInjector(String testName, final boolean immediate) {
+
+ final DivElement elt = Document.get().createDivElement();
+ elt.setId(testName);
+ elt.setInnerHTML("Hello");
+ Document.get().getBody().appendChild(elt);
+
+ StyleInjector.inject("#" + testName
+ + " {position: absolute; left: 100px; width: 50px; height 50px;}",
+ immediate);
+ StyleInjector.injectAtStart("#" + testName
+ + " {left: 25px; width: 100px !important;}", immediate);
+ StyleInjector.injectAtEnd("#" + testName + " {height: 100px;}", immediate);
+
+ Command command = new Command() {
+ public void execute() {
+ assertEquals(100, elt.getOffsetLeft());
+ assertEquals(100, elt.getClientHeight());
+ assertEquals(100, elt.getClientWidth());
+
+ if (!immediate) {
+ finishTest();
+ }
+ }
+ };
+
+ if (immediate) {
+ command.execute();
+ } else {
+ DeferredCommand.addCommand(command);
+ // We need to allow the BatchedCommands to execute
+ delayTestFinish(TEST_DELAY);
+ }
+ }
}