diff --git a/user/src/com/google/gwt/dom/client/StyleInjector.java b/user/src/com/google/gwt/dom/client/StyleInjector.java index 718bad41b73..1e4888066a5 100644 --- a/user/src/com/google/gwt/dom/client/StyleInjector.java +++ b/user/src/com/google/gwt/dom/client/StyleInjector.java @@ -74,11 +74,21 @@ public void setContents(StyleElement style, String contents) { private StyleElement createElement(String contents) { StyleElement style = Document.get().createStyleElement(); + propagateNonceIfPossible(style); style.setPropertyString("language", "text/css"); setContents(style, contents); return style; } + private static native void propagateNonceIfPossible(Element element) /*-{ + var doc = element.ownerDocument; + if (doc.querySelector && doc.querySelector('script[nonce]')) { + var firstNoncedScript = doc.querySelector('script[nonce]'); + var nonce = firstNoncedScript['nonce'] || firstNoncedScript.getAttribute('nonce'); + element.setAttribute('nonce', nonce); + } + }-*/; + private HeadElement getHead() { if (head == null) { Element elt = Document.get().getElementsByTagName("head").getItem(0); diff --git a/user/test/com/google/gwt/dom/client/StyleInjectorTest.java b/user/test/com/google/gwt/dom/client/StyleInjectorTest.java index e585a0de3e0..c35c549d2e4 100644 --- a/user/test/com/google/gwt/dom/client/StyleInjectorTest.java +++ b/user/test/com/google/gwt/dom/client/StyleInjectorTest.java @@ -15,6 +15,7 @@ */ package com.google.gwt.dom.client; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DeferredCommand; @@ -31,6 +32,41 @@ public String getModuleName() { return "com.google.gwt.dom.DOMTest"; } + @SuppressWarnings("deprecation") + public void testStyleInjectorCopiesNonceFromExistingScriptElement() { + final String nonce = "StyleInjectorNonce"; + ScriptElement noncedScript = Document.get().createScriptElement(); + noncedScript.setAttribute("nonce", nonce); + Document.get().getHead().insertBefore(noncedScript, Document.get().getHead().getFirstChild()); + + StyleElement style = null; + try { + style = StyleInjector.injectStylesheet(".styleInjectorNonceTest { color: red; }"); + assertEquals(nonce, getNonce(style)); + } finally { + if (style != null) { + style.removeFromParent(); + } + noncedScript.removeFromParent(); + } + } + + @SuppressWarnings("deprecation") + public void testStyleInjectorDoesNotCreateNonceWithoutSource() { + JavaScriptObject savedNonces = removeScriptNonces(); + StyleElement style = null; + try { + assertFalse(hasNoncedScript()); + style = StyleInjector.injectStylesheet(".styleInjectorNoNonceTest { color: blue; }"); + assertNull(getNonce(style)); + } finally { + if (style != null) { + style.removeFromParent(); + } + restoreScriptNonces(savedNonces); + } + } + @SuppressWarnings("deprecation") public void testOldMethods() { final DivElement elt = Document.get().createDivElement(); @@ -139,4 +175,31 @@ public void execute() { delayTestFinish(TEST_DELAY); } } + + private static native String getNonce(Element element) /*-{ + return element['nonce'] || element.getAttribute('nonce') || null; + }-*/; + + private static native boolean hasNoncedScript() /*-{ + return !!$doc.querySelector('script[nonce]'); + }-*/; + + private static native JavaScriptObject removeScriptNonces() /*-{ + var scripts = $doc.querySelectorAll('script[nonce]'); + var savedNonces = []; + for (var i = 0; i < scripts.length; i++) { + savedNonces.push({ + element: scripts[i], + nonce: scripts[i]['nonce'] || scripts[i].getAttribute('nonce') + }); + scripts[i].removeAttribute('nonce'); + } + return savedNonces; + }-*/; + + private static native void restoreScriptNonces(JavaScriptObject savedNonces) /*-{ + for (var i = 0; i < savedNonces.length; i++) { + savedNonces[i].element.setAttribute('nonce', savedNonces[i].nonce); + } + }-*/; }