Author: b...@google.com Date: Mon Apr 6 07:07:28 2009 New Revision: 5185 Added: trunk/user/src/com/google/gwt/dom/client/StyleInjector.java (contents, props changed) trunk/user/test/com/google/gwt/dom/client/StyleInjectorTest.java (contents, props changed) Modified: trunk/user/src/com/google/gwt/dom/DOM.gwt.xml trunk/user/test/com/google/gwt/dom/DOMSuite.java
Log: Initial add of StyleInjector. New StyleInjectorIE implementation based on work by tirsen and reuben. Patch by: bobv Review by: rjrjr, cromwellian Modified: trunk/user/src/com/google/gwt/dom/DOM.gwt.xml ============================================================================== --- trunk/user/src/com/google/gwt/dom/DOM.gwt.xml (original) +++ trunk/user/src/com/google/gwt/dom/DOM.gwt.xml Mon Apr 6 07:07:28 2009 @@ -44,4 +44,9 @@ <when-type-is class="com.google.gwt.dom.client.DOMImpl"/> <when-property-is name="user.agent" value="gecko"/> </replace-with> + + <replace-with class="com.google.gwt.dom.client.StyleInjector.StyleInjectorImplIE"> + <when-type-is class="com.google.gwt.dom.client.StyleInjector.StyleInjectorImpl"/> + <when-property-is name="user.agent" value="ie6"/> + </replace-with> </module> Added: trunk/user/src/com/google/gwt/dom/client/StyleInjector.java ============================================================================== --- (empty file) +++ trunk/user/src/com/google/gwt/dom/client/StyleInjector.java Mon Apr 6 07:07:28 2009 @@ -0,0 +1,225 @@ +/* + * 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.JsArray; +import com.google.gwt.core.client.JsArrayInteger; + +/** + * Used to add stylesheets to the document. + */ +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 <head> 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 { + + /* + * 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. + */ + 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(); + + public native void appendContents(StyleElement style, String contents) /*-{ + style.cssText += contents; + }-*/; + + @Override + public StyleElement injectStyleSheet(String contents) { + int idx = STYLE_ELEMENTS.length(); + if (idx < 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; + } else { + /* + * Find shortest style element to minimize re-parse time in the general + * case. + */ + 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); + shortestIdx = i; + } + } + assert shortestIdx != -1; + + StyleElement style = STYLE_ELEMENTS.get(shortestIdx); + STYLE_ELEMENT_LENGTHS.set(shortestIdx, shortestLen + contents.length()); + appendContents(style, contents); + return style; + } + } + + @Override + public StyleElement injectStyleSheetAtEnd(String contents) { + if (STYLE_ELEMENTS.length() == 0) { + return injectStyleSheet(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; + } + + @Override + public StyleElement injectStyleSheetAtStart(String contents) { + if (STYLE_ELEMENTS.length() == 0) { + return injectStyleSheet(contents); + } + + StyleElement style = STYLE_ELEMENTS.get(0); + STYLE_ELEMENT_LENGTHS.set(0, STYLE_ELEMENT_LENGTHS.get(0) + + contents.length()); + prependContents(style, contents); + + return style; + } + + 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 native StyleElement createElement() /*-{ + return $doc.createStyleSheet(); + }-*/; + } + + /** + * 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 + */ + public static StyleElement injectStylesheet(String contents) { + return StyleInjectorImpl.IMPL.injectStyleSheet(contents); + } + + /** + * 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 + */ + public static StyleElement injectStylesheetAtEnd(String contents) { + return StyleInjectorImpl.IMPL.injectStyleSheetAtEnd(contents); + } + + /** + * 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 + */ + public static StyleElement injectStylesheetAtStart(String contents) { + return StyleInjectorImpl.IMPL.injectStyleSheetAtStart(contents); + } + + /** + * 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. + */ + public static void setContents(StyleElement style, String contents) { + StyleInjectorImpl.IMPL.setContents(style, contents); + } + + /** + * Utility class. + */ + private StyleInjector() { + } +} Modified: trunk/user/test/com/google/gwt/dom/DOMSuite.java ============================================================================== --- trunk/user/test/com/google/gwt/dom/DOMSuite.java (original) +++ trunk/user/test/com/google/gwt/dom/DOMSuite.java Mon Apr 6 07:07:28 2009 @@ -21,6 +21,7 @@ import com.google.gwt.dom.client.MapTests; import com.google.gwt.dom.client.NodeTest; import com.google.gwt.dom.client.SelectTests; +import com.google.gwt.dom.client.StyleInjectorTest; import com.google.gwt.dom.client.TableTests; import com.google.gwt.junit.tools.GWTTestSuite; @@ -40,6 +41,7 @@ suite.addTestSuite(FormTests.class); suite.addTestSuite(MapTests.class); suite.addTestSuite(SelectTests.class); + suite.addTestSuite(StyleInjectorTest.class); suite.addTestSuite(TableTests.class); return suite; Added: trunk/user/test/com/google/gwt/dom/client/StyleInjectorTest.java ============================================================================== --- (empty file) +++ trunk/user/test/com/google/gwt/dom/client/StyleInjectorTest.java Mon Apr 6 07:07:28 2009 @@ -0,0 +1,88 @@ +/* + * 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.dom.client; + +import com.google.gwt.junit.client.GWTTestCase; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DeferredCommand; + +/** + * Tests StyleInjector by looking for effects of injected CSS on DOM elements. + */ +public class StyleInjectorTest extends GWTTestCase { + + @Override + public String getModuleName() { + return "com.google.gwt.dom.DOMTest"; + } + + public void testStyleInjector() { + final DivElement elt = Document.get().createDivElement(); + elt.setId("styleInjectorTest"); + elt.setInnerHTML("Hello StyleInjector!"); + Document.get().getBody().appendChild(elt); + + StyleInjector.injectStylesheet("#styleInjectorTest {position: absolute; left: 100px; width: 50px; height 50px;}"); + StyleInjector.injectStylesheetAtStart("#styleInjectorTest {left: 25px; width: 100px !important;}"); + StyleInjector.injectStylesheetAtEnd("#styleInjectorTest {height: 100px;}"); + + // We need to allow the document to be redrawn + delayTestFinish(500); + + DeferredCommand.addCommand(new Command() { + public void execute() { + assertEquals(100, elt.getOffsetLeft()); + assertEquals(100, elt.getClientHeight()); + assertEquals(100, elt.getClientWidth()); + + finishTest(); + } + }); + } + + /** + * Ensure that the IE createStyleSheet compatibility code is exercised. + */ + public void testLotsOfStyles() { + StyleElement[] elements = new StyleElement[100]; + for (int i = 0, j = elements.length; i < j; i++) { + elements[i] = StyleInjector.injectStylesheet("#styleInjectorTest" + i + + " {position: absolute; left: 100px; width: 50px; height 50px;}"); + } + + String id = "styleInjectorTest" + (elements.length - 1); + StyleInjector.injectStylesheetAtStart("#" + id + + " {left: 25px; width: 100px !important;}"); + StyleInjector.injectStylesheetAtEnd("#" + id + " {height: 100px;}"); + + final DivElement elt = Document.get().createDivElement(); + elt.setId(id); + elt.setInnerHTML("Hello StyleInjector!"); + Document.get().getBody().appendChild(elt); + + // We need to allow the document to be redrawn + delayTestFinish(500); + + DeferredCommand.addCommand(new Command() { + public void execute() { + assertEquals(100, elt.getOffsetLeft()); + assertEquals(100, elt.getClientHeight()); + assertEquals(100, elt.getClientWidth()); + finishTest(); + } + }); + } +} --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---