diff --git a/bom/pom.xml b/bom/pom.xml
index 870a925f93c7..ab691c2e3331 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -40,7 +40,7 @@ THE SOFTWARE.
9.6
2.0.12
- 1822.v120278426e1c
+ 1839.ved17667b_a_eb_5
2.4.21
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index d27160aaa8a2..dbf031d2461a 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -2242,10 +2242,19 @@ public static boolean isWipeOutPermissionEnabled() {
return SystemProperties.getBoolean("hudson.security.WipeOutPermission");
}
+ @Deprecated
public static String createRenderOnDemandProxy(JellyContext context, String attributesToCapture) {
return Stapler.getCurrentRequest().createJavaScriptProxy(new RenderOnDemandClosure(context, attributesToCapture));
}
+ /**
+ * Called from renderOnDemand.jelly to generate the parameters for the proxy object generation.
+ */
+ @Restricted(NoExternalUse.class)
+ public static StaplerRequest.RenderOnDemandParameters createRenderOnDemandProxyParameters(JellyContext context, String attributesToCapture) {
+ return Stapler.getCurrentRequest().createJavaScriptProxyParameters(new RenderOnDemandClosure(context, attributesToCapture));
+ }
+
public static String getCurrentDescriptorByNameUrl() {
return Descriptor.getCurrentDescriptorByNameUrl();
}
diff --git a/core/src/main/resources/lib/layout/renderOnDemand.jelly b/core/src/main/resources/lib/layout/renderOnDemand.jelly
index 029fb82d4cb8..dc2e2ecbde3a 100644
--- a/core/src/main/resources/lib/layout/renderOnDemand.jelly
+++ b/core/src/main/resources/lib/layout/renderOnDemand.jelly
@@ -36,8 +36,12 @@ THE SOFTWARE.
+
render-on-demand ${attrs.clazz}
- ${h.createRenderOnDemandProxy(context,attrs.capture)}
+ ${parameters.proxyMethod}
+ ${parameters.url}
+ ${parameters.crumb}
+ ${parameters.urlNames}
diff --git a/test/src/test/java/org/kohsuke/stapler/BindTest.java b/test/src/test/java/org/kohsuke/stapler/BindTest.java
new file mode 100644
index 000000000000..5c32adf15a58
--- /dev/null
+++ b/test/src/test/java/org/kohsuke/stapler/BindTest.java
@@ -0,0 +1,176 @@
+package org.kohsuke.stapler;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import hudson.ExtensionList;
+import hudson.model.InvisibleAction;
+import hudson.model.RootAction;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.htmlunit.Page;
+import org.htmlunit.ScriptException;
+import org.htmlunit.html.HtmlPage;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.bind.JavaScriptMethod;
+import org.kohsuke.stapler.bind.WithWellKnownURL;
+
+@RunWith(Parameterized.class)
+public class BindTest {
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Parameterized.Parameters
+ public static List contexts() {
+ return Arrays.asList("/jenkins", "");
+ }
+
+ public BindTest(String contextPath) {
+ j.contextPath = contextPath;
+ }
+
+ @Test
+ public void bindNormal() throws Exception {
+ final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class);
+ try (JenkinsRule.WebClient wc = j.createWebClient()) {
+ final HtmlPage htmlPage = wc.goTo(root.getUrlName());
+ final String scriptUrl = htmlPage
+ .getElementsByTagName("script")
+ .stream()
+ .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/$stapler/bound/"))
+ .findFirst()
+ .orElseThrow()
+ .getAttribute("src");
+
+ final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript");
+ final String content = script.getWebResponse().getContentAsString();
+ assertThat(content, startsWith("varname = makeStaplerProxy('" + j.contextPath + "/$stapler/bound/"));
+ assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);"));
+ }
+ assertThat(root.invocations, is(1));
+ }
+
+ @Test
+ public void bindWithWellKnownURL() throws Exception {
+ final RootActionWithWellKnownURL root = ExtensionList.lookupSingleton(RootActionWithWellKnownURL.class);
+ try (JenkinsRule.WebClient wc = j.createWebClient()) {
+ final HtmlPage htmlPage = wc.goTo(root.getUrlName());
+ final String scriptUrl = htmlPage
+ .getElementsByTagName("script")
+ .stream()
+ .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/theWellKnownRoot?"))
+ .findFirst()
+ .orElseThrow()
+ .getAttribute("src");
+
+ final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript");
+ assertThat(script.getWebResponse().getContentAsString(), is("varname = makeStaplerProxy('" + j.contextPath + "/theWellKnownRoot','test',['annotatedJsMethod2','byName2']);"));
+ }
+ assertThat(root.invocations, is(1));
+ }
+
+ @Test
+ public void bindNull() throws Exception {
+ final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class);
+ try (JenkinsRule.WebClient wc = j.createWebClient()) {
+ final ScriptException exception = assertThrows(ScriptException.class, () -> wc.goTo(root.getUrlName() + "/null"));
+ assertThat(exception.getFailingLineNumber(), is(2));
+ assertThat(exception.getFailingColumnNumber(), is(0));
+ assertThat(exception.getMessage(), containsString("TypeError: Cannot call method \"byName1\" of null"));
+
+ final HtmlPage htmlPage = exception.getPage();
+ final String scriptUrl = htmlPage.getElementsByTagName("script").stream().filter(it -> it.getAttribute("src").equals(j.contextPath + "/$stapler/bound/script/null?var=varname")).findFirst().orElseThrow().getAttribute("src");
+
+ final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript");
+ final String content = script.getWebResponse().getContentAsString();
+ assertThat(content, is("varname = null;"));
+ }
+ assertThat(root.invocations, is(0));
+ }
+
+ @Test
+ public void bindUnsafe() throws Exception {
+ final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class);
+ try (JenkinsRule.WebClient wc = j.createWebClient()) {
+ final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/unsafe-var");
+ final String content = htmlPage
+ .getElementsByTagName("script")
+ .stream()
+ .filter(it -> it.getTextContent().contains("makeStaplerProxy"))
+ .findFirst()
+ .orElseThrow()
+ .getTextContent();
+
+ assertThat(content, startsWith("window['varname']=makeStaplerProxy('" + j.contextPath + "/$stapler/bound/"));
+ assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);"));
+ }
+ assertThat(root.invocations, is(1));
+ }
+
+ @Test
+ public void bindInlineNull() throws Exception {
+ final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class);
+ try (JenkinsRule.WebClient wc = j.createWebClient()) {
+ final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/inline-null");
+ final String content = htmlPage
+ .getElementsByTagName("script")
+ .stream()
+ .filter(it -> it.getTextContent().contains("var inline"))
+ .findFirst()
+ .orElseThrow()
+ .getTextContent();
+
+ assertThat(content, containsString("var inline = null"));
+ }
+ assertThat(root.invocations, is(0));
+ }
+
+ @TestExtension
+ public static class RootActionImpl extends InvisibleAction implements RootAction {
+ private int invocations;
+
+ @Override
+ public String getUrlName() {
+ return "theRoot";
+ }
+
+ @JavaScriptMethod
+ public void annotatedJsMethod1(String foo) {}
+
+ public void jsByName1() {
+ invocations++;
+ }
+ }
+
+ @TestExtension
+ public static class RootActionWithWellKnownURL extends InvisibleAction implements RootAction, WithWellKnownURL {
+ private int invocations;
+
+ @Override
+ public String getUrlName() {
+ return "theWellKnownRoot";
+ }
+
+ @Override
+ public String getWellKnownUrl() {
+ return "/" + getUrlName();
+ }
+
+ @JavaScriptMethod
+ public void annotatedJsMethod2(String foo) {}
+
+ public void jsByName2() {
+ invocations++;
+ }
+ }
+}
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js
new file mode 100644
index 000000000000..952a9de74812
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-undef
+varname.byName1();
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly
new file mode 100644
index 000000000000..1de10c5ac222
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly
@@ -0,0 +1,10 @@
+
+
+
+
+ Root Action
+
+
+
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly
new file mode 100644
index 000000000000..b99131131c1a
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly
@@ -0,0 +1,11 @@
+
+
+
+
+ Root Action
+
+
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly
new file mode 100644
index 000000000000..cb81f08c8de5
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly
@@ -0,0 +1,10 @@
+
+
+
+
+ Root Action with null
+
+
+
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly
new file mode 100644
index 000000000000..ccb261682ba4
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly
@@ -0,0 +1,10 @@
+
+
+
+
+ Root Action
+
+
+
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js
new file mode 100644
index 000000000000..268b06ff66a4
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-undef
+varname.byName2();
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly
new file mode 100644
index 000000000000..563805f4889c
--- /dev/null
+++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly
@@ -0,0 +1,10 @@
+
+
+
+
+ Root Action
+
+
+
+
+
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js
index 3be3a4fddadb..efbfb8d17959 100644
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -996,7 +996,13 @@ function renderOnDemand(e, callback, noBehaviour) {
if (!e || !e.classList.contains("render-on-demand")) {
return;
}
- var proxy = eval(e.getAttribute("proxy"));
+
+ let proxyMethod = e.getAttribute("data-proxy-method");
+ let proxyUrl = e.getAttribute("data-proxy-url");
+ let proxyCrumb = e.getAttribute("data-proxy-crumb");
+ let proxyUrlNames = e.getAttribute("data-proxy-url-names").split(",");
+
+ var proxy = window[proxyMethod](proxyUrl, proxyCrumb, proxyUrlNames);
proxy.render(function (t) {
var contextTagName = e.parentNode.tagName;
var c;