Bug 220184

Summary: WKWebView should expose navigator.mediaDevices when content is loaded from app bundle
Product: WebKit Reporter: Laurent Denoue <ldenoue>
Component: WebRTCAssignee: youenn fablet <youennf>
Status: RESOLVED FIXED    
Severity: Normal CC: ajuma, ashley, brian44, cdumez, eric.amram, eric.carlson, ews-watchlist, ggaren, glenn, haimomesi, jamauro, jer.noble, lpromero, luizfilipe2557, marcbonsels, marvin, michael, niklasmerz, pascal.ethier, philipj, samiboudoukha, sergio, webkit-bug-importer, webkit, youennf
Priority: P2 Keywords: InRadar
Version: Safari 14   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=194666
https://bugs.webkit.org/show_bug.cgi?id=180551
Attachments:
Description Flags
Patch
none
Sample app to test getUserMedia. none

Description Laurent Denoue 2020-12-28 07:39:39 PST
WKWebView now exposes getUserMedia but only when loaded over HTTPS.
For native apps embedding WKWebView, it would be very handy to allow mediaDevices.getUserMedia for pages loaded from the app bundle, or maybe custom handlers.
Comment 1 Radar WebKit Bug Importer 2021-01-04 07:40:15 PST
<rdar://problem/72792032>
Comment 2 youenn fablet 2021-01-05 08:28:51 PST
Created attachment 417006 [details]
Patch
Comment 3 Laurent Denoue 2021-01-06 03:10:52 PST
Why can't we allow file protocol in addition to http or https?

Something like:
 if (origin.protocol() != "http" && origin.protocol() != "https" && origin.protocol() != "file") {
...
}

That way we could get getUserMedia working for resources loaded from the app bundle?
Comment 4 youenn fablet 2021-01-06 03:58:06 PST
(In reply to Laurent Denoue from comment #3)
> Why can't we allow file protocol in addition to http or https?
> 
> Something like:
>  if (origin.protocol() != "http" && origin.protocol() != "https" &&
> origin.protocol() != "file") {
> ...
> }
> 
> That way we could get getUserMedia working for resources loaded from the app
> bundle?

The uploaded patch would handle this case as well.
Comment 5 Laurent Denoue 2021-01-06 05:55:41 PST
(In reply to youenn fablet from comment #4)
> (In reply to Laurent Denoue from comment #3)
> > Why can't we allow file protocol in addition to http or https?
> > 
> > Something like:
> >  if (origin.protocol() != "http" && origin.protocol() != "https" &&
> > origin.protocol() != "file") {
> > ...
> > }
> > 
> > That way we could get getUserMedia working for resources loaded from the app
> > bundle?
> 
> The uploaded patch would handle this case as well.

Yes I saw it now, sorry about that. You removed the test on origin.protocol (that used to enforce http or https) altogether, thus allowing ANY origin to access the media. Neat. Do you know when this becomes available?
Comment 6 EWS 2021-01-07 00:31:26 PST
Committed r271229: <https://trac.webkit.org/changeset/271229>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 417006 [details].
Comment 7 Eric 2021-01-07 08:48:10 PST
Congrats Youenn, great fix 👏
Comment 8 youenn fablet 2021-01-27 06:27:37 PST
*** Bug 221031 has been marked as a duplicate of this bug. ***
Comment 9 Marc Bonsels 2021-01-27 22:34:06 PST
Sorry for the possibly stupid question, as I'm not really familiar with the involved procedures.

Has this fix been shipped with iOS 14.4? As mentioned in #221031, I'm still getting errors using getUserMedia and am wondering whether the issue is on my end or if the patch is not there yet.
Comment 10 Sami BDK 2021-01-29 07:55:16 PST
Thanks for the patch. This would be greatly apprecied by cordova/ionic/phonegap users.


Any idea on what version of iOS this will be shipped with ?
Comment 11 haimomesi 2021-02-01 12:15:54 PST
Will this be shipped with 14.5? Or in a beta version?
Comment 12 michael 2021-03-26 07:30:46 PDT
FYI, for anyone that comes across this page, this fix appears to be integrated into the iOS 14.5 beta.
Comment 13 Marvin 2021-03-31 07:30:34 PDT
I cant see it working on iOS 14.5(@michael said it's shipped with iOS 14.5).
Anybody else able to work with it on "ionic://localhost"?
Comment 14 youenn fablet 2021-04-01 06:42:55 PDT
(In reply to Marvin from comment #13)
> I cant see it working on iOS 14.5(@michael said it's shipped with iOS 14.5).
> Anybody else able to work with it on "ionic://localhost"?

I would expect it to work in the latest iOS 14.5 beta.
I would also expect the prompt to show the name of the app instead of localhost.
If that is not the case, please provide repro steps.
Comment 15 Pascal Ethier 2021-04-06 07:13:03 PDT
(In reply to Marvin from comment #13)
> I cant see it working on iOS 14.5(@michael said it's shipped with iOS 14.5).
> Anybody else able to work with it on "ionic://localhost"?

Hello,

New to this thread but this fix looks great!

I have just tried an Ionic/Capacitor app on iOS 14.5 beta 6.  I is based on Cordova and loads content from the App module. It makes a call to WebRTC mediaDevices.getUserMedia().

So far, mediaDevices.getUserMedia is still not exposed. 

Any clue which Beta this fix will be rolled-into?
Comment 16 Brian 2021-04-07 08:34:23 PDT
I've tested a capacitor app with iOS 14.5 beta 6 and it works fine calling navigator.mediaDevices.getUserMedia({video: true, audio: true})
Comment 17 Pascal Ethier 2021-04-07 09:43:55 PDT
(In reply to Brian from comment #16)
> I've tested a capacitor app with iOS 14.5 beta 6 and it works fine calling
> navigator.mediaDevices.getUserMedia({video: true, audio: true})

Hi Brian,

That's interesting.
I am doing navigator.mediaDevices.getUserMedia({video: true, audio: false})

I also have overridden the "hostname" to be something else than "localhost".

I wonder if one of those is why I am still seeing the error:
TypeError: undefined is not an object (evaluating 'navigator.mediaDevices.addEventListener')


Or Maybe it is in the way the build is configured?  
Did you have to add anything special in your PodFile or app configuration in XCode?

Any idea would be helpful.  Thank you!
Comment 18 youenn fablet 2021-04-07 09:46:43 PDT
> I also have overridden the "hostname" to be something else than "localhost".
> 
> I wonder if one of those is why I am still seeing the error:
> TypeError: undefined is not an object (evaluating
> 'navigator.mediaDevices.addEventListener')

localhost is required in the latest iOS version.
WebKit ToT has a fix that allows any custom scheme page to be considered SecureContext, and then have getUserMedia access.
Comment 19 Pascal Ethier 2021-04-07 12:46:16 PDT
(In reply to youenn fablet from comment #18)
> > I also have overridden the "hostname" to be something else than "localhost".
> > 
> > I wonder if one of those is why I am still seeing the error:
> > TypeError: undefined is not an object (evaluating
> > 'navigator.mediaDevices.addEventListener')
> 
> localhost is required in the latest iOS version.
> WebKit ToT has a fix that allows any custom scheme page to be considered
> SecureContext, and then have getUserMedia access.

Thank you youenn!  Yes, this worked.
Comment 20 skmbr 2021-05-04 12:48:14 PDT
(In reply to Pascal Ethier from comment #19)
> (In reply to youenn fablet from comment #18)
> > > I also have overridden the "hostname" to be something else than "localhost".
> > > 
> > > I wonder if one of those is why I am still seeing the error:
> > > TypeError: undefined is not an object (evaluating
> > > 'navigator.mediaDevices.addEventListener')
> > 
> > localhost is required in the latest iOS version.
> > WebKit ToT has a fix that allows any custom scheme page to be considered
> > SecureContext, and then have getUserMedia access.
> 
> Thank you youenn!  Yes, this worked.


I've been struggling to get this working in a Quasar/Cordova app.

Can any one point me in the direction of how I set up the SecureContext mentioned above?

And forgive my ignorance, but what is ToT ?
Comment 21 Louis Romero 2021-05-04 14:16:05 PDT
Created attachment 427698 [details]
Sample app to test getUserMedia.

I can't get it to work in a simple WKWebView (i.e. not using cordova, etc.).

Example:

```
<!doctype html>

<meta name="viewport" content="width=device-width, initial-scale=0.83, maximum-scale=3.0, minimum-scale=0.83">

<html>
<body>
<video></video>
</body>
</html>

<script>
const video = document.querySelector("video")
video.setAttribute('autoplay', '')
video.setAttribute('playsinline', '')
navigator.mediaDevices.getUserMedia( { video: true }).then((stream) => {
  video.srcObject = stream;
});
</script>
```

This works in Safari iOS 14.5, where the camera gets loaded, but in a WKWebView, the call to `getUserMedia` succeeds, and the video element appears, but all black, without the camera images.
Please see the xcodeproj attached that reproduces the issue.
Comment 22 Louis Romero 2021-05-04 14:18:34 PDT
(In reply to skmbr from comment #20)
> (In reply to Pascal Ethier from comment #19)
> > (In reply to youenn fablet from comment #18)
> > > > I also have overridden the "hostname" to be something else than "localhost".
> > > > 
> > > > I wonder if one of those is why I am still seeing the error:
> > > > TypeError: undefined is not an object (evaluating
> > > > 'navigator.mediaDevices.addEventListener')
> > > 
> > > localhost is required in the latest iOS version.
> > > WebKit ToT has a fix that allows any custom scheme page to be considered
> > > SecureContext, and then have getUserMedia access.
> > 
> > Thank you youenn!  Yes, this worked.
> 
> 
> I've been struggling to get this working in a Quasar/Cordova app.
> 
> Can any one point me in the direction of how I set up the SecureContext
> mentioned above?
> 
> And forgive my ignorance, but what is ToT ?

ToT is tip of trunk or tip of tree, i.e. at the latest revision in the repo. It will take some time before it gets in an iOS release.
Comment 23 Louis Romero 2021-05-04 15:03:04 PDT
(In reply to Louis Romero from comment #21)
> Created attachment 427698 [details]
> Sample app to test getUserMedia.
> 
> I can't get it to work in a simple WKWebView (i.e. not using cordova, etc.).
> 
> Example:
> 
> ```
> <!doctype html>
> 
> <meta name="viewport" content="width=device-width, initial-scale=0.83,
> maximum-scale=3.0, minimum-scale=0.83">
> 
> <html>
> <body>
> <video></video>
> </body>
> </html>
> 
> <script>
> const video = document.querySelector("video")
> video.setAttribute('autoplay', '')
> video.setAttribute('playsinline', '')
> navigator.mediaDevices.getUserMedia( { video: true }).then((stream) => {
>   video.srcObject = stream;
> });
> </script>
> ```
> 
> This works in Safari iOS 14.5, where the camera gets loaded, but in a
> WKWebView, the call to `getUserMedia` succeeds, and the video element
> appears, but all black, without the camera images.
> Please see the xcodeproj attached that reproduces the issue.

I just found the solution thanks to https://github.com/react-native-webview/react-native-webview/issues/1672.

I needed to set `allowsInlineMediaPlayback` on the WKWebViewConfiguration.

To summarize, the requirements for it to work are:
1. Use iOS 14.5+.
2. Use the following two attributes: <video autoplay playsinline>.
3. Request camera permission with AVCaptureDevice.authorizationStatus(for:) **before** creating the WKWebView (and thus have a NSCameraUsageDescription in your Info.plist)
4. Set allowsInlineMediaPlayback on the WKWebViewConfiguration.
Comment 24 skmbr 2021-05-05 15:49:09 PDT
I think I'm going slowly insane chasing this issue.

I actually had it working in my Quasar/Cordova app today.

The key was custom scheme and hostname support in Cordova ios 6:

https://cordova.apache.org/announcements/2020/06/01/cordova-ios-release-6.0.0.html

So I added the following to my config.xml...

<preference name="scheme" value="app" />
<preference name="hostname" value="localhost" />

... to put the app in a secure context, and the next time I built, it worked!

Then due to a separate issue with my app's local storage I completely removed the app from my phone before building again.

Now it doesn't work any more, and despite a console.log(window.isSecureContext) still returning true, navigator.mediaDevices is now undefined again.

I have no idea what's going on! 😭