1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.dom;
16
17 import java.net.URL;
18 import java.util.Arrays;
19
20 import org.htmlunit.WebDriverTestCase;
21 import org.htmlunit.junit.annotation.Alerts;
22 import org.htmlunit.junit.annotation.BuggyWebDriver;
23 import org.junit.jupiter.api.Test;
24 import org.openqa.selenium.By;
25 import org.openqa.selenium.WebDriver;
26
27
28
29
30
31
32
33
34 public class MutationObserverTest extends WebDriverTestCase {
35
36
37
38
39 @Test
40 @Alerts("ReferenceError")
41 public void observeNullNode() throws Exception {
42 final String html = DOCTYPE_HTML
43 + "<html><head><script>\n"
44 + LOG_TITLE_FUNCTION
45 + "function test() {\n"
46 + " var observer = new MutationObserver(function(mutations) {});\n"
47 + "\n"
48 + " try {\n"
49 + " observer.observe(div, {});\n"
50 + " } catch(e) { logEx(e); }\n"
51 + "}\n"
52 + "</script></head>\n"
53 + "<body onload='test()'>\n"
54 + " <div id='myDiv'>old</div>\n"
55 + "</body></html>";
56
57 loadPageVerifyTitle2(html);
58 }
59
60
61
62
63 @Test
64 @Alerts("TypeError")
65 public void observeNullInit() throws Exception {
66 final String html = DOCTYPE_HTML
67 + "<html><head><script>\n"
68 + LOG_TITLE_FUNCTION
69 + "function test() {\n"
70 + " var div = document.getElementById('myDiv');\n"
71 + " var observer = new MutationObserver(function(mutations) {});\n"
72 + "\n"
73 + " try {\n"
74 + " observer.observe(div);\n"
75 + " } catch(e) { logEx(e); }\n"
76 + "}\n"
77 + "</script></head>\n"
78 + "<body onload='test()'>\n"
79 + " <div id='myDiv'>old</div>\n"
80 + "</body></html>";
81
82 loadPageVerifyTitle2(html);
83 }
84
85
86
87
88 @Test
89 @Alerts("TypeError")
90 public void observeEmptyInit() throws Exception {
91 final String html = DOCTYPE_HTML
92 + "<html><head><script>\n"
93 + LOG_TITLE_FUNCTION
94 + "function test() {\n"
95 + " var div = document.getElementById('myDiv');\n"
96 + " var observer = new MutationObserver(function(mutations) {});\n"
97 + "\n"
98 + " try {\n"
99 + " observer.observe(div, {});\n"
100 + " } catch(e) { logEx(e); }\n"
101 + "}\n"
102 + "</script></head>\n"
103 + "<body onload='test()'>\n"
104 + " <div id='myDiv'>old</div>\n"
105 + "</body></html>";
106
107 loadPageVerifyTitle2(html);
108 }
109
110
111
112
113 @Test
114 @Alerts({"TypeError", "childList", "attributes", "characterData"})
115 public void observeRequiredMissingInit() throws Exception {
116 final String html = DOCTYPE_HTML
117 + "<html><head><script>\n"
118 + LOG_TITLE_FUNCTION
119 + "function test() {\n"
120 + " var div = document.getElementById('myDiv');\n"
121 + " var observer = new MutationObserver(function(mutations) {});\n"
122 + "\n"
123 + " try {\n"
124 + " observer.observe(div, {subtree: true});\n"
125 + " } catch(e) { logEx(e); }\n"
126 + " try {\n"
127 + " observer.observe(div, {childList: true});\n"
128 + " log('childList');\n"
129 + " } catch(e) { logEx(e); }\n"
130 + " try {\n"
131 + " observer.observe(div, {attributes: true});\n"
132 + " log('attributes');\n"
133 + " } catch(e) { logEx(e); }\n"
134 + " try {\n"
135 + " observer.observe(div, {characterData: true});\n"
136 + " log('characterData');\n"
137 + " } catch(e) { logEx(e); }\n"
138 + "}\n"
139 + "</script></head>\n"
140 + "<body onload='test()'>\n"
141 + " <div id='myDiv'>old</div>\n"
142 + "</body></html>";
143
144 loadPageVerifyTitle2(html);
145 }
146
147
148
149
150 @Test
151 @Alerts({"old", "new"})
152 public void characterData() throws Exception {
153 final String html = DOCTYPE_HTML
154 + "<html><head><script>\n"
155 + LOG_TITLE_FUNCTION
156 + "function test() {\n"
157 + " var div = document.getElementById('myDiv');\n"
158 + " var observer = new MutationObserver(function(mutations) {\n"
159 + " mutations.forEach(function(mutation) {\n"
160 + " log(mutation.oldValue);\n"
161 + " log(mutation.target.textContent);\n"
162 + " });\n"
163 + " });\n"
164 + "\n"
165 + " observer.observe(div, {\n"
166 + " characterData: true,\n"
167 + " characterDataOldValue: true,\n"
168 + " subtree: true\n"
169 + " });\n"
170 + "\n"
171 + " div.firstChild.textContent = 'new';\n"
172 + "}\n"
173 + "</script></head>\n"
174 + "<body onload='test()'>\n"
175 + " <div id='myDiv'>old</div>\n"
176 + "</body></html>";
177
178 loadPageVerifyTitle2(html);
179 }
180
181
182
183
184 @Test
185 @Alerts({"null", "new"})
186 public void characterDataNoOldValue() throws Exception {
187 final String html = DOCTYPE_HTML
188 + "<html><head><script>\n"
189 + LOG_TITLE_FUNCTION
190 + "function test() {\n"
191 + " var div = document.getElementById('myDiv');\n"
192 + " var observer = new MutationObserver(function(mutations) {\n"
193 + " mutations.forEach(function(mutation) {\n"
194 + " log(mutation.oldValue);\n"
195 + " log(mutation.target.textContent);\n"
196 + " });\n"
197 + " });\n"
198 + "\n"
199 + " observer.observe(div, {\n"
200 + " characterData: true,\n"
201 + " subtree: true\n"
202 + " });\n"
203 + "\n"
204 + " div.firstChild.textContent = 'new';\n"
205 + "}\n"
206 + "</script></head>\n"
207 + "<body onload='test()'>\n"
208 + " <div id='myDiv'>old</div>\n"
209 + "</body></html>";
210
211 loadPageVerifyTitle2(html);
212 }
213
214
215
216
217 @Test
218 public void characterDataNoSubtree() throws Exception {
219 final String html = DOCTYPE_HTML
220 + "<html><head><script>\n"
221 + LOG_TITLE_FUNCTION
222 + "function test() {\n"
223 + " var div = document.getElementById('myDiv');\n"
224 + " var observer = new MutationObserver(function(mutations) {\n"
225 + " mutations.forEach(function(mutation) {\n"
226 + " log(mutation.oldValue);\n"
227 + " log(mutation.target.textContent);\n"
228 + " });\n"
229 + " });\n"
230 + "\n"
231 + " observer.observe(div, {\n"
232 + " characterData: true,\n"
233 + " characterDataOldValue: true\n"
234 + " });\n"
235 + "\n"
236 + " div.firstChild.textContent = 'new';\n"
237 + "}\n"
238 + "</script></head>\n"
239 + "<body onload='test()'>\n"
240 + " <div id='myDiv'>old</div>\n"
241 + "</body></html>";
242 loadPageVerifyTitle2(html);
243 }
244
245
246
247
248 @Test
249 @Alerts({"attributes", "ltr"})
250 public void attributes() throws Exception {
251 final String html = DOCTYPE_HTML
252 + "<html><head><script>\n"
253 + LOG_TITLE_FUNCTION
254 + "function test() {\n"
255 + " var div = document.getElementById('myDiv');\n"
256 + " var observer = new MutationObserver(function(mutations) {\n"
257 + " mutations.forEach(function(mutation) {\n"
258 + " log(mutation.type);\n"
259 + " log(mutation.oldValue);\n"
260 + " });\n"
261 + " });\n"
262 + "\n"
263 + " observer.observe(div, {\n"
264 + " attributes: true,\n"
265 + " attributeFilter: ['dir'],\n"
266 + " attributeOldValue: true\n"
267 + " });\n"
268 + "\n"
269 + " div.dir = 'rtl';\n"
270 + "}\n"
271 + "</script></head>\n"
272 + "<body onload='test()'>\n"
273 + " <div id='myDiv' dir='ltr'>old</div>\n"
274 + "</body></html>";
275
276 loadPageVerifyTitle2(html);
277 }
278
279
280
281
282
283 @Test
284 @Alerts({"heho", "attributes", "value", "null", "x", "abc",
285 "heho", "attributes", "value", "null", "y", "abc"})
286 public void attributeValue() throws Exception {
287 final String html = DOCTYPE_HTML
288 + "<html>\n"
289 + "<head><script>\n"
290 + LOG_TITLE_FUNCTION
291 + " function test() {\n"
292 + " var config = { attributes: true, childList: true, characterData: true, subtree: true };\n"
293 + " var observer = new MutationObserver(function(mutations) {\n"
294 + " mutations.forEach(function(mutation) {\n"
295 + " log(mutation.type);\n"
296 + " log(mutation.attributeName);\n"
297 + " log(mutation.oldValue);\n"
298 + " log(mutation.target.getAttribute(\"value\"));\n"
299 + " log(mutation.target.value);\n"
300 + " });\n"
301 + " });\n"
302 + " observer.observe(document.getElementById('tester'), config);\n"
303 + " }\n"
304 + "</script></head>\n"
305 + "<body onload='test()'>\n"
306 + " <input id='tester' value=''>\n"
307 + " <button id='doAlert' onclick='log(\"heho\");'>DoAlert</button>\n"
308 + " <button id='doIt' "
309 + "onclick='document.getElementById(\"tester\").setAttribute(\"value\", \"x\")'>"
310 + "DoIt</button>\n"
311 + " <button id='doItAgain' "
312 + " onclick='document.getElementById(\"tester\").setAttribute(\"value\", \"y\")'>"
313 + "DoItAgain</button>\n"
314 + "</body></html>";
315 final WebDriver driver = loadPage2(html);
316 driver.findElement(By.id("tester")).sendKeys("abc");
317 verifyTitle2(driver, new String[] {});
318
319 driver.findElement(By.id("doAlert")).click();
320 verifyTitle2(driver, new String[] {"heho"});
321
322 final String[] expected = getExpectedAlerts();
323 driver.findElement(By.id("doIt")).click();
324 verifyTitle2(driver, Arrays.copyOfRange(expected, 0, 6));
325
326 driver.findElement(By.id("doAlert")).click();
327 verifyTitle2(driver, Arrays.copyOfRange(expected, 0, 7));
328
329 driver.findElement(By.id("doItAgain")).click();
330 verifyTitle2(driver, expected);
331 }
332
333
334
335
336
337 @Test
338 @Alerts({"heho", "attributes", "value", "null", "x", "abc", "0", "0",
339 "heho", "attributes", "value", "null", "null", "abc", "0", "0"})
340 public void attributeValueAddRemove() throws Exception {
341 final String html = DOCTYPE_HTML
342 + "<html>\n"
343 + "<head><script>\n"
344 + LOG_TITLE_FUNCTION
345 + " function test() {\n"
346 + " var config = { attributes: true, childList: true, characterData: true, subtree: true };\n"
347 + " var observer = new MutationObserver(function(mutations) {\n"
348 + " mutations.forEach(function(mutation) {\n"
349 + " log(mutation.type);\n"
350 + " log(mutation.attributeName);\n"
351 + " log(mutation.oldValue);\n"
352 + " log(mutation.target.getAttribute(\"value\"));\n"
353 + " log(mutation.target.value);\n"
354 + " log(mutation.addedNodes.length);\n"
355 + " log(mutation.removedNodes.length);\n"
356 + " });\n"
357 + " });\n"
358 + " observer.observe(document.getElementById('tester'), config);\n"
359 + " }\n"
360 + "</script></head>\n"
361 + "<body onload='test()'>\n"
362 + " <input id='tester'>\n"
363 + " <button id='doAlert' onclick='log(\"heho\");'>DoAlert</button>\n"
364 + " <button id='doIt' "
365 + "onclick='document.getElementById(\"tester\").setAttribute(\"value\", \"x\")'>"
366 + "DoIt</button>\n"
367 + " <button id='doItAgain' "
368 + " onclick='document.getElementById(\"tester\").removeAttribute(\"value\")'>"
369 + "DoItAgain</button>\n"
370 + "</body></html>";
371 final WebDriver driver = loadPage2(html);
372 driver.findElement(By.id("tester")).sendKeys("abc");
373 verifyTitle2(driver, new String[] {});
374
375 driver.findElement(By.id("doAlert")).click();
376 verifyTitle2(driver, new String[] {"heho"});
377
378 final String[] expected = getExpectedAlerts();
379 driver.findElement(By.id("doIt")).click();
380 verifyTitle2(driver, Arrays.copyOfRange(expected, 0, 8));
381
382 driver.findElement(By.id("doAlert")).click();
383 verifyTitle2(driver, Arrays.copyOfRange(expected, 0, 9));
384
385 driver.findElement(By.id("doItAgain")).click();
386 verifyTitle2(driver, expected);
387 }
388
389
390
391
392 @Test
393 @Alerts("[object HTMLHeadingElement]-attributes")
394 @BuggyWebDriver(
395 FF = {"[object HTMLInputElement]-attributesn",
396 "[object HTMLInputElement]-attributes",
397 "[object HTMLInputElement]-attributes",
398 "[object HTMLInputElement]-attributes",
399 "[object HTMLHeadingElement]-attributes"},
400 FF_ESR = {"[object HTMLInputElement]-attributes",
401 "[object HTMLInputElement]-attributes",
402 "[object HTMLInputElement]-attributes",
403 "[object HTMLInputElement]-attributes",
404 "[object HTMLHeadingElement]-attributes"})
405 public void attributeValue2() throws Exception {
406 final String html = DOCTYPE_HTML
407 + "<html><head><script>\n"
408 + LOG_TEXTAREA_FUNCTION
409 + " function makeRed() {\n"
410 + " document.getElementById('headline').setAttribute('style', 'color: red');\n"
411 + " }\n"
412
413 + " function print(mutation) {\n"
414 + " log(mutation.target + '-' + mutation.type);\n"
415 + " }\n"
416
417 + " function test() {\n"
418 + " var mobs = new MutationObserver(function(mutations) {\n"
419 + " mutations.forEach(print)\n"
420 + " });\n"
421
422 + " mobs.observe(document.getElementById('container'), {\n"
423 + " attributes: true,\n"
424 + " childList: true,\n"
425 + " characterData: true,\n"
426 + " subtree: true\n"
427 + " });\n"
428
429 + " document.addEventListener('beforeunload', function() {\n"
430 + " mobs.disconnect();\n"
431 + " });\n"
432 + " }\n"
433 + "</script></head><body onload='test()'>\n"
434 + " <div id='container'>\n"
435 + " <h1 id='headline' style='font-style: italic'>Some headline</h1>\n"
436 + " <input id='id1' type='button' onclick='makeRed()' value='Make Red'>\n"
437 + " </div>\n"
438 + LOG_TEXTAREA
439 + "</body></html>\n";
440 final WebDriver driver = loadPage2(html);
441 driver.findElement(By.id("id1")).click();
442
443 verifyTextArea2(driver, getExpectedAlerts());
444 }
445
446
447
448
449 @Test
450 @Alerts({"before", "after div", "after text", "div observed", "text observed"})
451 public void callbackOrder() throws Exception {
452 final String html = DOCTYPE_HTML
453 + "<html><head><script>\n"
454 + LOG_TITLE_FUNCTION
455 + "function test() {\n"
456 + " var div = document.getElementById('myDiv');\n"
457 + " var divObserver = new MutationObserver(function() {\n"
458 + " log('div observed');\n"
459 + " });\n"
460 + "\n"
461 + " divObserver.observe(div, { attributes: true });\n"
462 + "\n"
463 + " var text = document.createTextNode('')\n"
464 + " var txtObserver = new MutationObserver(function() {\n"
465 + " log('text observed');\n"
466 + " });\n"
467 + " txtObserver.observe(text, { characterData: true });"
468 + "\n"
469 + " log('before');\n"
470 + " div.style = 'background-color: red';\n"
471 + " log('after div');\n"
472 + " text.data = 42;\n"
473 + " log('after text');\n"
474 + "}\n"
475 + "</script></head>\n"
476 + "<body onload='test()'>\n"
477 + " <div id='myDiv' style='color: green'>old</div>\n"
478 + "</body></html>";
479
480 loadPageVerifyTitle2(html);
481 }
482
483
484
485
486 @Test
487 @Alerts("Content")
488 public void callbackRequiresStackSetup() throws Exception {
489 final String content = DOCTYPE_HTML
490 + "<html><head><title>Content</title></head><body><p>content</p></body></html>";
491
492 getMockWebConnection().setResponse(new URL(URL_FIRST, "content.html"), content);
493
494 final String html = DOCTYPE_HTML
495 + "<html><head><script>\n"
496 + "function test() {\n"
497 + "\n"
498 + " var text = document.createTextNode('')\n"
499 + " var txtObserver = new MutationObserver(function() {\n"
500 + " window.location.href = 'content.html'"
501 + " });\n"
502 + " txtObserver.observe(text, { characterData: true });"
503 + "\n"
504 + " text.data = 42\n"
505 + "}\n"
506 + "</script></head>\n"
507 + "<body onload='test()'>\n"
508 + " <div id='myDiv' dir='ltr'>old</div>\n"
509 + "</body></html>";
510
511 final WebDriver driver = loadPage2(html);
512 assertTitle(driver, getExpectedAlerts()[0]);
513 }
514
515
516
517
518 @Test
519 @Alerts(DEFAULT = {"[object MutationObserver]", "", "false"},
520 CHROME = {"[object MutationObserver]", "[object MutationObserver]", "true"},
521 EDGE = {"[object MutationObserver]", "[object MutationObserver]", "true"})
522 public void webKitMutationObserver() throws Exception {
523 final String html = DOCTYPE_HTML
524 + "<html><head>\n"
525 + "<script>\n"
526 + LOG_TITLE_FUNCTION
527 + "function test() {\n"
528 + " var observer = new MutationObserver(function() {});\n"
529 + " var wkObserver = '';\n"
530 + " if (typeof(WebKitMutationObserver) == 'function') {\n"
531 + " wkObserver = new WebKitMutationObserver(function() {});\n"
532 + " }\n"
533 + " log(observer);\n"
534 + " log(wkObserver);\n"
535 + " log(Object.getPrototypeOf(observer) == Object.getPrototypeOf(wkObserver));\n"
536 + "}\n"
537 + "</script></head>\n"
538 + "<body onload='test()'>\n"
539 + "</body></html>";
540
541 loadPageVerifyTitle2(html);
542 }
543 }