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