| <!doctype html> |
| <title>enumerateDevices() with navigation</title> |
| <script src=/resources/testharness.js></script> |
| <script src=/resources/testharnessreport.js></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <body></body> |
| <script> |
| 'use strict'; |
| const blank_url = '/common/blank.html'; |
| const search2 = '?2'; |
| |
| function promise_new_task(t) { |
| return new Promise(resolve => t.step_timeout(resolve, 0)); |
| } |
| function promise_event(target, name) { |
| return new Promise(resolve => target[`on${name}`] = resolve); |
| } |
| |
| promise_test(async t => { |
| // Gecko persists only toplevel documents, so load documents in a toplevel. |
| await test_driver.bless('window.open()'); |
| const proxy = window.open(blank_url); |
| t.add_cleanup(() => proxy.close()); |
| await promise_event(proxy, 'pageshow'); |
| const devices = proxy.navigator.mediaDevices; |
| // Use another task so that another load creates a new session history entry. |
| await promise_new_task(t); |
| |
| proxy.location = blank_url + search2; |
| await promise_event(proxy, 'pagehide'); |
| // Use another task to ensure the first subdocument is no longer fully |
| // active and proxy refers to the realm of the second document. |
| await promise_new_task(t); |
| assert_equals(proxy.location.search, search2, 'navigated search'); |
| // Enumerate from the inactive first Window. |
| const promise_enumerate = devices.enumerateDevices(); |
| // `then()` is used rather than static Promise methods because microtasks |
| // for `PromiseResolve()` do not run when Promises from inactive realms are |
| // involved. Whether microtasks for `then()` run depends on the realm of |
| // the handler rather than the realm of the Promise. |
| // Don't use `finally()`, because it uses `PromiseResolve()` and so |
| // microtasks don't run. |
| // See https://github.com/whatwg/html/issues/5319. |
| let promise_state = 'pending'; |
| promise_enumerate.then(() => promise_state = 'resolved', |
| () => promise_state = 'rejected'); |
| // Enumerate in the active second Window to provide enough time to check |
| // that the Promise from the inactive Window does not settle. |
| await proxy.navigator.mediaDevices.enumerateDevices(); |
| |
| proxy.history.back(); |
| await promise_event(proxy, 'pagehide'); |
| // enumerateDevices() Promise resolution is triggered only in parallel |
| // steps, so manipulation of the Promise (if the first document was |
| // persisted) would occur through a queued task, which would run after |
| // the pagehide event is dispatched and so after the associated |
| // microtask that runs the following assert. |
| // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-for-spec-authors |
| assert_equals(promise_state, 'pending', 'Promise state while inactive'); |
| // If the first document is restored, then that will occur immediately after |
| // pagehide (and associated microtasks), before the next global task is run. |
| // https://html.spec.whatwg.org/multipage/history.html#traverse-the-history-by-a-delta |
| await promise_new_task(t); |
| if (proxy.navigator.mediaDevices == devices) { |
| // The first document was persisted and restored. |
| assert_equals(proxy.location.search, '', 'history search'); |
| await promise_enumerate; |
| } else { |
| // The first document was not restored, but gets re-fetched. |
| await t.step_wait(() => proxy.location.search == '', 'navigation'); |
| assert_not_equals(proxy.navigator.mediaDevices, devices, 'new realm') |
| await proxy.navigator.mediaDevices.enumerateDevices(); |
| assert_equals(promise_state, 'pending', 'Promise state after discard'); |
| } |
| }, 'enumerateDevices with navigation'); |
| </script> |