James Marshall
James Marshall

Reputation: 339

In Cordova app, physical iPhone SE can't display images from "file://" URLs; OK in iOS simulator and Android

I'm writing an app with Cordova 11.0.0, cordova-ios 6.2.0, and Xcode 13.3 that sets the background image to the user's choice. Everything works fine on Android and on the iOS simulator. However, when running on a physical iPhone SE (2022) with iOS 15.4, the background image is not displayed, though it seems to set style.backgroundImage correctly on the element, and I'm able to read the image file correctly using a standard FileReader object. It appears that any local images using a file:// URL are not displayed. I'm loading them from under cordova.file.dataDirectory, so I can't just convert to a relative http:// URL. I believe my Content-Security-Policy is permissive enough (?) (see index.html below).

I made a small demo of this; the only plugin it uses is cordova-plugin-file 6.0.2. My config.xml contains:

    <content src="index.html" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <platform name="ios">
        <preference name="allowFileAccessFromFileURLs" value="true" />
        <preference name="allowUniversalAccessFromFileURLs" value="true" />
    </platform>

My index.html is:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Security-Policy"
            content="default-src * file: filesystem: ; img-src * file: filesystem: ; style-src * file: filesystem: 'unsafe-eval' ;">
        <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
        <link rel="stylesheet" href="css/index.css">
        <title>Hello World</title>
        <script src="cordova.js"></script>
        <script src="js/index.js"></script>
    </head>
    <body>
        <h1>Background image should show up</h1>
    </body>
</html>

My index.js is:

// Wait for the deviceready event before using any of Cordova's device APIs.
document.addEventListener('deviceready', onDeviceReady, false);

async function onDeviceReady() {
    try{
        // this block just copies file from under cordova.file.applicationDirectory
        //   to under cordova.file.dataDirectory, for this demo
        // FileEntry.copyTo() throws error on ios if file already exists, so use try/catch
        try {
            let entry= await new Promise((resolve, reject) => 
                resolveLocalFileSystemURL(cordova.file.applicationDirectory + 'www/img/home_bg.jpg',
                    resolve, reject) ) ;
            let dest_dir= await new Promise((resolve, reject) => 
                resolveLocalFileSystemURL(cordova.file.dataDirectory, resolve, reject) ) ;
            await new Promise((resolve, reject) =>
                entry.copyTo(dest_dir, undefined, resolve, reject) ) ;
        } catch(e) {}

        // set full background property
        let full_url= cordova.file.dataDirectory + 'home_bg.jpg' ;
        document.body.style.background= 'url(' + full_url + ') center/cover' ;

        // this shows the correct 'url("file:///Users/.../Library/NoCloud/home_bg.jpg")'
        alert('computed backgroundImage=[' + getComputedStyle(document.body).backgroundImage + ']') ;

    } catch(e) {
        alert('in odr(): '+e) ;
    }
}

My index.css is simply:

body {
    height:100vh;
    width:100vw;
}

h1 {
    font-size:24pt;
    margin:3rem;
    text-align:center;
}

And finally, I have a background image in img/home_bg.jpg that is copied to be under cordova.file.dataDirectory.

I've read many questions here about file:// URLs, but none of the solutions have worked and are appropriate (e.g. using data: URLs for the images is impractical and unscalable). I know that the app paths change for every install on an iPhone, but the code above should still work.

How can I get images to display on a physical iPhone using file:// URLs? Am I missing some setting in Xcode or elsewhere?

Thanks for any help!

Upvotes: 1

Views: 554

Answers (1)

James Marshall
James Marshall

Reputation: 339

Thanks to this answer by @jcbdrn, I managed to work around this by using blob: URLs for all displayed images:

let blob= new Blob([await read_file_binary(file_entry)]) ;
img.src= URL.createObjectURL(blob) ;

... where read_file_binary() reads a file as an arrayBuffer. Note that the Content Security Policy must include blob: in the img-src directive.

This isn't as efficient as just using file: URLs, but it's much better than using data: URLs. It does seem like a bug in either cordova-ios (?) or in WKWebView (?), but I'm set for now.

Upvotes: 1

Related Questions