diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js index dd856cff468..c7546405bd1 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js @@ -187,7 +187,7 @@ * Logging functionality. */ Wicket.Log = { - + enabled: false, log: function () { @@ -350,6 +350,31 @@ */ Wicket.Ajax.Call = Wicket.Class.create(); + Wicket.Ajax.Call._suspended = 0; + + Wicket.Ajax.Call.currentNotify = undefined; + + Wicket.Ajax.Call.suspend = function () { + if (typeof (Wicket.Ajax.Call.currentNotify) != "function") { + throw new Error("Can't suspend: no evaluation in process"); + } + Wicket.Ajax.Call._suspended++; + var notify = Wicket.Ajax.Call.currentNotify; + var released = false; + + return function () { + // release only once + if (released === false) { + released = true; + Wicket.Ajax.Call._suspended--; + if (Wicket.Ajax.Call._suspended === 0) { + notify(); + Wicket.Ajax.Call.currentNotify = undefined; + } + } + } + }; + Wicket.Ajax.Call.prototype = { initialize: jQuery.noop, @@ -529,20 +554,20 @@ }, /** - * Is an element still present for Ajax requests. + * Is an element still present for Ajax requests. */ _isPresent: function(id) { if (isUndef(id)) { // no id so no check whether present return true; } - + var element = Wicket.$(id); if (isUndef(element)) { // not present return false; } - + // present if no attributes at all or not a placeholder return (!element.hasAttribute || !element.hasAttribute('data-wicket-placeholder')); }, @@ -560,7 +585,7 @@ 'Wicket-Ajax': 'true', 'Wicket-Ajax-BaseURL': getAjaxBaseUrl() }, - + url = attrs.u, // the request (extra) parameters @@ -570,7 +595,7 @@ // the precondition to use if there are no explicit ones defaultPrecondition = [ function (attributes) { - return self._isPresent(attributes.c) && self._isPresent(attributes.f); + return self._isPresent(attributes.c) && self._isPresent(attributes.f); }], // a context that brings the common data for the success/fialure/complete handlers @@ -631,7 +656,7 @@ var el = Wicket.$(attrs.c); data = data.concat(Wicket.Form.serializeElement(el, attrs.sr)); } - + // collect the dynamic extra parameters if (jQuery.isArray(attrs.dep)) { var dynamicData = this._calculateDynamicParameters(attrs); @@ -650,7 +675,7 @@ for (var i = 0; i < data.length; i++) { formData.append(data[i].name, data[i].value || ""); } - + data = formData; wwwFormUrlEncoded = false; } catch (exception) { @@ -668,7 +693,7 @@ context: self, processData: wwwFormUrlEncoded, contentType: wwwFormUrlEncoded, - + beforeSend: function (jqXHR, settings) { self._executeHandlers(attrs.bsh, attrs, jqXHR, settings); we.publish(topic.AJAX_CALL_BEFORE_SEND, attrs, jqXHR, settings); @@ -823,37 +848,40 @@ return; } + var nonce; + var meta = root.getElementsByTagName("meta")[0]; + if (!isUndef(meta)) { + // var nonceEl = meta.getElementsByTagName("wicket-nonce")[0]; + // if (!isUndef(nonceEl)) { + // nonce = Wicket.DOM.text(nonceEl); + // } + nonce = Wicket.DOM.text(meta.getElementsByTagName("wicket-nonce")[0]); + } + var steps = context.steps; // go through the ajax response and execute all priority-invocations first - for (var i = 0; i < root.childNodes.length; ++i) { - var childNode = root.childNodes[i]; - if (childNode.tagName === "header-contribution") { - this.processHeaderContribution(context, childNode); - } else if (childNode.tagName === "priority-evaluate") { - this.processEvaluation(context, childNode); - } - } + this.processHeaderContributions(context, root.childNodes, nonce, false); // go through the ajax response and for every action (component, js evaluation, header contribution) // ad the proper closure to steps var stepIndexOfLastReplacedComponent = -1; for (var c = 0; c < root.childNodes.length; ++c) { var node = root.childNodes[c]; - if (node.tagName === "component") { if (stepIndexOfLastReplacedComponent === -1) { this.processFocusedComponentMark(context); } stepIndexOfLastReplacedComponent = steps.length; this.processComponent(context, node); - } else if (node.tagName === "evaluate") { - this.processEvaluation(context, node); } else if (node.tagName === "redirect") { this.processRedirect(context, node); } - } + + // go through the ajax response and execute all NON-priority-invocations + this.processHeaderContributions(context, root.childNodes, nonce, true); + if (stepIndexOfLastReplacedComponent !== -1) { this.processFocusedComponentReplaceCheck(steps, stepIndexOfLastReplacedComponent); } @@ -926,93 +954,16 @@ }); }, - /** - * Adds a closure that evaluates javascript code. - * @param context {Object} - the object that brings the executer's steps and the attributes - * @param node {XmlElement} - the <[priority-]evaluate> element with the script to evaluate - */ - processEvaluation: function (context, node) { - - // used to match evaluation scripts which manually call FunctionsExecuter's notify() when ready - var scriptWithIdentifierR = new RegExp("\\(function\\(\\)\\{([a-zA-Z_]\\w*)\\|((.|\\n)*)?\\}\\)\\(\\);$"); - - /** - * A regex used to split the text in (priority-)evaluate elements in the Ajax response - * when there are scripts which require manual call of 'FunctionExecutor#notify()' - * @type {RegExp} - */ - var scriptSplitterR = new RegExp("\\(function\\(\\)\\{[\\s\\S]*?}\\)\\(\\);", 'gi'); - - // get the javascript body - var text = Wicket.DOM.text(node); - - // aliases to improve performance - var steps = context.steps; - var log = Wicket.Log; - - var evaluateWithManualNotify = function (parameters, body) { - return function(notify) { - var toExecute = "(function(" + parameters + ") {" + body + "})"; - - try { - // do the evaluation in global scope - var f = window.eval(toExecute); - f(notify); - } catch (exception) { - log.error("Wicket.Ajax.Call.processEvaluation: Exception evaluating javascript: %s", text, exception); - } - return FunctionsExecuter.ASYNC; - }; - }; - - var evaluate = function (script) { - return function(notify) { - // just evaluate the javascript - try { - // do the evaluation in global scope - window.eval(script); - } catch (exception) { - log.error("Ajax.Call.processEvaluation: Exception evaluating javascript: %s", text, exception); - } - // continue to next step - return FunctionsExecuter.DONE; - }; - }; - - // test if the javascript is in form of identifier|code - // if it is, we allow for letting the javascript decide when the rest of processing will continue - // by invoking identifier();. This allows usage of some asynchronous/deferred logic before the next script - // See WICKET-5039 - if (scriptWithIdentifierR.test(text)) { - var scripts = []; - var scr; - while ( (scr = scriptSplitterR.exec(text) ) !== null ) { - scripts.push(scr[0]); - } - - for (var s = 0; s < scripts.length; s++) { - var script = scripts[s]; - if (script) { - var scriptWithIdentifier = script.match(scriptWithIdentifierR); - if (scriptWithIdentifier) { - steps.push(evaluateWithManualNotify(scriptWithIdentifier[1], scriptWithIdentifier[2])); - } - else { - steps.push(evaluate(script)); - } - } + processHeaderContributions: function (context, nodes, nonce, afterComponents) { + var c = Wicket.Head.Contributor; + for (var i = 0; i < nodes.length; ++i) { + var childNode = nodes[i]; + if (childNode.tagName === "header-contribution") { + c.processContribution(context, childNode, nonce, afterComponents); } - } else { - steps.push(evaluate(text)); } }, - // Adds a closure that processes a header contribution - processHeaderContribution: function (context, node) { - var c = Wicket.Head.Contributor; - c.processContribution(context, node); - }, - // Adds a closure that processes a redirect processRedirect: function (context, node) { var text = Wicket.DOM.text(node); @@ -1231,7 +1182,7 @@ var result = []; if (input && input.type) { var $input = jQuery(input); - + if (input.type === 'file') { for (var f = 0; f < input.files.length; f++) { result.push({"name" : input.name, "value" : input.files[f]}); @@ -1608,7 +1559,7 @@ * Reads the text from the node's children nodes. * Used instead of jQuery.text() because it is very slow in IE10/11. * WICKET-5132, WICKET-5510 - * @param node {DOMElement} the root node + * @param node {Element} the root node */ text: function (node) { if (isUndef(node)) { @@ -1730,7 +1681,7 @@ }, null, attrs.sel); }); }, - + process: function(data) { var call = new Wicket.Ajax.Call(); call.process(data); @@ -1766,7 +1717,7 @@ parse: function (headerNode) { // the header contribution is stored as CDATA section in the header-contribution element, // we need to parse it since each header contribution needs to be treated separately - + // get the header contribution text and unescape it if necessary var text = Wicket.DOM.text(headerNode); @@ -1789,7 +1740,7 @@ }, // Processes the parsed header contribution - processContribution: function (context, headerNode) { + processContribution: function (context, headerNode, nonce, afterComponents) { var xmldoc = this.parse(headerNode); var rootNode = xmldoc.documentElement; @@ -1801,13 +1752,15 @@ // go through the individual elements and process them according to their type for (var i = 0; i < rootNode.childNodes.length; i++) { var node = rootNode.childNodes[i]; - // Chromium reports the error as a child node if (this._checkParserError(node)) { return; } if (!isUndef(node.tagName)) { + if (afterComponents !== (node.getAttribute("data-wicket-evaluation") === "after")) { + continue; + } var name = node.tagName.toLowerCase(); // it is possible that a reference is surrounded by a 0) { // add javascript to document head - Wicket.Head.addJavascript(text, id, "", type); + Wicket.Head.addJavascript(text, id, "", type, nonce); + return FunctionsExecuter.DONE; } else { + Wicket.Ajax.Call.currentNotify = notify; try { // do the evaluation in global scope - window.eval(text); - } catch (e) { - Wicket.Log.error("Wicket.Head.Contributor.processScript: %s", text, e); + jQuery.globalEval(text, {nonce: nonce}); + } catch (exception) { + Wicket.Log.error("Wicket.Head.Contributor.processScript: Exception evaluating javascript: %s", text, exception); + } + // continue to next step + if (Wicket.Ajax.Call._suspended) { + // suspended + return FunctionsExecuter.ASYNC; + } else { + // execution finished, cleanup the last notify + Wicket.Ajax.Call.currentNotify = undefined; + Wicket.Ajax.Call._suspended = 0; + // continue to next step + return FunctionsExecuter.DONE; } } - - // continue to next step - return FunctionsExecuter.DONE; } }); }, @@ -2004,14 +1968,17 @@ $meta = jQuery(meta), attrs = jQuery(node).prop("attributes"), name = node.getAttribute("name"), + id = node.getAttribute("id"), httpEquiv = node.getAttribute("http-equiv"); - if (name) { + if (id) { + jQuery('meta[id="' + id + '"]').remove(); + } else if (name) { jQuery('meta[name="' + name + '"]').remove(); } else if (httpEquiv) { jQuery('meta[http-equiv="' + httpEquiv + '"]').remove(); } - + jQuery.each(attrs, function() { $meta.attr(this.name, this.value); }); @@ -2113,7 +2080,7 @@ // attribute to filter out duplicates. However, since we set the body of the element, we can't assign // also a src value. Therefore we put the url to the src_ (notice the underscore) attribute. // Wicket.Head.containsElement is aware of that and takes also the underscored attributes into account. - addJavascript: function (content, id, fakeSrc, type) { + addJavascript: function (content, id, fakeSrc, type, nonce) { var script = Wicket.Head.createElement("script"); if (id) { script.id = id; @@ -2128,6 +2095,9 @@ script.setAttribute("src_", fakeSrc); script.setAttribute("type", type); + if (nonce) { + script.setAttribute("nonce", nonce) + } // set the javascript as element content if (null === script.canHaveChildren || script.canHaveChildren) { @@ -2350,7 +2320,7 @@ delete Wicket.TimerHandles[timerId]; } }, - + /** * Clear all remaining timers. */ @@ -2365,7 +2335,7 @@ } } }, - + /** * Events related code * Based on code from Mootools (http://mootools.net) diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/AfterDomRenderAjaxJavaScriptHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/AfterDomRenderAjaxJavaScriptHeaderItem.java new file mode 100644 index 00000000000..6f22ba9dd1c --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/AfterDomRenderAjaxJavaScriptHeaderItem.java @@ -0,0 +1,50 @@ +/* + * 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; + +import org.apache.wicket.util.value.AttributeMap; + +/** + * Special header NON-PRIORITY item for ajax evaluations + * + * @author Andrew Kondratev + */ +public class AfterDomRenderAjaxJavaScriptHeaderItem extends JavaScriptContentHeaderItem +{ + /** + * Creates a new {@code AfterDomRenderAjaxJavaScriptHeaderItem}. + * + * @param javaScript + * javascript content to be rendered. + */ + protected AfterDomRenderAjaxJavaScriptHeaderItem(CharSequence javaScript) + { + super(javaScript, null, null); + } + + public static AfterDomRenderAjaxJavaScriptHeaderItem forScript(CharSequence javaScript) { + return new AfterDomRenderAjaxJavaScriptHeaderItem(javaScript); + } + + @Override + protected void renderExtraAttributes(AttributeMap attributes) + { + // evaluate this header item after components were rendered + attributes.add("data-wicket-evaluation", "after"); + } + +} diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java index fccadee9655..afd7b08d841 100644 --- a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java +++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java @@ -74,6 +74,7 @@ public void render(Response response) attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, "text/javascript"); attributes.putAttribute(JavaScriptUtils.ATTR_ID, getId()); attributes.putAttribute(JavaScriptUtils.ATTR_CSP_NONCE, getNonce()); + renderExtraAttributes(attributes); JavaScriptUtils.writeInlineScript(response, getJavaScript(), attributes); if (hasCondition) @@ -82,6 +83,10 @@ public void render(Response response) } } + protected void renderExtraAttributes(AttributeMap attributes) { + // no-op by default + } + @Override public Iterable getRenderTokens() { diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java index 6376518503a..230ebe18940 100644 --- a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java +++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java @@ -67,7 +67,7 @@ public void render(HeaderItem item) String policy = getContentSecurityPolicy(nonce); - super.render(MetaDataHeaderItem.forHttpEquiv(CONTENT_SECURITY_POLICY, policy)); + super.render(MetaDataHeaderItem.forHttpEquiv(CONTENT_SECURITY_POLICY, policy).addTagAttribute("id", "meta-csp")); } ((AbstractCspHeaderItem)item).setNonce(nonce); @@ -88,6 +88,6 @@ public void render(HeaderItem item) */ protected String getContentSecurityPolicy(String nonce) { - return String.format("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1$s'; style-src 'nonce-%1$s';", nonce); + return String.format("script-src 'strict-dynamic' 'nonce-%1$s'; style-src 'nonce-%1$s';", nonce); } } diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java index 2e026bd6247..9de1969a492 100644 --- a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java +++ b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java @@ -48,6 +48,7 @@ import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Generics; import org.apache.wicket.util.string.AppendingStringBuffer; +import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,11 +58,9 @@ *

* The elements of such response are: *

*/ public abstract class PartialPageUpdate @@ -92,6 +91,7 @@ public abstract class PartialPageUpdate */ protected final Map markupIdToComponent = new LinkedHashMap(); + /** * A flag that indicates that components cannot be added anymore. * See https://issues.apache.org/jira/browse/WICKET-3564 @@ -155,6 +155,8 @@ public void writeTo(final Response response, final String encoding) onBeforeRespond(response); + setUpHeader(); + // process added components writeComponents(response, encoding); @@ -171,6 +173,8 @@ public void writeTo(final Response response, final String encoding) evaluationScripts.addAll(appendJavaScripts); writeNormalEvaluations(response, evaluationScripts); + closeHeader(response); + writeFooter(response, encoding); } finally { if (header != null && originalHeaderContainer!= null) { @@ -259,11 +263,14 @@ private void writeComponents(Response response, String encoding) { writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding); } + } + private void closeHeader(Response response) + { if (header != null) { RequestCycle cycle = RequestCycle.get(); - + // some header responses buffer all calls to render*** until close is called. // when they are closed, they do something (i.e. aggregate all JS resource urls to a // single url), and then "flush" (by writing to the real response) before closing. @@ -550,15 +557,6 @@ public IHeaderResponse getHeaderResponse() */ protected void writeHeaderContribution(final Response response, final Component component) { - headerRendering = true; - - // create the htmlheadercontainer if needed - if (header == null) - { - header = new PartialHtmlHeaderContainer(this); - page.addOrReplace(header); - } - RequestCycle requestCycle = component.getRequestCycle(); // save old response, set new @@ -579,6 +577,17 @@ protected void writeHeaderContribution(final Response response, final Component headerRendering = false; } + private void setUpHeader() + { + headerRendering = true; + // create the htmlheadercontainer if needed + if (header == null) + { + header = new PartialHtmlHeaderContainer(this); + page.addOrReplace(header); + } + } + /** * Sets the Content-Type header to indicate the type of the response. * @@ -612,7 +621,7 @@ private static class PartialHtmlHeaderContainer extends HtmlHeaderContainer /** * Constructor. * - * @param update + * @param pageUpdate * the partial page update */ public PartialHtmlHeaderContainer(PartialPageUpdate pageUpdate) diff --git a/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java index 0af43424d65..1f5df297b7f 100644 --- a/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java +++ b/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java @@ -16,15 +16,17 @@ */ package org.apache.wicket.page; -import java.util.Collection; - import org.apache.wicket.Component; import org.apache.wicket.Page; +import org.apache.wicket.markup.head.AfterDomRenderAjaxJavaScriptHeaderItem; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; import org.apache.wicket.request.Response; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.http.WebResponse; import org.apache.wicket.util.string.Strings; +import java.util.Collection; + /** * A {@link PartialPageUpdate} that serializes itself to XML. */ @@ -121,17 +123,16 @@ protected void writeHeaderContribution(Response response) @Override protected void writeNormalEvaluations(final Response response, final Collection scripts) { - writeEvaluations(response, "evaluate", scripts); - + writeEvaluations(scripts, false); } @Override protected void writePriorityEvaluations(Response response, Collection scripts) { - writeEvaluations(response, "priority-evaluate", scripts); + writeEvaluations(scripts, true); } - private void writeEvaluations(final Response response, String elementName, Collection scripts) + private void writeEvaluations(Collection scripts, boolean isPriority) { if (scripts.size() > 0) { @@ -140,32 +141,19 @@ private void writeEvaluations(final Response response, String elementName, Colle { combinedScript.append("(function(){").append(script).append("})();"); } - writeEvaluation(elementName, response, combinedScript); + writeEvaluation(combinedScript, isPriority); } } - /** - * @param invocation - * type of invocation tag, usually {@literal evaluate} or - * {@literal priority-evaluate} - * @param response - * @param js - */ - private void writeEvaluation(final String invocation, final Response response, final CharSequence js) + private void writeEvaluation(final CharSequence js, boolean isPriority) { - response.write("<"); - response.write(invocation); - response.write(">"); - - response.write(""); - - response.write(""); - - bodyBuffer.reset(); + if (header != null) + { + header.getHeaderResponse().render( + isPriority + ? JavaScriptHeaderItem.forScript(js, null) + : AfterDomRenderAjaxJavaScriptHeaderItem.forScript(js)); + } } protected CharSequence encode(CharSequence str) diff --git a/wicket-core/src/main/java/org/apache/wicket/response/filter/AjaxServerAndClientTimeFilter.java b/wicket-core/src/main/java/org/apache/wicket/response/filter/AjaxServerAndClientTimeFilter.java index 618a39b6f3c..bc4e72a3f88 100644 --- a/wicket-core/src/main/java/org/apache/wicket/response/filter/AjaxServerAndClientTimeFilter.java +++ b/wicket-core/src/main/java/org/apache/wicket/response/filter/AjaxServerAndClientTimeFilter.java @@ -24,6 +24,7 @@ import org.apache.wicket.util.string.AppendingStringBuffer; import org.apache.wicket.core.util.string.JavaScriptUtils; import org.apache.wicket.util.string.interpolator.MapVariableInterpolator; +import org.apache.wicket.util.value.AttributeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,6 +54,10 @@ */ public class AjaxServerAndClientTimeFilter implements IResponseFilter { + private static final String HEADER_CONTRIBUTION_START = ""; + private static final String HEADER_CONTRIBUTION_END = "]]>"; + public static final String CDATA_SCRIPT_END = "/*]]]]>*/"; + private static Logger log = LoggerFactory.getLogger(AjaxServerAndClientTimeFilter.class); /** @@ -66,34 +71,62 @@ public AppendingStringBuffer filter(AppendingStringBuffer responseBuffer) int ajaxStart = responseBuffer.indexOf(""); int ajaxEnd = responseBuffer.indexOf(""); long timeTaken = System.currentTimeMillis() - RequestCycle.get().getStartTime(); - if (headIndex != -1 && bodyIndex != -1) - { - AppendingStringBuffer endScript = new AppendingStringBuffer(150); - endScript.append("\n").append(JavaScriptUtils.SCRIPT_OPEN_TAG); - endScript.append("\nwindow.defaultStatus='"); - endScript.append(getStatusString(timeTaken, "ServerAndClientTimeFilter.statustext")); - endScript.append("';\n").append(JavaScriptUtils.SCRIPT_CLOSE_TAG).append("\n"); - responseBuffer.insert(bodyIndex - 1, endScript); - responseBuffer.insert(headIndex + 6, "\n" + JavaScriptUtils.SCRIPT_OPEN_TAG + - "\nvar clientTimeVariable = new Date().getTime();\n" + - JavaScriptUtils.SCRIPT_CLOSE_TAG + "\n"); - } - else if (ajaxStart != -1 && ajaxEnd != -1) + String nonce = getNonce(); + boolean hasBody = headIndex != -1 && bodyIndex != -1; + boolean hasAjaxResponse = ajaxStart != -1 && ajaxEnd != -1; + + if (hasBody || hasAjaxResponse) { AppendingStringBuffer startScript = new AppendingStringBuffer(250); - startScript.append(""); - responseBuffer.insert(ajaxEnd, startScript.toString()); - responseBuffer.insert(ajaxStart + 15, - ""); + startScript.append(createScriptOpenTag(false, nonce)); + startScript.append(JavaScriptUtils.SCRIPT_CONTENT_PREFIX); + startScript.append("clientTimeVariable = new Date().getTime();"); + startScript.append(CDATA_SCRIPT_END); + AppendingStringBuffer endScript = new AppendingStringBuffer(300); + endScript.append(createScriptOpenTag(true, nonce)); + endScript.append(JavaScriptUtils.SCRIPT_CONTENT_PREFIX); + endScript.append("\nwindow.defaultStatus='" + getStatusString(timeTaken, "ajax.ServerAndClientTimeFilter.statustext")); + endScript.append(CDATA_SCRIPT_END); + + if (hasBody) + { + responseBuffer.insert(bodyIndex, endScript); + responseBuffer.insert(headIndex + 6, startScript); + } + else + { + AppendingStringBuffer headerContributionStartBuffer = new AppendingStringBuffer(500); + headerContributionStartBuffer.append(HEADER_CONTRIBUTION_START); + headerContributionStartBuffer.append(startScript); + headerContributionStartBuffer.append(HEADER_CONTRIBUTION_END); + AppendingStringBuffer headerContributionEndBuffer = new AppendingStringBuffer(500); + headerContributionEndBuffer.append(HEADER_CONTRIBUTION_START); + headerContributionEndBuffer.append(endScript); + headerContributionEndBuffer.append(HEADER_CONTRIBUTION_END); + responseBuffer.insert(ajaxEnd, headerContributionEndBuffer); + responseBuffer.insert(ajaxStart + 15, headerContributionStartBuffer); + } } log.info(timeTaken + "ms server time taken for request " + RequestCycle.get().getRequest().getUrl() + " response size: " + responseBuffer.length()); return responseBuffer; } + private String createScriptOpenTag(boolean isAfterDomRender, String nonce) + { + AttributeMap attrs = new AttributeMap(); + attrs.putAttribute(JavaScriptUtils.ATTR_TYPE, "text/javascript"); + if (isAfterDomRender) + { + attrs.putAttribute("data-wicket-evaluation", "after"); + } + if (nonce != null) + { + attrs.putAttribute(JavaScriptUtils.ATTR_CSP_NONCE, "nonce"); + } + return ""; + } + /** * Returns a locale specific status message about the server and client time. * @@ -111,8 +144,16 @@ private String getStatusString(long timeTaken, String resourceKey) .getString(resourceKey, null, "Server parsetime: ${servertime}, Client parsetime: ${clienttime}"); final Map map = new HashMap(4); - map.put("clienttime", "' + (new Date().getTime() - clientTimeVariable)/1000 + 's"); + map.put("clienttime", "' + (new Date().getTime() - clientTimeVariable)/1000 + 's';"); map.put("servertime", ((double)timeTaken) / 1000 + "s"); return MapVariableInterpolator.interpolate(txt, map); } + + /** + * Get CSP nonce + */ + protected String getNonce() { + return null; + } + } \ No newline at end of file diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html index 63c9d0839ae..00b6c1fc960 100644 --- a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html +++ b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html @@ -1,5 +1,5 @@ - +