1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host;
16
17 import java.nio.charset.StandardCharsets;
18 import java.util.Arrays;
19 import java.util.Base64;
20
21 import org.htmlunit.Page;
22 import org.htmlunit.WebWindow;
23 import org.htmlunit.corejs.javascript.Context;
24 import org.htmlunit.corejs.javascript.Function;
25 import org.htmlunit.corejs.javascript.FunctionObject;
26 import org.htmlunit.corejs.javascript.Scriptable;
27 import org.htmlunit.corejs.javascript.ScriptableObject;
28 import org.htmlunit.corejs.javascript.VarScope;
29 import org.htmlunit.javascript.HtmlUnitScriptable;
30 import org.htmlunit.javascript.JavaScriptEngine;
31 import org.htmlunit.javascript.background.BackgroundJavaScriptFactory;
32 import org.htmlunit.javascript.background.JavaScriptJob;
33 import org.htmlunit.util.StringUtils;
34
35
36
37
38
39
40
41
42
43 public final class WindowOrWorkerGlobalScopeMixin {
44
45
46
47
48
49
50
51 private static final int MIN_TIMER_DELAY = 1;
52
53 private WindowOrWorkerGlobalScopeMixin() {
54 super();
55 }
56
57
58
59
60
61
62
63 public static String atob(final String encodedData, final HtmlUnitScriptable scriptable) {
64 final String withoutWhitespace = StringUtils.replaceChars(encodedData, " \t\r\n\u000c", "");
65 final byte[] bytes = withoutWhitespace.getBytes(StandardCharsets.ISO_8859_1);
66 try {
67 return new String(Base64.getDecoder().decode(bytes), StandardCharsets.ISO_8859_1);
68 }
69 catch (final IllegalArgumentException e) {
70 throw JavaScriptEngine.asJavaScriptException(
71 scriptable,
72 "Failed to execute atob(): " + e.getMessage(),
73 org.htmlunit.javascript.host.dom.DOMException.INVALID_CHARACTER_ERR);
74 }
75 }
76
77
78
79
80
81
82
83 public static String btoa(final String stringToEncode, final HtmlUnitScriptable scriptable) {
84 final int l = stringToEncode.length();
85 for (int i = 0; i < l; i++) {
86 if (stringToEncode.charAt(i) > 255) {
87 throw JavaScriptEngine.asJavaScriptException(
88 scriptable,
89 "Function btoa supports only latin1 characters",
90 org.htmlunit.javascript.host.dom.DOMException.INVALID_CHARACTER_ERR);
91 }
92 }
93
94 final byte[] bytes = stringToEncode.getBytes(StandardCharsets.ISO_8859_1);
95 try {
96 return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
97 }
98 catch (final IllegalArgumentException e) {
99 throw JavaScriptEngine.asJavaScriptException(
100 scriptable,
101 "Failed to execute btoa(): " + e.getMessage(),
102 org.htmlunit.javascript.host.dom.DOMException.INVALID_CHARACTER_ERR);
103 }
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public static Object setTimeout(final Context context, final Scriptable thisObj,
121 final Object[] args, final Function function) {
122 if (args.length < 1) {
123 throw JavaScriptEngine.typeError("Function not provided");
124 }
125
126 final int timeout = JavaScriptEngine.toInt32((args.length > 1) ? args[1] : JavaScriptEngine.UNDEFINED);
127 final Object[] params = (args.length > 2)
128 ? Arrays.copyOfRange(args, 2, args.length)
129 : JavaScriptEngine.EMPTY_ARGS;
130 return setTimeoutIntervalImpl((Window) thisObj, args[0], timeout, true, params);
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144 public static Object setInterval(final Context context, final Scriptable thisObj,
145 final Object[] args, final Function function) {
146 if (args.length < 1) {
147 throw JavaScriptEngine.typeError("Function not provided");
148 }
149
150 final int timeout = JavaScriptEngine.toInt32((args.length > 1) ? args[1] : JavaScriptEngine.UNDEFINED);
151 final Object[] params = (args.length > 2)
152 ? Arrays.copyOfRange(args, 2, args.length)
153 : JavaScriptEngine.EMPTY_ARGS;
154 return setTimeoutIntervalImpl((Window) thisObj, args[0], timeout, false, params);
155 }
156
157
158
159
160
161
162
163
164
165
166 public static Object queueMicrotask(final Scriptable thisObj, final Object[] args) {
167 if (args.length < 1) {
168 throw JavaScriptEngine.typeError("At least 1 argument required");
169 }
170 if (!(args[0] instanceof Function)) {
171 throw JavaScriptEngine.typeError("Argument 1 is not callable");
172 }
173
174 final Function callback = (Function) args[0];
175 final VarScope scope = ScriptableObject.getTopLevelScope(thisObj.getParentScope());
176 final Context cx = Context.getCurrentContext();
177 cx.enqueueMicrotask(() -> {
178 try {
179 callback.call(cx, scope, thisObj, JavaScriptEngine.EMPTY_ARGS);
180 }
181 catch (final Exception e) {
182
183 }
184 });
185
186 return JavaScriptEngine.UNDEFINED;
187 }
188
189 private static int setTimeoutIntervalImpl(final Window window, final Object code,
190 int timeout, final boolean isTimeout, final Object[] params) {
191 if (timeout < MIN_TIMER_DELAY) {
192 timeout = MIN_TIMER_DELAY;
193 }
194
195 final WebWindow webWindow = window.getWebWindow();
196 final Page page = (Page) window.getDomNodeOrNull();
197 Integer period = null;
198 if (!isTimeout) {
199 period = timeout;
200 }
201
202 if (code instanceof String s) {
203 final String description = "window.set"
204 + (isTimeout ? "Timeout" : "Interval")
205 + "(" + s + ", " + timeout + ")";
206 final JavaScriptJob job = BackgroundJavaScriptFactory.theFactory().
207 createJavaScriptJob(timeout, period, description, webWindow, s);
208 return webWindow.getJobManager().addJob(job, page);
209 }
210
211 if (code instanceof Function f) {
212 final String functionName;
213 if (f instanceof FunctionObject object) {
214 functionName = object.getFunctionName();
215 }
216 else {
217 functionName = String.valueOf(f);
218 }
219
220 final String description = "window.set"
221 + (isTimeout ? "Timeout" : "Interval")
222 + "(" + functionName + ", " + timeout + ")";
223 final JavaScriptJob job = BackgroundJavaScriptFactory.theFactory().
224 createJavaScriptJob(timeout, period, description, webWindow, f, params);
225 return webWindow.getJobManager().addJob(job, page);
226 }
227
228 throw JavaScriptEngine.reportRuntimeError("Unknown type for function.");
229 }
230 }