1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.javascript.preprocessor;
16
17 import org.htmlunit.ScriptPreProcessor;
18 import org.htmlunit.html.HtmlElement;
19 import org.htmlunit.html.HtmlPage;
20
21 /**
22 * A {@link ScriptPreProcessor} implementation that applies compatibility patches to
23 * htmx 2.0.3 - 2.0.10 versions of the htmx JavaScript library.
24 * <p>
25 * This preprocessor rewrites certain ECMAScript syntax constructs
26 * (such as spread operator usage in array push and for-of loops over array copies)
27 * to equivalent ES5-compatible code. This is necessary because these features are
28 * not yet supported by the JavaScript engine htmlunit-corejs (Rhino).
29 * </p>
30 * <p>
31 * The class can be chained with other {@link ScriptPreProcessor} instances via its constructor.
32 * </p>
33 * <p>
34 * Supported patches include:
35 * </p>
36 * <ul>
37 * <li>Replacing <code>result.push(...toArray(...))</code> with <code>result.push.apply(result, toArray(...))</code></li>
38 * <li>Replacing <code>result.push(...findAttributeTargets(...))</code> with <code>result.push.apply(result, findAttributeTargets(...))</code></li>
39 * <li>Replacing <code>for (const preservedElt of [...pantry.children])</code> with <code>for (const preservedElt of Array.from(pantry.children))</code></li>
40 * <li>Similar replacements for minified htmx scripts (e.g., <code>htmx.min.js</code>)</li>
41 * </ul>
42 * <p>
43 * <b>Usage Example:</b>
44 * </p>
45 * <pre>
46 * try (WebClient webClient = new WebClient()) {
47 * webClient.setScriptPreProcessor(new HtmxTwoZeroSevenScriptPreProcessor());
48 * // use webClient as needed
49 * }
50 * </pre>
51 *
52 * @author Ronald Brill
53 * @see ScriptPreProcessor
54 */
55 public class HtmxTwoZeroSevenScriptPreProcessor implements ScriptPreProcessor {
56
57 private final ScriptPreProcessor nextScriptPreProcessor_;
58
59 /**
60 * Ctor.
61 */
62 public HtmxTwoZeroSevenScriptPreProcessor() {
63 nextScriptPreProcessor_ = null;
64 }
65
66 /**
67 * Ctor.
68 * @param nextScriptPreProcessor the next {@link ScriptPreProcessor}
69 */
70 public HtmxTwoZeroSevenScriptPreProcessor(final ScriptPreProcessor nextScriptPreProcessor) {
71 nextScriptPreProcessor_ = nextScriptPreProcessor;
72 }
73
74 /**
75 * {@inheritDoc}
76 */
77 @Override
78 public String preProcess(final HtmlPage htmlPage, final String sourceCode, final String sourceName,
79 final int lineNumber, final HtmlElement htmlElement) {
80
81 String patchedSourceCode = sourceCode;
82
83 if (sourceName.contains("/htmx.js") && !sourceName.contains("/htmx.js#")) {
84 patchedSourceCode = sourceCode.replace(
85 "result.push(...toArray(rootNode.querySelectorAll(standardSelector)))",
86 "result.push.apply(result, toArray(rootNode.querySelectorAll(standardSelector)))");
87
88 patchedSourceCode = patchedSourceCode.replace(
89 "result.push(...findAttributeTargets(eltToInheritFrom, attrName))",
90 "result.push.apply(result, findAttributeTargets(eltToInheritFrom, attrName))");
91
92 patchedSourceCode = patchedSourceCode.replace(
93 "for (const preservedElt of [...pantry.children]) {",
94 "for (const preservedElt of Array.from(pantry.children)) {");
95 }
96 else if (sourceName.contains("/htmx.min.js") && !sourceName.contains("/htmx.min.js#")) {
97 // 2.0.4
98 patchedSourceCode = sourceCode.replace(
99 "i.push(...M(c.querySelectorAll(e)))",
100 "i.push.apply(i,M(c.querySelectorAll(e)))");
101
102 // 2.0.7
103 patchedSourceCode = patchedSourceCode.replace(
104 "i.push(...F(c.querySelectorAll(e)))",
105 "i.push.apply(i,F(c.querySelectorAll(e)))");
106
107 patchedSourceCode = patchedSourceCode.replace(
108 "r.push(...we(i,n))",
109 "r.push.apply(r,we(i,n))");
110
111 patchedSourceCode = patchedSourceCode.replace(
112 "for(const t of[...e.children]){",
113 "for(const t of Array.from(e.children)){");
114
115 // 2.0.10
116 patchedSourceCode = patchedSourceCode.replace(
117 "i.push(...F(u.querySelectorAll(e)))",
118 "i.push.apply(i,F(u.querySelectorAll(e)))");
119
120 patchedSourceCode = patchedSourceCode.replace(
121 "r.push(...ve(i,n))",
122 "r.push.apply(r,ve(i,n))");
123 }
124
125 if (nextScriptPreProcessor_ != null) {
126 return nextScriptPreProcessor_.preProcess(htmlPage, patchedSourceCode, sourceName, lineNumber, htmlElement);
127 }
128
129 return patchedSourceCode;
130 }
131 }