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