Remove caching of style sheets in IE6 (cache may be invalid when there are multiple modules)

Review at http://gwt-code-reviews.appspot.com/159804

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7694 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/dom/client/StyleElement.java b/user/src/com/google/gwt/dom/client/StyleElement.java
index 0a1da64..9f3e3d6 100644
--- a/user/src/com/google/gwt/dom/client/StyleElement.java
+++ b/user/src/com/google/gwt/dom/client/StyleElement.java
@@ -40,6 +40,13 @@
   }
 
   /**
+   * The CSS text.
+   */
+  public final native String getCssText() /*-{
+    return this.cssText;
+  }-*/;
+
+  /**
    * Enables/disables the style sheet.
    * @deprecated use {@link #isDisabled()} instead
    */
@@ -73,6 +80,13 @@
    }-*/;
 
   /**
+   * Sets the CSS text.
+   */
+  public final native void setCssText(String cssText) /*-{
+    this.cssText = cssText;
+  }-*/;
+
+  /**
    * Enables/disables the style sheet.
    */
   public final native void setDisabled(boolean disabled) /*-{
diff --git a/user/src/com/google/gwt/dom/client/StyleInjector.java b/user/src/com/google/gwt/dom/client/StyleInjector.java
index c1dabcd..673d6ac 100644
--- a/user/src/com/google/gwt/dom/client/StyleInjector.java
+++ b/user/src/com/google/gwt/dom/client/StyleInjector.java
@@ -17,8 +17,6 @@
 
 import com.google.gwt.core.client.GWT;
 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;
@@ -85,14 +83,29 @@
    */
   public static class StyleInjectorImplIE extends StyleInjectorImpl {
 
-    /*
-     * TODO(bobv) : Talk to scottb about being able to read this out of the
-     * module xml file as a configuration-property to handle cases where
-     * multiple GWT modules are living in the same page.
+    /**
+     * The maximum number of style tags that can be handled by IE.
      */
     private static final int MAX_STYLE_SHEETS = 30;
-    private static final JsArray<StyleElement> STYLE_ELEMENTS = JavaScriptObject.createArray().cast();
-    private static final JsArrayInteger STYLE_ELEMENT_LENGTHS = JavaScriptObject.createArray().cast();
+    
+    /**
+     * A cache of the lengths of the current style sheets.  A value of 0
+     * indicates that the length has not yet been retrieved.
+     */
+    private static int[] styleSheetLengths = new int[MAX_STYLE_SHEETS];
+
+    private static native int getDocumentStyleCount() /*-{
+      return $doc.styleSheets.length;
+    }-*/;
+
+    private static native StyleElement getDocumentStyleSheet(int index) /*-{
+      return $doc.styleSheets[index];
+    }-*/;
+
+    private static native int getDocumentStyleSheetLength(int index) /*-{
+      return $doc.styleSheets[index].cssText.length;
+    }-*/;
+
 
     public native void appendContents(StyleElement style, String contents) /*-{
       style.cssText += contents;
@@ -100,89 +113,81 @@
 
     @Override
     public StyleElement injectStyleSheet(String contents) {
-      int idx = STYLE_ELEMENTS.length();
-      if (getDocumentStyleCount() < MAX_STYLE_SHEETS) {
+      int numStyles = getDocumentStyleCount(); 
+      if (numStyles < MAX_STYLE_SHEETS) {
         // Just create a new style element and add it to the list
-        StyleElement style = createElement();
-        setContents(style, contents);
-        STYLE_ELEMENTS.set(idx, style);
-        STYLE_ELEMENT_LENGTHS.set(idx, contents.length());
-        return style;
+        return createNewStyleSheet(contents);
       } else {
         /*
          * Find shortest style element to minimize re-parse time in the general
          * case.
+         * 
+         * We cache the lengths of the style sheets in order to avoid expensive
+         * calls to retrieve their actual contents. Note that if another module
+         * or script makes changes to the style sheets that we are unaware of,
+         * the worst that will happen is that we will choose a style sheet to
+         * append to that is not actually of minimum size.
          */
         int shortestLen = Integer.MAX_VALUE;
         int shortestIdx = -1;
-        for (int i = 0; i < idx; i++) {
-          if (STYLE_ELEMENT_LENGTHS.get(i) < shortestLen) {
-            shortestLen = STYLE_ELEMENT_LENGTHS.get(i);
+        for (int i = 0; i < numStyles; i++) {
+          int len = styleSheetLengths[i];
+          if (len == 0) {
+            // Cache the length
+            len = styleSheetLengths[i] = getDocumentStyleSheetLength(i);
+          }
+          if (len <= shortestLen) {
+            shortestLen = len;
             shortestIdx = i;
           }
         }
-
-        /**
-         * 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?
-         */
-        assert shortestIdx != -1;
-
-        StyleElement style = STYLE_ELEMENTS.get(shortestIdx);
-        STYLE_ELEMENT_LENGTHS.set(shortestIdx, shortestLen + contents.length());
-        appendContents(style, contents);
-        return style;
+        styleSheetLengths[shortestIdx] += contents.length();
+        return appendToStyleSheet(shortestIdx, contents, true); 
       }
     }
 
     @Override
     public StyleElement injectStyleSheetAtEnd(String contents) {
-      if (STYLE_ELEMENTS.length() == 0) {
-        return injectStyleSheet(contents);
+      int documentStyleCount = getDocumentStyleCount();
+      if (documentStyleCount == 0) {
+        return createNewStyleSheet(contents);
       }
-
-      int idx = STYLE_ELEMENTS.length() - 1;
-      StyleElement style = STYLE_ELEMENTS.get(idx);
-      STYLE_ELEMENT_LENGTHS.set(idx, STYLE_ELEMENT_LENGTHS.get(idx)
-          + contents.length());
-      appendContents(style, contents);
-
-      return style;
+    
+      return appendToStyleSheet(documentStyleCount - 1, contents, true);
     }
 
     @Override
     public StyleElement injectStyleSheetAtStart(String contents) {
-      if (STYLE_ELEMENTS.length() == 0) {
-        return injectStyleSheet(contents);
+      if (getDocumentStyleCount() == 0) {
+        return createNewStyleSheet(contents);
       }
-
-      StyleElement style = STYLE_ELEMENTS.get(0);
-      STYLE_ELEMENT_LENGTHS.set(0, STYLE_ELEMENT_LENGTHS.get(0)
-          + contents.length());
-      prependContents(style, contents);
-
-      return style;
+    
+      return appendToStyleSheet(0, contents, false); // prepend
     }
 
     public native void prependContents(StyleElement style, String contents) /*-{
       style.cssText = contents + style.cssText;
     }-*/;
 
-    @Override
-    public native void setContents(StyleElement style, String contents) /*-{
-      style.cssText = contents;
-    }-*/;
+    private StyleElement appendToStyleSheet(int idx, String contents, boolean append) {
+      StyleElement style = getDocumentStyleSheet(idx);
+      if (append) {
+        appendContents(style, contents);
+      } else {
+        prependContents(style, contents);
+      }
+      return style;
+    }
 
     private native StyleElement createElement() /*-{
       return $doc.createStyleSheet();
     }-*/;
 
-    private native int getDocumentStyleCount() /*-{
-      return $doc.styleSheets.length;
-    }-*/;
+    private StyleElement createNewStyleSheet(String contents) {
+      StyleElement style = createElement();
+      style.setCssText(contents);
+      return style;
+    }
   }
 
   private static final JsArrayString toInject = JavaScriptObject.createArray().cast();