-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why does navigator.mediaDevices.enumerateDevices()
require that Document
must have active keyboard focus?
#905
Comments
The previous discussion states e.g. However that does not seem to be accurate in the scope of the question of this ticket: definitely agree that camera/mic should only be possible to turn on from the currently visible tab. Though So after reading the above tickets, the same questions do still remain:
|
Short answer: trackers. Additionally, users unplugging or inserting a USB device may be time-correlated to uniquely identify them across origins, even if browsers time-fuzz (they don't) the devicechange event that fires then. You'll find the steps that fire the devicechange event contains similar "focus" language for that reason. Since
It might be simpler to do this: if (document.visibilityState == "visible") {
await navigator.mediaDevices.enumerateDevices();
} Longer answer: one might think
Promises have replaced callbacks, but they're still just a mechanism to trigger callbacks. As such they don't interfere with garbage collection, and no resources are "left hanging" in the traditional sense just because callbacks aren't called. |
Sorry, maybe some confusion. I was not referring to any garbage collector dependency here, but to the general loading flow being paused/stalled from progressing on the background.
This is something I did consider at first, but I would object that is a bad and brittle design pattern to implement in user code, since it creeps in/duplicates "magic" logic from the spec, and there is then a bad race condition bug that can happen if the user immediately navigates to another page while the promise is processing. (so the JS code check would pass, but from browser POV visibility was no longer there) That is why the request for a callback form that would actually reject if "now is not a good time". That way developers could actually write logic where they could say their intent to the API. |
Repeating comment from #903: We might want to see if we can get UMA on this to figure out how big breakage might be. |
Any breakage would be sites already not working in Firefox. Not saying this is zero, just that it's likely not a staggering number, or we would have heard about it. |
Hmm, I think there's a bug in the enumerateDevices algorithm here actually: it's referencing the Document in parallel, which is a no-no. I think it needs to move up ahead of the in-parallel steps, which is what Firefox does. I'll do a PR. cc @karlt With that fixed, there is no race possible, because the check is done synchronously. I think the remaining issue is resolving #752 (comment).
I can't speak for everyone, but I think if we can make this work: if (document.visibilityState == "visible") {
await navigator.mediaDevices.enumerateDevices();
} ...then I'd be inclined to say the value of adding the following new API is negative: try {
navigator.mediaDevices.enumerateDevices({rejectIfUnavailable: true});
} catch (e) {
} I say negative, because with web compat so poor right now, I worry exposing such a method would let apps opportunistically wait for focus only on browsers that require it. — Instead, I'd prefer for other user agents to catch up here. |
Thanks! I would recommend adding an explicit guideline note in the spec to hint implementors to realize that this synchronicity is explicitly depended on - so that no implementations will be doing other things under the "as-if" rule. That is because there are likely no unit test suites that can be written to verify that a browser works explicitly under that API contract(?) We are cautious to start relying on the above, just to realize that we'd then get a small % of users hanging the page loads on background in practice when they open a page and then immediately navigate away, if some browser is doing something else under the "as-if" rule. |
Actually referencing a document off main-thread would be a potential security bug that should get caught in review, so this was more of a spec-writing bug I think (e.g. we didn't trip over this in Firefox). We wrote some Mozilla-specific tests last year, but once #752 is resolved, it might be possible to write a test that queues tasks to call
"as-if" would in practice require an interpretation of the spec that puts this check after the actual (time-consuming) enumeration step, I think, which would be a clear violation. But more immediately, you're right since, as mentioned in #752 (comment) Firefox right now also requires system focus, which is hard to check for in JS. So for now I'd recommend: const wait = ms => new Promise(r => setTimeout(r, ms));
if (document.visibilityState == "visible") {
await Promise.race([navigator.mediaDevices.enumerateDevices(), wait(2000)]);
} Not to be taken literally on pageload though, since |
The problem highlighted here is not so much that the async operation does not resolve immediately, but that the useful information about types of devices present is not necessarily available when required. If the promise hasn't resolved at the time that the information is desired, then an application could proceed based on a guess of whether devices are present and adjust later in a similar way to how it might adjustment on a devicechange event. However, the problem highlighted here is that the wrong UI would be visible until the promise resolves. There are some reasons to not make this device information available without a permission gate. |
With #912 merged, enumerateDevices() no longer requires keyboard focus, and the visibility requirement can be ensured synchronously ahead of calling it, as shown in #905 (comment). Leaving this open until tests mentioned there have been added. |
At Unity we are implementing support for Unity web exported content on mobile devices, and one part of that work is exposing access for Unity projects to utilize the webcam and other audio capture devices.
Recently we have gotten reports about issues, where on Firefox, the Unity page load will not progress on the background, but users must have the Unity WebGL game tab active on the foreground for loading to proceed. This is reported only to occur on Firefox.
Further investigating into the issue, the problem is due to the
navigator.mediaDevices.enumerateDevices()
check, which Unity performs at page loading stage. This check populates initial webcam and microphone information to the Unity C# project code to access, and only after it completes, will the main C# content start.The reason for implementing a device enumeration gate to the Unity content loading progress is that after the loading has finished, Unity C# code may be initializing 3D scene data based on the existence availability of a webcam or a microphone.
(Actually starting a webcam/microphone access in Unity is still an asynchronous operation)
However simply querying the set of available devices in Unity is designed to be a synchronous operation. A Unity C# project can potentially access the webcam info immediately at project startup, hence why we gate the actual content startup to run an
enumerateDevices()
step.The web spec states that the
enumerateDevices()
operation does not require a user permissions check. Only starting a device, and getting detailed device info does - which does sound OK for our needs.However, for some odd reason, it has been specced that the Promise returned by
navigator.mediaDevices.enumerateDevices()
should stay lingering until theDocument
object of the calling JS scripting context has acquired keyboard focus. See [1] and [2].What this means is that JS page content which wishes to simply enumerate devices, without necessarily the intent of activating any of them, will not be able to make progress if the page is on the background. As a result, we find ourselves implementing clunky timeout watchdog timers to check if the enumeration has hung and it will be a waste of time to wait for it to resolve.
Such behavior is not ideal, since realizing that the enumeration will likely "never" come (as long as the user is not coming to wake the page up) will take some time as well, and precious startup loading time will have been wasted. The result is that the Unity content will not be able to complete the 3D scene load noninteractively, that may be dependent on the webcam availability.
May I inquire as to what was the rationale in requiring the
Document
to have keyboard focus until device enumeration Promise is allowed to resolve? For what it's worth, it does seem like Firefox, Chrome and Safari are implementing this check differently, and only Firefox does actually require for that to be true. (see [3] and [4])Would it be possible to actually consider removing that requirement? To my understanding that requirement is not serving a security related benefit, since the information that is returned is already non-identifying? (only after acquiring a permission for a device using the Permissions API one will get detailed HW info). Or am I misguided here?
Or if removing the requirement is not at all possible to even consider, would it be possible to be able to perform an enumeration query that would be able to immediately reject the Promise if "now is not the time to allow doing this type of query", so that these types of watchdog timers would not be needed, and JS page load would be able to proceed quickly, without needing to resort to implementing a clumsy watchdog timer?
That way JS pages would not be left hanging, and they could decide to do something else with the precious loading time.
Thanks for considering!
The text was updated successfully, but these errors were encountered: