All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dom.client.StyleInjector Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2008 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.dom.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
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. 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 {

  /**
   * The DOM-compatible way of adding stylesheets. This implementation requires
   * the host HTML page to have a head element defined.
   */
  public static class StyleInjectorImpl {
    private static final StyleInjectorImpl IMPL = GWT.create(StyleInjectorImpl.class);

    private HeadElement head;

    public StyleElement injectStyleSheet(String contents) {
      StyleElement style = createElement(contents);
      getHead().appendChild(style);
      return style;
    }

    public StyleElement injectStyleSheetAtEnd(String contents) {
      return injectStyleSheet(contents);
    }

    public StyleElement injectStyleSheetAtStart(String contents) {
      StyleElement style = createElement(contents);
      getHead().insertBefore(style, head.getFirstChild());
      return style;
    }

    public void setContents(StyleElement style, String contents) {
      style.setInnerText(contents);
    }

    private StyleElement createElement(String contents) {
      StyleElement style = Document.get().createStyleElement();
      style.setPropertyString("language", "text/css");
      setContents(style, contents);
      return style;
    }

    private HeadElement getHead() {
      if (head == null) {
        Element elt = Document.get().getElementsByTagName("head").getItem(0);
        assert elt != null : "The host HTML page does not have a  element"
            + " which is required by StyleInjector";
        head = HeadElement.as(elt);
      }
      return head;
    }
  }

  /**
   * IE doesn't allow manipulation of a style element through DOM methods. There
   * is also a hard-coded limit on the number of times that createStyleSheet can
   * be called before IE6 starts throwing exceptions.
   */
  public static class StyleInjectorImplIE extends StyleInjectorImpl {

    /**
     * The maximum number of style tags that can be handled by IE.
     */
    private static final int MAX_STYLE_SHEETS = 30;
    
    /**
     * 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;
    }-*/;

    @Override
    public StyleElement injectStyleSheet(String contents) {
      int numStyles = getDocumentStyleCount(); 
      if (numStyles < MAX_STYLE_SHEETS) {
        // Just create a new style element and add it to the list
        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 < numStyles; i++) {
          int len = styleSheetLengths[i];
          if (len == 0) {
            // Cache the length
            len = styleSheetLengths[i] = getDocumentStyleSheetLength(i);
          }
          if (len <= shortestLen) {
            shortestLen = len;
            shortestIdx = i;
          }
        }
        styleSheetLengths[shortestIdx] += contents.length();
        return appendToStyleSheet(shortestIdx, contents, true); 
      }
    }

    @Override
    public StyleElement injectStyleSheetAtEnd(String contents) {
      int documentStyleCount = getDocumentStyleCount();
      if (documentStyleCount == 0) {
        return createNewStyleSheet(contents);
      }
    
      return appendToStyleSheet(documentStyleCount - 1, contents, true);
    }

    @Override
    public StyleElement injectStyleSheetAtStart(String contents) {
      if (getDocumentStyleCount() == 0) {
        return createNewStyleSheet(contents);
      }
    
      return appendToStyleSheet(0, contents, false); // prepend
    }

    public native void prependContents(StyleElement style, String contents) /*-{
      style.cssText = contents + style.cssText;
    }-*/;

    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 StyleElement createNewStyleSheet(String contents) {
      StyleElement style = createElement();
      style.setCssText(contents);
      return style;
    }
  }

  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 true 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 true 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 true 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) {
    toInject.push(contents);
    return flush(toInject);
  }

  /**
   * Add stylesheet data to the document as though it were declared after all
   * stylesheets previously created by {@link #injectStylesheet(String)}. 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 injectStylesheetAtEnd(String contents) {
    toInjectAtEnd.push(contents);
    return flush(toInjectAtEnd);
  }

  /**
   * Add stylesheet data to the document as though it were declared before any
   * stylesheet previously created by {@link #injectStylesheet(String)}. 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 injectStylesheetAtStart(String contents) {
    toInjectAtStart.unshift(contents);
    return flush(toInjectAtStart);
  }

  /**
   * Replace the contents of a previously-injected stylesheet. Updating the
   * stylesheet in-place is typically more efficient than removing a
   * previously-created element and adding a new one. This method should be used
   * with some caution as StyleInjector may recycle StyleElements on certain
   * browsers.
   * 
   * @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 which 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() {
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy