WICKET-6498 JavaScriptDeferHeaderResponse

- translates OnDomReady and Load header items to their native non-jquery equivalents
- application uses a default IHeaderResponseDecorator now, that decorates with the ResourceAggregator; this allows to chain header response before and *after* the aggregator
diff --git a/wicket-core/src/main/java/org/apache/wicket/Application.java b/wicket-core/src/main/java/org/apache/wicket/Application.java
index d6d4bb5..356c8b9 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Application.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Application.java
@@ -195,7 +195,9 @@
 	/**
 	 * The decorator this application uses to decorate any header responses created by Wicket
 	 */
-	private IHeaderResponseDecorator headerResponseDecorator;
+	private IHeaderResponseDecorator headerResponseDecorator = (headerresponse) -> {
+		return new ResourceAggregator(headerresponse);
+	};
 
 	/**
 	 * Checks if the <code>Application</code> threadlocal is set in this thread
@@ -1614,9 +1616,10 @@
 	 * @param headerResponseDecorator
 	 *            your custom decorator
 	 */
-	public final Application setHeaderResponseDecorator(
-		final IHeaderResponseDecorator headerResponseDecorator)
+	public final Application setHeaderResponseDecorator(final IHeaderResponseDecorator headerResponseDecorator)
 	{
+		Args.notNull(headerResponseDecorator, "headerResponseDecorator");
+		
 		this.headerResponseDecorator = headerResponseDecorator;
 		return this;
 	}
@@ -1636,14 +1639,7 @@
 	 */
 	public final IHeaderResponse decorateHeaderResponse(final IHeaderResponse response)
 	{
-		final IHeaderResponse aggregatingResponse = new ResourceAggregator(response);
-
-		if (headerResponseDecorator == null)
-		{
-			return aggregatingResponse;
-		}
-
-		return headerResponseDecorator.decorate(aggregatingResponse);
+		return headerResponseDecorator.decorate(response);
 	}
 
 	/**
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponse.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponse.java
index 8dee02e..233b588 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponse.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponse.java
@@ -27,7 +27,6 @@
 import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
 import org.apache.wicket.markup.head.HeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.ResourceAggregator;
 import org.apache.wicket.markup.head.internal.HeaderResponse;
 import org.apache.wicket.markup.html.DecoratingHeaderResponse;
 import org.apache.wicket.request.Response;
@@ -263,14 +262,12 @@
 			}
 		};
 
-		ResourceAggregator resourceAggregator = new ResourceAggregator(headerRenderer);
-
 		for (HeaderItem curItem : resp)
 		{
-			resourceAggregator.render(curItem);
+			headerRenderer.render(curItem);
 		}
 
-		resourceAggregator.close();
+		headerRenderer.close();
 
 		return strResponse.getBuffer();
 	}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/JavaScriptDeferHeaderResponse.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/JavaScriptDeferHeaderResponse.java
new file mode 100644
index 0000000..15dd49e
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/JavaScriptDeferHeaderResponse.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+package org.apache.wicket.markup.head.filter;
+
+import org.apache.wicket.core.util.string.JavaScriptUtils;
+import org.apache.wicket.markup.head.AbstractJavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.IWrappedHeaderItem;
+import org.apache.wicket.markup.head.JavaScriptContentHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.OnLoadHeaderItem;
+import org.apache.wicket.markup.html.DecoratingHeaderResponse;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * A header response that defers all {@link AbstractJavaScriptReferenceHeaderItem}s.
+ * 
+ * @author svenmeier
++ */
+public class JavaScriptDeferHeaderResponse extends DecoratingHeaderResponse
+{
+	public JavaScriptDeferHeaderResponse(IHeaderResponse response)
+	{
+		super(response);
+	}
+	
+	@Override
+	public void render(HeaderItem item)
+	{
+		if (item instanceof IWrappedHeaderItem) {
+			item = ((IWrappedHeaderItem)item).getWrapped();
+		}
+
+		if (item instanceof AbstractJavaScriptReferenceHeaderItem) {
+			((AbstractJavaScriptReferenceHeaderItem)item).setDefer(true);
+		} else if (item instanceof OnDomReadyHeaderItem) {
+			item = new NativeOnDomContentLoadedHeaderItem(((OnDomReadyHeaderItem)item).getJavaScript());
+		} else if (item instanceof OnLoadHeaderItem) {
+			item = new NativeOnLoadHeaderItem(((OnLoadHeaderItem)item).getJavaScript());
+		} else if (item instanceof JavaScriptContentHeaderItem) {
+			item = new NativeOnDomContentLoadedHeaderItem(((JavaScriptContentHeaderItem)item).getJavaScript());
+		}
+		
+		super.render(item);
+	}
+
+	private class NativeOnDomContentLoadedHeaderItem extends OnDomReadyHeaderItem
+	{
+		/**
+		 * Construct.
+		 *
+		 * @param javaScript
+		 */
+		public NativeOnDomContentLoadedHeaderItem(CharSequence javaScript)
+		{
+			super(javaScript);
+		}
+
+		@Override
+		public void render(Response response)
+		{
+			CharSequence js = getJavaScript();
+			if (Strings.isEmpty(js) == false)
+			{
+				JavaScriptUtils.writeJavaScript(response,
+					"(function(){ var f = function() {" + js + ";};\nif ('loading' !== document.readyState) f(); else document.addEventListener('DOMContentLoaded', f); })();");
+			}
+		}
+	}
+	
+	private class NativeOnLoadHeaderItem extends OnLoadHeaderItem
+	{
+
+		/**
+		 * Construct.
+		 *
+		 * @param javaScript
+		 */
+		public NativeOnLoadHeaderItem(CharSequence javaScript)
+		{
+			super(javaScript);
+		}
+
+		@Override
+		public void render(Response response)
+		{
+			CharSequence js = getJavaScript();
+			if (Strings.isEmpty(js) == false)
+			{
+				JavaScriptUtils.writeJavaScript(response,
+					"(function(){ var f = function() {" + js + ";};\nif ('complete' === document.readyState) f(); else window.addEventListener('load', f); })();");
+			}
+		}
+	}	
+} 
\ No newline at end of file
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
index 6c50132..d8a901a 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
@@ -19,6 +19,7 @@
 import java.util.Collections;
 
 import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.ResourceAggregator;
 import org.apache.wicket.markup.head.StringHeaderItem;
 import org.apache.wicket.markup.head.internal.HeaderResponse;
 import org.apache.wicket.markup.html.IHeaderResponseDecorator;
@@ -45,7 +46,7 @@
 			{
 				// use this header resource decorator to load all JavaScript resources in the page
 				// footer (after </body>)
-				return new JavaScriptFilteredIntoFooterHeaderResponse(response, "footerJS");
+				return new ResourceAggregator(new JavaScriptFilteredIntoFooterHeaderResponse(response, "footerJS"));
 			}
 		});
 		executeTest(FilteredHeaderPage.class, "FilteredHeaderPageExpected.html");
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/DecoratingHeaderResponseTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/DecoratingHeaderResponseTest.java
index 7a8b533..30f8d5b 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/html/DecoratingHeaderResponseTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/DecoratingHeaderResponseTest.java
@@ -24,6 +24,7 @@
 import org.apache.wicket.markup.head.IHeaderResponse;

 import org.apache.wicket.markup.head.JavaScriptHeaderItem;

 import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;

+import org.apache.wicket.markup.head.ResourceAggregator;

 import org.apache.wicket.markup.parser.XmlPullParser;

 import org.apache.wicket.markup.parser.XmlTag;

 import org.apache.wicket.request.resource.PackageResourceReference;

@@ -56,7 +57,7 @@
 			@Override

 			public IHeaderResponse decorate(IHeaderResponse response)

 			{

-				return new DecoratingHeaderResponse(response)

+				return new ResourceAggregator(new DecoratingHeaderResponse(response)

 				{

 					@Override

 					public void render(HeaderItem item)

@@ -69,7 +70,7 @@
 						}

 						super.render(item);

 					}

-				};

+				});

 			}

 		});

 		tester.startPage(TestPage.class);

diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
index 82caa41..c38265a 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
@@ -22,6 +22,8 @@
 import org.apache.wicket.application.IComponentInitializationListener;
 import org.apache.wicket.examples.WicketExampleApplication;
 import org.apache.wicket.examples.ajax.builtin.modal.ModalWindowPage;
+import org.apache.wicket.markup.head.ResourceAggregator;
+import org.apache.wicket.markup.head.filter.JavaScriptDeferHeaderResponse;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.response.filter.AjaxServerAndClientTimeFilter;
 
@@ -44,6 +46,11 @@
 
 		getDebugSettings().setAjaxDebugModeEnabled(true);
 		
+		setHeaderResponseDecorator(response -> {
+			// use this header resource decorator to defer all JavaScript resources
+			return new ResourceAggregator(new JavaScriptDeferHeaderResponse(response));
+		});
+
 		getComponentInitializationListeners().add(new IComponentInitializationListener()
 		{
 			@Override
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/ResourceDecorationApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/ResourceDecorationApplication.java
index 5af41de..b8b45e9 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/ResourceDecorationApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/resourcedecoration/ResourceDecorationApplication.java
@@ -17,6 +17,7 @@
 package org.apache.wicket.examples.resourcedecoration;
 
 import org.apache.wicket.Application;
+import org.apache.wicket.markup.head.ResourceAggregator;
 import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse;
 import org.apache.wicket.markup.html.IHeaderResponseDecorator;
 import org.apache.wicket.protocol.http.WebApplication;
@@ -46,7 +47,7 @@
 		setHeaderResponseDecorator(response -> {
 			// use this header resource decorator to load all JavaScript resources in the page
 			// footer (after </body>)
-			return new JavaScriptFilteredIntoFooterHeaderResponse(response, "footerJS");
+			return new ResourceAggregator(new JavaScriptFilteredIntoFooterHeaderResponse(response, "footerJS"));
 		});
 	}