1 /*
2 * Copyright (c) 2002-2025 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.7 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 * The class can be chained with other {@link ScriptPreProcessor} instances via its constructor.
31 * <p>
32 * Supported patches include:
33 * <ul>
34 * <li>Replacing <code>result.push(...toArray(...))</code> with <code>result.push.apply(result, toArray(...))</code></li>
35 * <li>Replacing <code>result.push(...findAttributeTargets(...))</code> with <code>result.push.apply(result, findAttributeTargets(...))</code></li>
36 * <li>Replacing <code>for (const preservedElt of [...pantry.children])</code> with <code>for (const preservedElt of Array.from(pantry.children))</code></li>
37 * <li>Similar replacements for minified htmx scripts (e.g., <code>htmx.min.js</code>)</li>
38 * </ul>
39 * <p>
40 * <b>Usage Example:</b>
41 * <pre>
42 * try (WebClient webClient = new WebClient()) {
43 * webClient.setScriptPreProcessor(new HtmxTwoZeroSevenScriptPreProcessor());
44 * // use webClient as needed
45 * }
46 * </pre>
47 *
48 * @author Ronald Brill
49 * @see ScriptPreProcessor
50 */
51 public class HtmxTwoZeroSevenScriptPreProcessor implements ScriptPreProcessor {
52
53 private final ScriptPreProcessor nextScriptPreProcessor_;
54
55 /**
56 * Ctor.
57 */
58 public HtmxTwoZeroSevenScriptPreProcessor() {
59 nextScriptPreProcessor_ = null;
60 }
61
62 /**
63 * Ctor.
64 * @param nextScriptPreProcessor the next {@link ScriptPreProcessor}
65 */
66 public HtmxTwoZeroSevenScriptPreProcessor(final ScriptPreProcessor nextScriptPreProcessor) {
67 nextScriptPreProcessor_ = nextScriptPreProcessor;
68 }
69
70 /**
71 * {@inheritDoc}
72 */
73 @Override
74 public String preProcess(final HtmlPage htmlPage, final String sourceCode, final String sourceName,
75 final int lineNumber, final HtmlElement htmlElement) {
76
77 String patchedSourceCode = sourceCode;
78
79 if (sourceName.contains("/htmx.js") && !sourceName.contains("/htmx.js#")) {
80 patchedSourceCode = sourceCode.replace(
81 "result.push(...toArray(rootNode.querySelectorAll(standardSelector)))",
82 "result.push.apply(result, toArray(rootNode.querySelectorAll(standardSelector)))");
83
84 patchedSourceCode = patchedSourceCode.replace(
85 "result.push(...findAttributeTargets(eltToInheritFrom, attrName))",
86 "result.push.apply(result, findAttributeTargets(eltToInheritFrom, attrName))");
87
88 patchedSourceCode = patchedSourceCode.replace(
89 "for (const preservedElt of [...pantry.children]) {",
90 "for (const preservedElt of Array.from(pantry.children)) {");
91 }
92 else if (sourceName.contains("/htmx.min.js") && !sourceName.contains("/htmx.min.js#")) {
93 // 2.0.4
94 patchedSourceCode = sourceCode.replace(
95 "i.push(...M(c.querySelectorAll(e)))",
96 "i.push.apply(i,M(c.querySelectorAll(e)))");
97
98 // 2.0.7
99 patchedSourceCode = patchedSourceCode.replace(
100 "i.push(...F(c.querySelectorAll(e)))",
101 "i.push.apply(i,F(c.querySelectorAll(e)))");
102
103 patchedSourceCode = patchedSourceCode.replace(
104 "r.push(...we(i,n))",
105 "r.push.apply(r,we(i,n))");
106
107 patchedSourceCode = patchedSourceCode.replace(
108 "for(const t of[...e.children]){",
109 "for(const t of Array.from(e.children)){");
110 }
111
112 if (nextScriptPreProcessor_ != null) {
113 return nextScriptPreProcessor_.preProcess(htmlPage, patchedSourceCode, sourceName, lineNumber, htmlElement);
114 }
115
116 return patchedSourceCode;
117 }
118 }