test_animations_event_order.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. <!doctype html>
  2. <html>
  3. <!--
  4. https://bugzilla.mozilla.org/show_bug.cgi?id=1183461
  5. -->
  6. <!--
  7. This test is similar to those in test_animations.html with the exception
  8. that those tests interact with a single element at a time. The tests in this
  9. file are specifically concerned with testing the ordering of events between
  10. elements for which most of the utilities in animation_utils.js are not
  11. suited.
  12. -->
  13. <head>
  14. <meta charset=utf-8>
  15. <title>Test for CSS Animation and Transition event ordering
  16. (Bug 1183461)</title>
  17. <script type="application/javascript"
  18. src="/tests/SimpleTest/SimpleTest.js"></script>
  19. <!-- We still need animation_utils.js for advance_clock -->
  20. <script type="application/javascript" src="animation_utils.js"></script>
  21. <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
  22. <style>
  23. @keyframes anim { to { margin-left: 100px } }
  24. @keyframes animA { to { margin-left: 100px } }
  25. @keyframes animB { to { margin-left: 100px } }
  26. @keyframes animC { to { margin-left: 100px } }
  27. </style>
  28. </head>
  29. <body>
  30. <a target="_blank"
  31. href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug
  32. 1183461</a>
  33. <div id="display"></div>
  34. <pre id="test">
  35. <script type="application/javascript">
  36. 'use strict';
  37. // Take over the refresh driver right from the start.
  38. advance_clock(0);
  39. // Common test scaffolding
  40. var gEventsReceived = [];
  41. var gDisplay = document.getElementById('display');
  42. [ 'animationstart',
  43. 'animationiteration',
  44. 'animationend',
  45. 'transitionrun',
  46. 'transitionstart',
  47. 'transitionend',
  48. 'transitioncancel' ]
  49. .forEach(event =>
  50. gDisplay.addEventListener(event,
  51. event => gEventsReceived.push(event),
  52. false));
  53. function checkEventOrder(...args) {
  54. // Argument format:
  55. // Arguments = ExpectedEvent*, desc
  56. // ExpectedEvent =
  57. // [ target|animationName|transitionProperty, (pseudo,) message ]
  58. var expectedEvents = args.slice(0, -1);
  59. var desc = args[args.length - 1];
  60. var isTestingNameOrProperty = expectedEvents.length &&
  61. typeof expectedEvents[0][0] == 'string';
  62. var formatEvent = (target, nameOrProperty, pseudo, message) =>
  63. isTestingNameOrProperty ?
  64. `${nameOrProperty}${pseudo}:${message}` :
  65. `${target.id}${pseudo}:${message}`;
  66. var actual = gEventsReceived.map(
  67. event => formatEvent(event.target,
  68. event.animationName || event.propertyName,
  69. event.pseudoElement, event.type)
  70. ).join(';');
  71. var expected = expectedEvents.map(
  72. event => event.length == 3 ?
  73. formatEvent(event[0], event[0], event[1], event[2]) :
  74. formatEvent(event[0], event[0], '', event[1])
  75. ).join(';');
  76. is(actual, expected, desc);
  77. gEventsReceived = [];
  78. }
  79. // 1. TESTS FOR SORTING BY TREE ORDER
  80. // 1a. Test that simultaneous events are sorted by tree order (siblings)
  81. var divs = [ document.createElement('div'),
  82. document.createElement('div'),
  83. document.createElement('div') ];
  84. divs.forEach((div, i) => {
  85. gDisplay.appendChild(div);
  86. div.setAttribute('style', 'animation: anim 10s');
  87. div.setAttribute('id', 'div' + i);
  88. getComputedStyle(div).animationName; // trigger building of animation
  89. });
  90. advance_clock(0);
  91. checkEventOrder([ divs[0], 'animationstart' ],
  92. [ divs[1], 'animationstart' ],
  93. [ divs[2], 'animationstart' ],
  94. 'Simultaneous start on siblings');
  95. divs.forEach(div => div.remove());
  96. divs = [];
  97. // 1b. Test that simultaneous events are sorted by tree order (children)
  98. divs = [ document.createElement('div'),
  99. document.createElement('div'),
  100. document.createElement('div') ];
  101. // Create the following arrangement:
  102. //
  103. // display
  104. // / \
  105. // div[0] div[1]
  106. // /
  107. // div[2]
  108. gDisplay.appendChild(divs[0]);
  109. gDisplay.appendChild(divs[1]);
  110. divs[0].appendChild(divs[2]);
  111. divs.forEach((div, i) => {
  112. div.setAttribute('style', 'animation: anim 10s');
  113. div.setAttribute('id', 'div' + i);
  114. getComputedStyle(div).animationName; // trigger building of animation
  115. });
  116. advance_clock(0);
  117. checkEventOrder([ divs[0], 'animationstart' ],
  118. [ divs[2], 'animationstart' ],
  119. [ divs[1], 'animationstart' ],
  120. 'Simultaneous start on children');
  121. divs.forEach(div => div.remove());
  122. divs = [];
  123. // 1c. Test that simultaneous events are sorted by tree order (pseudos)
  124. divs = [ document.createElement('div'),
  125. document.createElement('div') ];
  126. // Create the following arrangement:
  127. //
  128. // display
  129. // |
  130. // div[0]
  131. // ::before,
  132. // ::after
  133. // |
  134. // div[1]
  135. gDisplay.appendChild(divs[0]);
  136. divs[0].appendChild(divs[1]);
  137. divs.forEach((div, i) => {
  138. div.setAttribute('style', 'animation: anim 10s');
  139. div.setAttribute('id', 'div' + i);
  140. });
  141. var extraStyle = document.createElement('style');
  142. document.head.appendChild(extraStyle);
  143. var sheet = extraStyle.sheet;
  144. sheet.insertRule('#div0::after { animation: anim 10s }', 0);
  145. sheet.insertRule('#div0::before { animation: anim 10s }', 1);
  146. getComputedStyle(divs[0]).animationName; // build animation
  147. getComputedStyle(divs[1]).animationName; // build animation
  148. advance_clock(0);
  149. checkEventOrder([ divs[0], 'animationstart' ],
  150. [ divs[0], '::before', 'animationstart' ],
  151. [ divs[0], '::after', 'animationstart' ],
  152. [ divs[1], 'animationstart' ],
  153. 'Simultaneous start on pseudo-elements');
  154. divs.forEach(div => div.remove());
  155. divs = [];
  156. sheet = undefined;
  157. extraStyle.remove();
  158. extraStyle = undefined;
  159. // 2. TESTS FOR SORTING BY TIME
  160. // 2a. Test that events are sorted by time
  161. divs = [ document.createElement('div'),
  162. document.createElement('div') ];
  163. divs.forEach((div, i) => {
  164. gDisplay.appendChild(div);
  165. div.setAttribute('style', 'animation: anim 10s');
  166. div.setAttribute('id', 'div' + i);
  167. });
  168. divs[0].style.animationDelay = '5s';
  169. advance_clock(0);
  170. advance_clock(20000);
  171. checkEventOrder([ divs[1], 'animationstart' ],
  172. [ divs[0], 'animationstart' ],
  173. [ divs[1], 'animationend' ],
  174. [ divs[0], 'animationend' ],
  175. 'Sorting of start and end events by time');
  176. divs.forEach(div => div.remove());
  177. divs = [];
  178. // 2b. Test different events within the one element
  179. var div = document.createElement('div');
  180. gDisplay.appendChild(div);
  181. div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s
  182. 'anim 10s 5s, ' + // Start at t=5s
  183. 'anim 3s'; // End at t=3s
  184. div.setAttribute('id', 'div');
  185. getComputedStyle(div).animationName; // build animation
  186. advance_clock(0);
  187. advance_clock(5000);
  188. checkEventOrder([ div, 'animationstart' ],
  189. [ div, 'animationstart' ],
  190. [ div, 'animationend' ],
  191. [ div, 'animationiteration' ],
  192. [ div, 'animationstart' ],
  193. 'Sorting of different events by time within an element');
  194. div.remove();
  195. div = undefined;
  196. // 2c. Test negative delay is sorted equal to zero delay but before
  197. // positive delay
  198. divs = [ document.createElement('div'),
  199. document.createElement('div'),
  200. document.createElement('div') ];
  201. divs.forEach((div, i) => {
  202. gDisplay.appendChild(div);
  203. div.setAttribute('id', 'div' + i);
  204. });
  205. divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last
  206. divs[1].style.animation = 'anim 20s'; // 0s delay
  207. divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as
  208. // 0s delay, i.e. use document
  209. // position
  210. advance_clock(0);
  211. advance_clock(5000);
  212. checkEventOrder([ divs[1], 'animationstart' ],
  213. [ divs[2], 'animationstart' ],
  214. [ divs[0], 'animationstart' ],
  215. 'Sorting of events including negative delay');
  216. divs.forEach(div => div.remove());
  217. divs = [];
  218. // 3. TESTS FOR SORTING BY animation-name POSITION
  219. // 3a. Test animation-name position
  220. div = document.createElement('div');
  221. gDisplay.appendChild(div);
  222. div.style.animation = 'animA 10s, animB 5s, animC 5s 2';
  223. div.setAttribute('id', 'div');
  224. getComputedStyle(div).animationName; // build animation
  225. advance_clock(0);
  226. checkEventOrder([ 'animA', 'animationstart' ],
  227. [ 'animB', 'animationstart' ],
  228. [ 'animC', 'animationstart' ],
  229. 'Sorting of simultaneous animationstart events by ' +
  230. 'animation-name');
  231. advance_clock(5000);
  232. checkEventOrder([ 'animB', 'animationend' ],
  233. [ 'animC', 'animationiteration' ],
  234. 'Sorting of different types of events by animation-name');
  235. div.remove();
  236. div = undefined;
  237. // 3b. Test time trumps animation-name position
  238. div = document.createElement('div');
  239. gDisplay.appendChild(div);
  240. div.style.animation = 'animA 10s 2s, animB 10s 1s';
  241. div.setAttribute('id', 'div');
  242. advance_clock(0);
  243. advance_clock(3000);
  244. checkEventOrder([ 'animB', 'animationstart' ],
  245. [ 'animA', 'animationstart' ],
  246. 'Events are sorted by time first, before animation-position');
  247. div.remove();
  248. div = undefined;
  249. // 4. TESTS FOR TRANSITIONS
  250. // 4a. Test sorting transitions by document position
  251. divs = [ document.createElement('div'),
  252. document.createElement('div') ];
  253. divs.forEach((div, i) => {
  254. gDisplay.appendChild(div);
  255. div.style.marginLeft = '0px';
  256. div.style.transition = 'margin-left 10s';
  257. div.setAttribute('id', 'div' + i);
  258. });
  259. getComputedStyle(divs[0]).marginLeft;
  260. divs.forEach(div => div.style.marginLeft = '100px');
  261. getComputedStyle(divs[0]).marginLeft;
  262. advance_clock(0);
  263. advance_clock(10000);
  264. checkEventOrder([ divs[0], 'transitionrun' ],
  265. [ divs[0], 'transitionstart' ],
  266. [ divs[1], 'transitionrun' ],
  267. [ divs[1], 'transitionstart' ],
  268. [ divs[0], 'transitionend' ],
  269. [ divs[1], 'transitionend' ],
  270. 'Simultaneous transitionrun/start/end on siblings');
  271. divs.forEach(div => div.remove());
  272. divs = [];
  273. // 4b. Test sorting transitions by document position (children)
  274. divs = [ document.createElement('div'),
  275. document.createElement('div'),
  276. document.createElement('div') ];
  277. // Create the following arrangement:
  278. //
  279. // display
  280. // / \
  281. // div[0] div[1]
  282. // /
  283. // div[2]
  284. gDisplay.appendChild(divs[0]);
  285. gDisplay.appendChild(divs[1]);
  286. divs[0].appendChild(divs[2]);
  287. divs.forEach((div, i) => {
  288. div.style.marginLeft = '0px';
  289. div.style.transition = 'margin-left 10s';
  290. div.setAttribute('id', 'div' + i);
  291. });
  292. getComputedStyle(divs[0]).marginLeft;
  293. divs.forEach(div => div.style.marginLeft = '100px');
  294. getComputedStyle(divs[0]).marginLeft;
  295. advance_clock(0);
  296. advance_clock(10000);
  297. checkEventOrder([ divs[0], 'transitionrun' ],
  298. [ divs[0], 'transitionstart' ],
  299. [ divs[2], 'transitionrun' ],
  300. [ divs[2], 'transitionstart' ],
  301. [ divs[1], 'transitionrun' ],
  302. [ divs[1], 'transitionstart' ],
  303. [ divs[0], 'transitionend' ],
  304. [ divs[2], 'transitionend' ],
  305. [ divs[1], 'transitionend' ],
  306. 'Simultaneous transitionrun/start/end on children');
  307. divs.forEach(div => div.remove());
  308. divs = [];
  309. // 4c. Test sorting transitions by document position (pseudos)
  310. divs = [ document.createElement('div'),
  311. document.createElement('div') ];
  312. // Create the following arrangement:
  313. //
  314. // display
  315. // |
  316. // div[0]
  317. // ::before,
  318. // ::after
  319. // |
  320. // div[1]
  321. gDisplay.appendChild(divs[0]);
  322. divs[0].appendChild(divs[1]);
  323. divs.forEach((div, i) => {
  324. div.setAttribute('id', 'div' + i);
  325. });
  326. extraStyle = document.createElement('style');
  327. document.head.appendChild(extraStyle);
  328. sheet = extraStyle.sheet;
  329. sheet.insertRule('div, #div0::after, #div0::before { ' +
  330. ' transition: margin-left 10s; ' +
  331. ' margin-left: 0px }', 0);
  332. sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' +
  333. ' margin-left: 100px }', 1);
  334. sheet.insertRule('#div0::after, #div0::before { ' +
  335. ' content: " " }', 2);
  336. getComputedStyle(divs[0]).marginLeft;
  337. divs.forEach(div => div.classList.add('active'));
  338. getComputedStyle(divs[0]).marginLeft;
  339. advance_clock(0);
  340. advance_clock(10000);
  341. checkEventOrder([ divs[0], 'transitionrun' ],
  342. [ divs[0], 'transitionstart' ],
  343. [ divs[0], '::before', 'transitionrun' ],
  344. [ divs[0], '::before', 'transitionstart' ],
  345. [ divs[0], '::after', 'transitionrun' ],
  346. [ divs[0], '::after', 'transitionstart' ],
  347. [ divs[1], 'transitionrun' ],
  348. [ divs[1], 'transitionstart' ],
  349. [ divs[0], 'transitionend' ],
  350. [ divs[0], '::before', 'transitionend' ],
  351. [ divs[0], '::after', 'transitionend' ],
  352. [ divs[1], 'transitionend' ],
  353. 'Simultaneous transitionrun/start/end on pseudo-elements');
  354. divs.forEach(div => div.remove());
  355. divs = [];
  356. sheet = undefined;
  357. extraStyle.remove();
  358. extraStyle = undefined;
  359. // 4d. Test sorting transitions by time
  360. divs = [ document.createElement('div'),
  361. document.createElement('div') ];
  362. divs.forEach((div, i) => {
  363. gDisplay.appendChild(div);
  364. div.style.marginLeft = '0px';
  365. div.setAttribute('id', 'div' + i);
  366. });
  367. divs[0].style.transition = 'margin-left 10s';
  368. divs[1].style.transition = 'margin-left 5s';
  369. getComputedStyle(divs[0]).marginLeft;
  370. divs.forEach(div => div.style.marginLeft = '100px');
  371. getComputedStyle(divs[0]).marginLeft;
  372. advance_clock(0);
  373. advance_clock(10000);
  374. checkEventOrder([ divs[0], 'transitionrun' ],
  375. [ divs[0], 'transitionstart' ],
  376. [ divs[1], 'transitionrun' ],
  377. [ divs[1], 'transitionstart' ],
  378. [ divs[1], 'transitionend' ],
  379. [ divs[0], 'transitionend' ],
  380. 'Sorting of transitionrun/start/end events by time');
  381. divs.forEach(div => div.remove());
  382. divs = [];
  383. // 4e. Test sorting transitions by time (with delay)
  384. divs = [ document.createElement('div'),
  385. document.createElement('div') ];
  386. divs.forEach((div, i) => {
  387. gDisplay.appendChild(div);
  388. div.style.marginLeft = '0px';
  389. div.setAttribute('id', 'div' + i);
  390. });
  391. divs[0].style.transition = 'margin-left 5s 5s';
  392. divs[1].style.transition = 'margin-left 5s';
  393. getComputedStyle(divs[0]).marginLeft;
  394. divs.forEach(div => div.style.marginLeft = '100px');
  395. getComputedStyle(divs[0]).marginLeft;
  396. advance_clock(0);
  397. advance_clock(10 * 1000);
  398. checkEventOrder([ divs[0], 'transitionrun' ],
  399. [ divs[1], 'transitionrun' ],
  400. [ divs[1], 'transitionstart' ],
  401. [ divs[0], 'transitionstart' ],
  402. [ divs[1], 'transitionend' ],
  403. [ divs[0], 'transitionend' ],
  404. 'Sorting of transitionrun/start/end events by time' +
  405. '(including delay)');
  406. divs.forEach(div => div.remove());
  407. divs = [];
  408. // 4f. Test sorting transitions by transition-property
  409. div = document.createElement('div');
  410. gDisplay.appendChild(div);
  411. div.style.opacity = '0';
  412. div.style.marginLeft = '0px';
  413. div.style.transition = 'all 5s';
  414. getComputedStyle(div).marginLeft;
  415. div.style.opacity = '1';
  416. div.style.marginLeft = '100px';
  417. getComputedStyle(div).marginLeft;
  418. advance_clock(0);
  419. advance_clock(10000);
  420. checkEventOrder([ 'margin-left', 'transitionrun' ],
  421. [ 'margin-left', 'transitionstart' ],
  422. [ 'opacity', 'transitionrun' ],
  423. [ 'opacity', 'transitionstart' ],
  424. [ 'margin-left', 'transitionend' ],
  425. [ 'opacity', 'transitionend' ],
  426. 'Sorting of transitionrun/start/end events by ' +
  427. 'transition-property')
  428. div.remove();
  429. div = undefined;
  430. // 4g. Test document position beats transition-property
  431. divs = [ document.createElement('div'),
  432. document.createElement('div') ];
  433. divs.forEach((div, i) => {
  434. gDisplay.appendChild(div);
  435. div.style.marginLeft = '0px';
  436. div.style.opacity = '0';
  437. div.style.transition = 'all 10s';
  438. div.setAttribute('id', 'div' + i);
  439. });
  440. getComputedStyle(divs[0]).marginLeft;
  441. divs[0].style.opacity = '1';
  442. divs[1].style.marginLeft = '100px';
  443. getComputedStyle(divs[0]).marginLeft;
  444. advance_clock(0);
  445. advance_clock(10000);
  446. checkEventOrder([ divs[0], 'transitionrun' ],
  447. [ divs[0], 'transitionstart' ],
  448. [ divs[1], 'transitionrun' ],
  449. [ divs[1], 'transitionstart' ],
  450. [ divs[0], 'transitionend' ],
  451. [ divs[1], 'transitionend' ],
  452. 'Transition events are sorted by document position first, ' +
  453. 'before transition-property');
  454. divs.forEach(div => div.remove());
  455. divs = [];
  456. // 4h. Test time beats transition-property
  457. div = document.createElement('div');
  458. gDisplay.appendChild(div);
  459. div.style.opacity = '0';
  460. div.style.marginLeft = '0px';
  461. div.style.transition = 'margin-left 10s, opacity 5s';
  462. getComputedStyle(div).marginLeft;
  463. div.style.opacity = '1';
  464. div.style.marginLeft = '100px';
  465. getComputedStyle(div).marginLeft;
  466. advance_clock(0);
  467. advance_clock(10000);
  468. checkEventOrder([ 'margin-left', 'transitionrun' ],
  469. [ 'margin-left', 'transitionstart' ],
  470. [ 'opacity', 'transitionrun' ],
  471. [ 'opacity', 'transitionstart' ],
  472. [ 'opacity', 'transitionend' ],
  473. [ 'margin-left', 'transitionend' ],
  474. 'Transition events are sorted by time first, before ' +
  475. 'transition-property');
  476. div.remove();
  477. div = undefined;
  478. // 4i. Test sorting transitions by document position (negative delay)
  479. divs = [ document.createElement('div'),
  480. document.createElement('div') ];
  481. divs.forEach((div, i) => {
  482. gDisplay.appendChild(div);
  483. div.style.marginLeft = '0px';
  484. div.setAttribute('id', 'div' + i);
  485. });
  486. divs[0].style.transition = 'margin-left 10s 5s';
  487. divs[1].style.transition = 'margin-left 10s';
  488. getComputedStyle(divs[0]).marginLeft;
  489. divs.forEach(div => div.style.marginLeft = '100px');
  490. getComputedStyle(divs[0]).marginLeft;
  491. advance_clock(0);
  492. advance_clock(15 * 1000);
  493. checkEventOrder([ divs[0], 'transitionrun' ],
  494. [ divs[1], 'transitionrun' ],
  495. [ divs[1], 'transitionstart' ],
  496. [ divs[0], 'transitionstart' ],
  497. [ divs[1], 'transitionend' ],
  498. [ divs[0], 'transitionend' ],
  499. 'Simultaneous transitionrun/start/end on siblings');
  500. divs.forEach(div => div.remove());
  501. divs = [];
  502. // 4j. Test sorting transitions with cancel
  503. // The order of transitioncancel is based on StyleManager.
  504. // So this test looks like wrong result at a glance. However
  505. // the gecko will cancel div1's transition before div2 in this case.
  506. divs = [ document.createElement('div'),
  507. document.createElement('div') ];
  508. divs.forEach((div, i) => {
  509. gDisplay.appendChild(div);
  510. div.style.marginLeft = '0px';
  511. div.setAttribute('id', 'div' + i);
  512. });
  513. divs[0].style.transition = 'margin-left 10s 5s';
  514. divs[1].style.transition = 'margin-left 10s';
  515. getComputedStyle(divs[0]).marginLeft;
  516. divs.forEach(div => div.style.marginLeft = '100px');
  517. getComputedStyle(divs[0]).marginLeft;
  518. advance_clock(0);
  519. advance_clock(5 * 1000);
  520. divs.forEach(div => div.style.display = 'none' );
  521. getComputedStyle(divs[0]).display;
  522. advance_clock(10 * 1000);
  523. checkEventOrder([ divs[0], 'transitionrun' ],
  524. [ divs[1], 'transitionrun' ],
  525. [ divs[1], 'transitionstart' ],
  526. [ divs[0], 'transitionstart' ],
  527. [ divs[1], 'transitioncancel' ],
  528. [ divs[0], 'transitioncancel' ],
  529. 'Simultaneous transitionrun/start/cancel on siblings');
  530. divs.forEach(div => div.remove());
  531. divs = [];
  532. SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
  533. </script>
  534. </body>
  535. </html>