Bug 154916 - WKWebView needs feature to allow file:// subresources
Summary: WKWebView needs feature to allow file:// subresources
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebKit API (show other bugs)
Version: WebKit Nightly Build
Hardware: iPhone / iPad iOS 9.2
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2016-03-02 07:12 PST by Ashley Gullen
Modified: 2020-05-18 03:41 PDT (History)
22 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ashley Gullen 2016-03-02 07:12:50 PST
WKWebView can load an index page from a file, as is typically done by Cordova applications. However applications using this approach immediately run in to some serious problems:

- XMLHttpRequest to local files fails
- images count as cross-origin, so cannot be uploaded as WebGL textures
- <audio> and <video> cannot load resources from file://
- scripts count as cross-origin, so all errors turn in to "Script error" on line 0, making debugging difficult

These problems make it virtually impossible to port a lot of Cordova apps to WKWebView, where they previously worked in UIWebView.

I suggest that either these restrictions are lifted and restored to how UIWebView treats them, or perhaps a new flag added to WKWebView to allow such subresource loads from local files to count as same-origin.
Comment 1 Ashley Gullen 2016-03-07 09:28:14 PST
Please note this all impacts the viability of porting Construct 2 content from UIWebView to WKWebView. For more information see: https://www.scirra.com/blog/ashley/25/hacking-something-useful-out-of-wkwebview

In particular bug 101671 makes this worse, since there is no workaround for video playback.
Comment 2 Brady Eidson 2016-09-23 08:36:55 PDT
(In reply to comment #0)
> 
> - XMLHttpRequest to local files fails
> - images count as cross-origin, so cannot be uploaded as WebGL textures
> - <audio> and <video> cannot load resources from file://
> - scripts count as cross-origin, so all errors turn in to "Script error" on
> line 0, making debugging difficult
> 

Do each of these four complaints still reproduce in iOS10?

Do you have a test app showing them that we could use to verify solutions to the problem(s)?
Comment 3 Darryl Pogue 2016-09-23 10:28:40 PDT
I have a test case app that demonstrates the issue with XHR to file URLs:
https://github.com/dpogue/WKFileTest

There's a variable to switch between UIWebView and WKWebView.
Comment 4 Pier Bover 2016-09-24 10:00:21 PDT
Hi all

I filed this bug https://bugs.webkit.org/show_bug.cgi?id=162512 which seems to be related to this.

The error I'm getting when trying to communicate between the main window and a local iframe is: Blocked a frame with origin "null" from accessing a frame with origin "null". Protocols, domains, and ports must match.

There is a simple cordova project there which reproduces a similar problem with window.postMessage() using file:// protocol.
Comment 5 Darryl Pogue 2016-09-24 10:03:08 PDT
(In reply to comment #3)
> I have a test case app that demonstrates the issue with XHR to file URLs:
> https://github.com/dpogue/WKFileTest
> 
> There's a variable to switch between UIWebView and WKWebView.

Just for the sake of mentioning it, this works correctly if you set the allowFileAccessFromFileURLs preference in WKPreferences, but there's no public API to do so.
Comment 6 Pier Bover 2016-09-24 10:21:05 PDT
(In reply to comment #5)
> 
> Just for the sake of mentioning it, this works correctly if you set the
> allowFileAccessFromFileURLs preference in WKPreferences, but there's no
> public API to do so.

Yep, it's there in the source:
https://github.com/WebKit/webkit/blob/master/Source/WebKit2/UIProcess/API/Cocoa/WKPreferences.mm#L388-L396

Are you saying that there is no way to enable that preference when creating a WKWebView?
Comment 7 guram.kajaia 2016-10-11 04:42:27 PDT
(In reply to comment #6)
> (In reply to comment #5)
> > 
> > Just for the sake of mentioning it, this works correctly if you set the
> > allowFileAccessFromFileURLs preference in WKPreferences, but there's no
> > public API to do so.
> 
> Yep, it's there in the source:
> https://github.com/WebKit/webkit/blob/master/Source/WebKit2/UIProcess/API/
> Cocoa/WKPreferences.mm#L388-L396
> 
> Are you saying that there is no way to enable that preference when creating
> a WKWebView?

There is a way to set this property:
wkwebview.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
Comment 8 Anders Carlsson 2016-10-11 07:30:08 PDT
(In reply to comment #7)
> (In reply to comment #6)
> > (In reply to comment #5)
> > > 
> > > Just for the sake of mentioning it, this works correctly if you set the
> > > allowFileAccessFromFileURLs preference in WKPreferences, but there's no
> > > public API to do so.
> > 
> > Yep, it's there in the source:
> > https://github.com/WebKit/webkit/blob/master/Source/WebKit2/UIProcess/API/
> > Cocoa/WKPreferences.mm#L388-L396
> > 
> > Are you saying that there is no way to enable that preference when creating
> > a WKWebView?
> 
> There is a way to set this property:
> wkwebview.configuration.preferences.setValue(true, forKey:
> "allowFileAccessFromFileURLs")

That's not a supported way to do things. It might stop working at any time.
Comment 9 ae 2017-02-06 17:23:42 PST
I am actually doing this (wkwebview.configuration.preferences.setValue(true, forKey:"allowFileAccessFromFileURLs") in one of my apps, and after already dancing around happily because it worked, it crashes on the iPhone 5 simulator (not on 5s, and not on a real iPhone SE). Odd.
Comment 10 Jeremy Colton 2017-05-02 10:05:41 PDT
(In reply to ae from comment #9)
> I am actually doing this (wkwebview.configuration.preferences.setValue(true,
> forKey:"allowFileAccessFromFileURLs") in one of my apps, and after already
> dancing around happily because it worked, it crashes on the iPhone 5
> simulator (not on 5s, and not on a real iPhone SE). Odd.

Hi, in your Cordova app, where are you adding this code?  The WKWebViewEngine.m shows an error when I add this line to it with the other wkWebView.configuration.preferences data and the API in XCode doesn't show a setValue method...

Thanks
Comment 11 ae 2017-05-03 01:00:00 PDT
(In reply to Jeremy Colton from comment #10)
> (In reply to ae from comment #9)
> > I am actually doing this (wkwebview.configuration.preferences.setValue(true,
> > forKey:"allowFileAccessFromFileURLs") in one of my apps, and after already
> > dancing around happily because it worked, it crashes on the iPhone 5
> > simulator (not on 5s, and not on a real iPhone SE). Odd.
> 
> Hi, in your Cordova app, where are you adding this code?  The
> WKWebViewEngine.m shows an error when I add this line to it with the other
> wkWebView.configuration.preferences data and the API in XCode doesn't show a
> setValue method...
> 
> Thanks

Hi, I used to set it via:

WKPreferences *preferences = [[WKPreferences alloc] init];
[preferences setValue:@"true" forKey:@"allowFileAccessFromFileURLs"];

However, this started to crash on the simulator one day and now I've resorted to serving files via an embedded GCDWebServer.
Comment 12 volker.graf 2017-06-07 07:00:21 PDT
Has anybody a working fix for this Problem ??

I am trying to load a few local Images (located unter www/img/...) using mapbox-gl-js like

map.loadImage('file://img/car16.png', (error, image) => {
	if (error) {
		alert("Car16 image not loaded");
                console.error(error);
		throw error;
	}
	map.addImage('car16', image);
});


I modified - 
(WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings
{
   ...
   ...
    @try {
        [configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
    }
    @catch (NSException *exception) {}
    
    @try {
        [configuration setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"];
    }
    @catch (NSException *exception) {}
    
    return configuration;
}

The Result I'm getting is

Failed to load resource: The requested URL was not found on this server. file://img/car16.png 

is there something I'm missing ??
Comment 13 ae 2017-06-07 08:47:37 PDT
You need the full path of the Documents directory, converted to an NSURL (file://var/private/blahblah/bla.jpg). On the road right now so can't look it up. In the end, you're probably better off and more future proof by embedding a local webserver. Ridiculous but works. I had good experience with GCDWebServer.
Comment 14 volker.graf 2017-06-07 09:28:12 PDT
Cab you point me to a WORKING example ? All the Stuff I tried concerning the local Webserver ended up in not being able to install the Webserver
Comment 15 volker.graf 2017-06-08 06:41:08 PDT
Ok I tried the Following:

  var cpath = cordova.file.applicationDirectory+'www/img/car16.png';

                map.loadImage(cpath, (error, image) => {
                    if (error) {
                        alert("Car16 image not loaded");
                        console.error(cpath);
                        console.error(error);
                        throw error;
                    }
                    map.addImage('car16', image);

                });

whereas cpath is
file:///var/containers/Bundle/Application/A08997CA-C292-48AA-AA70-DC7DF987BFC5/CordovaVectorICL.app/www/img/car16.png

but it fails =( .... Any Ideas ?
Comment 16 Ashley Gullen 2017-06-08 09:12:11 PDT
I wrote about my workarounds using Cordova in this blog post: https://www.scirra.com/blog/ashley/25/hacking-something-useful-out-of-wkwebview

It's a huge pain, you have to change all your code that loads resources and then cross-domain issues make it awkward. For example there's flat out no way to play video from a local file with WKWebView, unless you serve it with a local web server. For things like images and small snippets of audio, you can use Cordova to read the local files as ArrayBuffer or Blob.
Comment 17 Brady Eidson 2017-06-08 14:48:14 PDT
If you're serving 100% local file: content, consider using https://developer.apple.com/documentation/webkit/wkurlschemehandler instead.
Comment 18 volker.graf 2017-06-09 02:58:26 PDT
(In reply to Ashley Gullen from comment #16)
> I wrote about my workarounds using Cordova in this blog post:
> https://www.scirra.com/blog/ashley/25/hacking-something-useful-out-of-
> wkwebview
> 
> It's a huge pain, you have to change all your code that loads resources and
> then cross-domain issues make it awkward. For example there's flat out no
> way to play video from a local file with WKWebView, unless you serve it with
> a local web server. For things like images and small snippets of audio, you
> can use Cordova to read the local files as ArrayBuffer or Blob.

Well I tried you arraybuffer-Idea .. but it seems that MapBoxGL doesnt't like the ArrayBuffer =) 

I have to admit that I never managed to get the local Webserver running i have not found any documentation concerning the cordova-plugin-webserver Plugin .. 

Any hints ?
Comment 19 volker.graf 2017-06-09 03:37:00 PDT
(In reply to volker.graf from comment #18)
> (In reply to Ashley Gullen from comment #16)
> > I wrote about my workarounds using Cordova in this blog post:
> > https://www.scirra.com/blog/ashley/25/hacking-something-useful-out-of-
> > wkwebview
> > 
> > It's a huge pain, you have to change all your code that loads resources and
> > then cross-domain issues make it awkward. For example there's flat out no
> > way to play video from a local file with WKWebView, unless you serve it with
> > a local web server. For things like images and small snippets of audio, you
> > can use Cordova to read the local files as ArrayBuffer or Blob.
> 
> Well I tried you arraybuffer-Idea .. but it seems that MapBoxGL doesnt't
> like the ArrayBuffer =) 
> 
> I have to admit that I never managed to get the local Webserver running i
> have not found any documentation concerning the cordova-plugin-webserver
> Plugin .. 
> 
> Any hints ?

Thanx for the Article again .. I FINALLY managed to mage it work by doing the Following:


fetchLocalFileViaCordovaAsURL("./img/car24.png",
                        function(url) {
                            map.loadImage(url, (error, image) => {
                                if (error) {
                                    alert("Car24 image not loaded");
                                    throw error;
                                }
                                map.addImage('car24', image);
                                console.log("Added car16");
                            });
                        },
                        function() {

                        });

with the following Plugins installed:

cordova-plugin-compat 1.1.0 "Compat"
cordova-plugin-file 4.3.3 "File"
cordova-plugin-statusbar 2.2.3 "StatusBar"
cordova-plugin-whitelist 1.3.2 "Whitelist"
cordova-plugin-wkwebview-engine 1.1.3 "Cordova WKWebView Engine"
Comment 20 Pier Bover 2017-09-03 20:14:56 PDT
Hi everyone

According to information on this repo, WkWebView can access file with the file:/// protocol when the files are in the temp folder.

https://github.com/ShingoFukuyama/WKWebViewTips

The author also mentions that using **allowingReadAccessToURL** it is possible to access files on other folders.

If this is true it might be a helpful workaround until this is fixed (or not).
Comment 21 ae 2017-09-04 04:25:48 PDT
(In reply to Pier Bover from comment #20)
> Hi everyone
> 
> According to information on this repo, WkWebView can access file with the
> file:/// protocol when the files are in the temp folder.
> 
> https://github.com/ShingoFukuyama/WKWebViewTips
> 
> The author also mentions that using **allowingReadAccessToURL** it is
> possible to access files on other folders.

Hi Pier,

yes, the temp thing works, but it is impractical and slow to copy the whole Documents folder, potentially holding hundreds of MB, on every app launch. We've used that workaround for some time, but now resorted to GCDWebServer.

And concerning allowingReadAccessToURL: We're now using this to provide access to the APP BUNDLE. However, AFAICS, this only allows to specify ONE folder. Most apps will need access to both the Documents folder AND the App bundle.
Comment 22 Radar WebKit Bug Importer 2017-11-29 10:51:02 PST
<rdar://problem/35751740>
Comment 23 Brady Eidson 2017-11-29 11:42:50 PST
Now that iOS11 has shipped, I'll re-draw attention to my comment from earlier this summer:
https://bugs.webkit.org/show_bug.cgi?id=154916#c17
Comment 24 tbrian 2018-03-27 09:25:35 PDT
(In reply to Brady Eidson from comment #23)
> Now that iOS11 has shipped, I'll re-draw attention to my comment from
> earlier this summer:
> https://bugs.webkit.org/show_bug.cgi?id=154916#c17

Hi Brady,

I tried to implement a custom scheme but this doesn't help with loading files into Javascript, since XMLHttpRequest or fetch still seem to only allow http(s) schemes.

I get the following error:
XMLHttpRequest cannot load myscheme://test. Cross origin requests are only supported for HTTP

Is this the expected behavior or am I doing something wrong?
Comment 25 tbrian 2018-03-27 14:25:12 PDT
It actually works if the HTML page is also "served" over the custom scheme instead of file:, so I'm guessing that file: not being allowed to access a custom scheme through XMLHttpRequest is working as intended.
Comment 26 Guillaume Ripoche 2018-09-19 02:37:08 PDT
Hello

I thought I would fill a new bug report but our issue is very similar to this one.

TL;DR WKwebview does not access resources downloaded in data directory.

-

Version: 603.3.8

Reproduced on:
 - iphone 5 running iOS 10.3.3
 - iphone 6 running iOS 11 then iOS 12.0

-

With UIwebview deprecation announcement we started the work to migrate to WKwebview. Being cordova users we followed the instructions here:
https://cordova.apache.org/news/2018/08/01/future-cordova-ios-webview.html

Basically on cordova you need to add two plugins:
 - cordova-plugin-wkwebview-engine to use the WKwebview
 - cordova-plugin-wkwebview-file-xhr to fix XHR requests

-

1 - First, our app is built and shipped including assets (icons, images, etc) whose path looks like:
file:///var/containers/Bundle/Application/[APP_ID]/[APP_NAME]/www/files/project/home/iconA.svg

These assets are correctly displayed.

2 - Then on startup the app also checks with our backend if there is any update available. If some assets have been updated, then the app downloads them in the data directory. The path to the above asset becomes:
file:///var/mobile/Containers/Data/Application/[APP_ID]/Library/NoCloud//V1/files/project/home/iconA.svg

...but these assets are not displayed.

NB1: In both cases, the resource is declared in a background-image css property.
NB2: We've been using this process for years with success using the former UIwebview.

*

In the network panel of Safari's web inspector (which allows to inspect a cordova app in the same manner), the resource appears in red, with a red message indicating that an error occurred while trying to load it. There is no error code, nothing technical to help, no more specific than this fuzzy message...

I ran some tests and it appears that the resource is present on the file system at the given path. (Using cordova-plugin-file I can output the svg content).

As stated in above comments: using temp directory works (thanks for the tip!) but needs extra work and startup time.

Is webkit webview deliberately forbidding data directory access?
Comment 27 Ashley Gullen 2018-09-19 04:23:18 PDT
FWIW our workaround in the end is to give up completely on XHR and fetch, and route all requests through library functions that can delegate to cordova-plugin-file when in cordova mode.
Comment 28 ae 2018-09-19 04:30:34 PDT
(In reply to Guillaume Ripoche from comment #26)
> Hello
> 
> I thought I would fill a new bug report but our issue is very similar to
> this one.
> 
> TL;DR WKwebview does not access resources downloaded in data directory.

In our experience with SevenApp (our own in-house "Cordova equivalent"), the best workaround is to use custom URL schemes and then a custom handler in native code that just reads the file(s) from the local filesystem and sends them verbatim thru the connection. 1 hour of coding, eternal peace ;)
Comment 29 Ashley Gullen 2018-09-19 05:09:23 PDT
I filed an issue requesting for this approach to be built in to cordova-ios: https://github.com/apache/cordova-ios/issues/415
Comment 30 Guillaume Ripoche 2018-09-25 05:37:31 PDT
(In reply to ae from comment #28)
> In our experience with SevenApp (our own in-house "Cordova equivalent"), the
> best workaround is to use custom URL schemes and then a custom handler in
> native code that just reads the file(s) from the local filesystem and sends
> them verbatim thru the connection. 1 hour of coding, eternal peace ;)

Hello

We are working on it, and were wondering if you used WKWebViewConfiguration.setURLSchemeHandler method. If so, you don't support iOS 10 and below anymore?

Seems like we cannot upload 2 differents builds to Apple (contrary to android), so we are looking for a way to support both iOS12+WK webview  and iOS 11 and below+UI webview.

BTW we don't plan to ship a iOS11+WK webview build because currently there is a critical issue concerning HTML5 History API, still present on iOS 11.4 (despite the 'RESOLVED FIXED' status set 6 months ago), see https://bugs.webkit.org/show_bug.cgi?id=183028

Any hints would be appreciated! Thanks
Comment 31 ae 2018-09-25 09:59:28 PDT
(In reply to Guillaume Ripoche from comment #30)
> (In reply to ae from comment #28)
> We are working on it, and were wondering if you used
> WKWebViewConfiguration.setURLSchemeHandler method. If so, you don't support
> iOS 10 and below anymore?

Yes indeed, we only support iOS 11 and up now. The gist of it all is, in case it's helpful for you:

[configuration setURLSchemeHandler:self forURLScheme:@"sevenapp"];

- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task {
	NSURL* url = task.request.URL;
	NSString* path = /* get requested file path here */;
	NSData* data = [NSData dataWithContentsOfFile: path];
	NSURLResponse* response = [[NSURLResponse alloc] initWithURL:url MIMEType:MIMEType expectedContentLength:[data length] textEncodingName:nil];

	[task didReceiveResponse:response];
	[task didReceiveData:data];
	[task didFinish];
}
Comment 32 jiansong.shao 2018-12-20 23:34:16 PST
It seems customURLScheme doesn't support https, I can not load js file using customURLScheme on an https server, it always show "mixed content blocked" in the web console, is there any workaround for this?
Comment 33 A Perdereau 2019-10-05 04:28:29 PDT
(In reply to jiansong.shao from comment #32)
> It seems customURLScheme doesn't support https, I can not load js file using
> customURLScheme on an https server, it always show "mixed content blocked"
> in the web console, is there any workaround for this?

While this work fine for small size files (images, scripts, ...) This technique fails for videas because reading teh whole file in one operation into an NSData is inneficient. Any idea to handle this case?
Comment 34 Alex Christensen 2019-10-07 13:36:04 PDT
WKURLSchemeTask's didReceiveData: can be called multiple times, each with a piece of the response data.  Videos are usually requested using byte range requests, so you only need to include those bytes anyways.
Comment 35 jcesarmobile 2019-10-23 10:30:53 PDT
Alex Christensen, yeah, the problem is the requests don't include range headers, so it's not possible to send parts based on that.
I've reported that in a separate issue. https://bugs.webkit.org/show_bug.cgi?id=203302