You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by pa...@apache.org on 2020/02/11 20:10:33 UTC

[wicket] 01/02: WICKET-6737: Fixed CSP violations in examples

This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a commit to branch csp-examples
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 2310883c2a52a1256506700015007d6a4ef41511
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Tue Feb 11 21:09:18 2020 +0100

    WICKET-6737: Fixed CSP violations in examples
---
 .../wicket/examples/WicketExampleApplication.java  |  2 +
 .../bean/validation/BeanValidationApplication.java |  2 +
 .../bean/validation/BeanValidationPage.html        |  8 +---
 .../bean/validation/BeanValidationPage.java        | 11 +++++
 .../HomePage.css => bean/validation/bean.css}      | 13 +++---
 .../apache/wicket/examples/csp/CspApplication.java |  2 -
 .../apache/wicket/examples/csp/NonceDemoPage.html  |  1 +
 .../org/apache/wicket/examples/hangman/Guess.html  |  2 +-
 .../wicket/examples/kittenCaptcha/HomePage.html    |  2 +-
 .../examples/library/AuthenticatedWebPage.html     |  2 +-
 .../wicket/examples/library/BookDetails.html       | 12 +----
 .../org/apache/wicket/examples/library/Home.html   |  2 +-
 .../wicket/examples/media/VideosApplication.java   |  8 ++++
 .../examples/resourcedecoration/HomePage.css       |  4 ++
 .../examples/resourcedecoration/HomePage.html      |  6 +--
 .../spring/common/web/ExampleApplication.java      |  2 +
 .../wicket/examples/sri/IntegrityDemoPage.html     |  4 +-
 .../velocity/VelocityTemplateApplication.java      |  2 +
 .../examples/websocket/JSR356Application.java      |  5 ++-
 .../packageMount/PackageMountedPage.html           |  2 +-
 .../resources/org/apache/wicket/examples/style.css | 21 +++++++++
 .../captcha/kittens/KittenCaptchaPanel.html        | 42 -----------------
 .../captcha/kittens/KittenCaptchaPanel.java        | 16 +++++++
 .../extensions/captcha/kittens/kittencaptcha.js    | 52 ++++++++++++++++++++++
 24 files changed, 141 insertions(+), 82 deletions(-)

diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
index 394b344..016276e 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
@@ -44,6 +44,8 @@ public abstract class WicketExampleApplication extends WebApplication
 	@Override
 	protected void init()
 	{
+		super.init();
+		
 		// WARNING: DO NOT do this on a real world application unless
 		// you really want your app's passwords all passed around and
 		// stored in unencrypted browser cookies (BAD IDEA!)!!!
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationApplication.java
index a2fd84c..81890fe 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationApplication.java
@@ -31,6 +31,8 @@ public class BeanValidationApplication extends WicketExampleApplication
 	@Override
 	protected void init()
 	{
+		super.init();
+		
 		new BeanValidationConfiguration().configure(this);
 	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.html
index 10c6300..a6b1f9e 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.html
@@ -1,19 +1,13 @@
 <html xmlns:wicket="http://wicket.apache.org">
 <head>
 <title>Wicket Examples - Bean Validation</title>
-<style>
-	.note { font-size:.8em; }
-	.required {font-weight: bold;}
-	table {border-collapse: collapse; border-spacing: 0;}
-	th, td {padding: 4px;}
-</style>
 </head>
 <body>
     <wicket:extend>
 	<div wicket:id="feedbackErrors"></div>
 
 	<form wicket:id="form" novalidate="novalidate">
-		<table style="border-collapse: collapse; border-spacing: 0;">
+		<table>
 			<tr>
 				<td><label wicket:for="name"><wicket:label>Name</wicket:label></label></td>
 				<td><input wicket:id="name" type="text" size="30"/></td>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.java
index 3173867..2532ab0 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/BeanValidationPage.java
@@ -23,11 +23,14 @@ import org.apache.wicket.examples.WicketExamplePage;
 import org.apache.wicket.extensions.markup.html.form.datetime.LocalDateTextField;
 import org.apache.wicket.feedback.ExactLevelFeedbackMessageFilter;
 import org.apache.wicket.feedback.FeedbackMessage;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.request.resource.CssResourceReference;
 
 public class BeanValidationPage extends WicketExamplePage
 {
@@ -61,4 +64,12 @@ public class BeanValidationPage extends WicketExamplePage
 		
 		add(new FeedbackPanel("feedbackSuccess", new ExactLevelFeedbackMessageFilter(FeedbackMessage.INFO)));
 	}
+	
+	@Override
+	public void renderHead(IHeaderResponse response)
+	{
+		super.renderHead(response);
+		response.render(CssHeaderItem
+			.forReference(new CssResourceReference(BeanValidationPage.class, "bean.css")));
+	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/bean.css
similarity index 85%
copy from wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css
copy to wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/bean.css
index 34e16e2..4c3cbf8 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/bean/validation/bean.css
@@ -14,11 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-.borderedThing {
-	border: 1px solid red;	
-}
-
-.pending {
-	border: 1px solid yellow;
-}
+.note { font-size:.8em; }
+.required {font-weight: bold;}
+table {border-collapse: collapse; border-spacing: 0;}
+th, td {padding: 4px;}
+ 
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
index ae9187d..02f8b89 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
@@ -32,8 +32,6 @@ public class CspApplication extends WicketExampleApplication
 	{
 		super.init();
 
-		getCsp().blocking().strict();
-		
 		mountPage("noncedemo", NonceDemoPage.class);
 	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/NonceDemoPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/NonceDemoPage.html
index 6aab2b8..32549a7 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/NonceDemoPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/NonceDemoPage.html
@@ -5,6 +5,7 @@
 <body>
 <wicket:extend>
     <h3>CSP Nonce Demo</h3>
+    <p>This example demonstrates the CSP protection offered by Wicket. In a modern browser, you will see 2 reports sent back to the application about CSP violations.</p>
     <div wicket:id="testNonceScript" class="test-nonce-script"></div>
     <div wicket:id="testNoNonceScript" class="test-no-nonce-script"></div>
     <div class="injected-style"><wicket:message key="styleInjectionContainer"/></div>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/hangman/Guess.html b/wicket-examples/src/main/java/org/apache/wicket/examples/hangman/Guess.html
index 5f45e56..6c8d470 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/hangman/Guess.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/hangman/Guess.html
@@ -7,7 +7,7 @@
 	<p><span wicket:id="guessesRemaining" id="guessesRemaining">3</span> guesses remaining</p>
 	<p>Word: <span wicket:id="word">WICK_T</span></p>
 	<p>
-		<div style="width:250px">
+		<div class="width-250">
 			<span wicket:id="letters">
 				<a wicket:id="letter" href="#"><img type="image" wicket:id="image" /></a>
 			</span>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html
index fc927a5..d083c35 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/kittenCaptcha/HomePage.html
@@ -6,7 +6,7 @@
 </head>
 <body>
     <wicket:extend>
-    <div style="font-family: helvetica" wicket:id="captcha"></div>
+    <div wicket:id="captcha"></div>
     <p/>
 	<a wicket:id="checkKittens">Check kittens!</a>
 </wicket:extend>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/library/AuthenticatedWebPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/library/AuthenticatedWebPage.html
index 66ec54a..da77611 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/library/AuthenticatedWebPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/library/AuthenticatedWebPage.html
@@ -10,7 +10,7 @@
        <wicket:link><a href = "Home.html">Home</a></wicket:link>&#160;
        <wicket:link><a href="SignOut.html">Sign Out</a></wicket:link>
        <br />
-       <div style="border:1px solid gray; overflow: hidden; padding: 1em;">
+       <div class="library-content">
 		<wicket:child/>           
        </div>
 	</p>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/library/BookDetails.html b/wicket-examples/src/main/java/org/apache/wicket/examples/library/BookDetails.html
index 22c311d..3719502 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/library/BookDetails.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/library/BookDetails.html
@@ -1,14 +1,4 @@
 <wicket:extend xmlns:wicket="http://wicket.apache.org">
-
-<style type="text/css">
-<!--
-.italic {
-	font-style: italic;
-}
--->
-</style>
-
-
   Book Details
   <p></p>
   <table>
@@ -61,7 +51,7 @@
 	  </td>
 	</tr>	
   </table>	
-  <div style="margin-top: 1em">
+  <div class="margin-top-1em">
   <a wicket:id = "edit" href="EditBook.html">Edit</a>
   </div>
 </wicket:extend>
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/library/Home.html b/wicket-examples/src/main/java/org/apache/wicket/examples/library/Home.html
index 52c97a7..bb02151 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/library/Home.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/library/Home.html
@@ -37,7 +37,7 @@
       </td>
     </tr>
   </table>	
-  <div style="margin-top: 1em">
+  <div class="margin-top-1em">
   <span wicket:id = "navigator"/>
   
   </div>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/media/VideosApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/media/VideosApplication.java
index daca4e2..18bbdae 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/media/VideosApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/media/VideosApplication.java
@@ -17,6 +17,8 @@
 package org.apache.wicket.examples.media;
 
 import org.apache.wicket.Page;
+import org.apache.wicket.csp.CSPDirective;
+import org.apache.wicket.csp.CSPDirectiveSrcValue;
 import org.apache.wicket.examples.WicketExampleApplication;
 import org.apache.wicket.markup.html.IPackageResourceGuard;
 import org.apache.wicket.markup.html.SecurePackageResourceGuard;
@@ -38,12 +40,18 @@ public class VideosApplication extends WicketExampleApplication
 	@Override
 	protected void init()
 	{
+		super.init();
+		
 		IPackageResourceGuard packageResourceGuard = getResourceSettings().getPackageResourceGuard();
 		if (packageResourceGuard instanceof SecurePackageResourceGuard)
 		{
 			SecurePackageResourceGuard guard = (SecurePackageResourceGuard)packageResourceGuard;
 			guard.addPattern("+*.mp4");
 		}
+		
+		getCsp().blocking()
+			.add(CSPDirective.MEDIA_SRC, CSPDirectiveSrcValue.SELF)
+			.add(CSPDirective.MEDIA_SRC, "https://w3c-test.org/media/movie_300.mp4");
 	}
 
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css
index 34e16e2..064be4a 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.css
@@ -22,3 +22,7 @@
 .pending {
 	border: 1px solid yellow;
 }
+
+.large {
+	font-size: 1.5em;
+}
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.html
index 94c4679..e1e189a 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/HomePage.html
@@ -7,9 +7,9 @@
         <br/> 
     	<div id="bodyContent" class="bodyContent">
 	    	<h1 class="header">Resource Aggregation Example Application</h1>
-	    	<p style="font-size: 1.5em">If the background is light grey, then app.css was loaded.</p>
-	    	<p style="font-size: 1.5em">If this box has a red border, top.js was not loaded from the header</p>
-	    	<p style="font-size: 1.5em">If the h1 above has a dashed bottom border (and the one below has a dashed top border), header.css and footer.css were loaded.
+	    	<p class="large">If the background is light grey, then app.css was loaded.</p>
+	    	<p class="large">If this box has a red border, top.js was not loaded from the header</p>
+	    	<p class="large">If the h1 above has a dashed bottom border (and the one below has a dashed top border), header.css and footer.css were loaded.
 	    	They are merged at the server side and delivered together to the browser</p>
 	    	<div wicket:id="jsProofPlaceholder" class="jsProofPlaceholder">
 	    		placeholder that JS can do something with
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/spring/common/web/ExampleApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/spring/common/web/ExampleApplication.java
index a8a4ac0..e0ca6cb 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/spring/common/web/ExampleApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/spring/common/web/ExampleApplication.java
@@ -43,6 +43,8 @@ public class ExampleApplication extends WicketExampleApplication
 	@Override
 	protected void init()
 	{
+		super.init();
+		
 		getDebugSettings().setDevelopmentUtilitiesEnabled(true);
 
 		// THIS LINE IS IMPORTANT - IT INSTALLS THE COMPONENT INJECTOR THAT WILL
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
index 3204a4e..fae1cfb 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
@@ -7,8 +7,8 @@
     <h3>SRI Demo</h3>
     
     <div id="sri">
-	    <p class="sri-false" style="color: red;">Integrity test NOT passed</p>
-	    <p class="sri-true" style="color: green; font-weight: bold; display: none;">Integrity test passed</p>
+	    <p class="sri-false color-red">Integrity test NOT passed</p>
+	    <p class="sri-true color-green wicket--hidden">Integrity test passed</p>
     </div> 
 </wicket:extend>
 </body>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/velocity/VelocityTemplateApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/velocity/VelocityTemplateApplication.java
index b444c16..7d6c76f 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/velocity/VelocityTemplateApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/velocity/VelocityTemplateApplication.java
@@ -93,6 +93,8 @@ public class VelocityTemplateApplication extends WicketExampleApplication
 	@Override
 	protected void init()
 	{
+		super.init();
+		
 		getDebugSettings().setDevelopmentUtilitiesEnabled(true);
 		IPackageResourceGuard packageResourceGuard = getResourceSettings().getPackageResourceGuard();
 		if (packageResourceGuard instanceof SecurePackageResourceGuard)
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/JSR356Application.java b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/JSR356Application.java
index cf2992e..53d7dee 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/JSR356Application.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/JSR356Application.java
@@ -64,8 +64,9 @@ public class JSR356Application extends WicketExampleApplication
 			webSocketSettings.setSecurePort(8443);
 		}
 
-		getCsp().blocking().add(CSPDirective.SCRIPT_SRC, "https://www.google.com")
-				.add(CSPDirective.STYLE_SRC, "https://www.google.com", "https://ajax.googleapis.com");
+		// The websocket example loads JS from ajax.googleapis.com, which is not allowed by the CSP.
+		// This now serves as an example on how to disable CSP
+		getCsp().blocking().disabled();
 	}
 
     @Override
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/requestmapper/packageMount/PackageMountedPage.html b/wicket-examples/src/main/resources/org/apache/wicket/examples/requestmapper/packageMount/PackageMountedPage.html
index d73347c..93530a5 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/requestmapper/packageMount/PackageMountedPage.html
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/requestmapper/packageMount/PackageMountedPage.html
@@ -27,7 +27,7 @@
 	    
 	    <p>
 	    	This page is mounted because there is a registered PackageMapper for this page's package:
-	    	<div style="background-color: lightgrey;">
+	    	<div class="light-grey">
 	    	<pre>
 	public class RequestMapperApplication extends WicketExampleApplication
 	{
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
index 2827e1a..7f4cad8 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
@@ -1393,3 +1393,24 @@ div.wicket-aa ul li.selected {
 	margin:	10px;
 }
 
+.width-250 {
+	width: 250px;
+}
+
+.italic {
+	font-style: italic;
+}
+
+.margin-top-1em {
+	margin-top: 1em;
+}
+
+.library-content {
+	border: 1px solid gray;
+	overflow: hidden;
+	padding: 1em;
+}
+
+.light-grey {
+	background-color: lightgrey;
+}
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html
index 5edafe5..422de5a 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.html
@@ -16,45 +16,6 @@
    limitations under the License.
 -->
 <html xmlns:wicket="http://wicket.apache.org">
-<wicket:head>
-	<script type="text/javascript">
-		function getEventX(element, event) {
-			var result;
-			if (event.offsetX != null) {
-				result = event.offsetX;
-			} else {
-				result = event.pageX;
-				do {
-					result = result - element.offsetLeft;
-					element = element.offsetParent;
-				} while (element != null)
-			}
-			return parseInt(result, 10);
-		}
-		function getEventY(element, event) {
-			var result;
-			if (event.offsetY != null) {
-				result = event.offsetY;
-			} else {
-				result = event.pageY;
-				do {
-					result = result - element.offsetTop;
-					element = element.offsetParent;
-				} while (element != null)
-			}
-			return parseInt(result, 10);
-		}
-		function getImage() {
-			return Wicket.$("imageContainer").getElementsByTagName("img")[0];
-		}
-		function showLoadingIndicator() {
-			Wicket.$('loading').style.visibility="visible";
-		}
-		function hideLoadingIndicator() {
-			Wicket.$('loading').style.visibility="hidden";
-		}
-	</script>
-</wicket:head>
 <body>
 <wicket:panel>
 	<div wicket:id="animalSelectionLabel"></div>
@@ -66,9 +27,6 @@
 			<wicket:message key="pleaseWait"></wicket:message>
 		</div>
 	</div>
-	<script type="text/javascript">
-		Wicket.Event.add(getImage(), "load", function() { hideLoadingIndicator(); } );
-	</script>	
 </wicket:panel>
 </body>
 </html>
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java
index 287573d..b861b04 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/KittenCaptchaPanel.java
@@ -37,6 +37,10 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxCallListener;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.attributes.IAjaxCallListener;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.OnEventHeaderItem;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.image.Image;
 import org.apache.wicket.markup.html.image.NonCachingImage;
@@ -48,6 +52,7 @@ import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.DynamicImageResource;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -292,6 +297,17 @@ public class KittenCaptchaPanel extends Panel
 		// Could not place animal
 		return null;
 	}
+	
+	@Override
+	public void renderHead(IHeaderResponse response)
+	{
+		super.renderHead(response);
+		response.render(JavaScriptHeaderItem.forReference(
+			new JavaScriptResourceReference(KittenCaptchaPanel.class, "kittencaptcha.js")));
+		response.render(OnEventHeaderItem.forComponent(image, "load", "hideLoadingIndicator()"));
+		response.render(OnDomReadyHeaderItem.forScript("if (document.getElementById('"
+			+ image.getMarkupId() + "').complete) hideLoadingIndicator();"));
+	}
 
 	/**
 	 * @param max
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/kittencaptcha.js b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/kittencaptcha.js
new file mode 100644
index 0000000..d0bd78c
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/captcha/kittens/kittencaptcha.js
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+function getEventX(element, event) {
+	var result;
+	if (event.offsetX != null) {
+		result = event.offsetX;
+	} else {
+		result = event.pageX;
+		do {
+			result = result - element.offsetLeft;
+			element = element.offsetParent;
+		} while (element != null)
+	}
+	return parseInt(result, 10);
+}
+function getEventY(element, event) {
+	var result;
+	if (event.offsetY != null) {
+		result = event.offsetY;
+	} else {
+		result = event.pageY;
+		do {
+			result = result - element.offsetTop;
+			element = element.offsetParent;
+		} while (element != null)
+	}
+	return parseInt(result, 10);
+}
+function getImage() {
+	return Wicket.$("imageContainer").getElementsByTagName("img")[0];
+}
+function showLoadingIndicator() {
+	Wicket.$('loading').style.visibility="visible";
+}
+function hideLoadingIndicator() {
+	Wicket.$('loading').style.visibility="hidden";
+}