apache / cordova-ios

Apache Cordova iOS
https://cordova.apache.org/
Apache License 2.0
2.15k stars 987 forks source link

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

Open jmarshall-com opened 2 years ago

jmarshall-com commented 2 years ago

Issue Type

Description

UPDATE 3: I managed to work around this using blob: URLs as generated from URL.createObjectURL(). See the first comment below.

UPDATE 2: I've upgraded Xcode to 13.4, and iOS to 15.5, and still see the problem. Can anyone else reproduce this problem using the app code listed below?

UPDATE: Just updated cordova-plugin-file to the brand new 7.0.0, but the problem still remains.

Images with "file:" URL don't load in app on physical iPhone SE (2022), but load fine on Android and in the iOS simulator. The test case below tries to set document.body.style.background, but setting (new Image).src doesn't work either; the image's onload() never runs. After setting document.body.style.background, getComputedStyle(document.body).backgroundImage shows the correct URL it was set to (as parsed from the background property), but the image never appears.

I understand that on a physical iPhone, the various paths are changed every install due to the new ID in the paths, but the test case below accommodates that, so I don't think that's the problem.

Content-Security-Policy is set to be permissive (see HTML file below), and includes file: and others in default-src, img-src, and style-src. (Is there perhaps some system-wide CSP that overrides that?)

This is for user-submitted images, so it has to be under cordova.file.dataDirectory, not under a read-only directory.

Information

I wrote this post on stackoverflow about this problem, but am including all the details here too.

The only plugin I'm using is cordova-plugin-file 7.0.0 .

Command or Code

The test case here was created as a brand new Cordova project.

The config.xml of the test case:

<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>HelloCordova</name>
    <description>Sample Apache Cordova App</description>
    <author email="dev@cordova.apache.org" href="https://cordova.apache.org">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <platform name="android">
        <preference name="AndroidXEnabled" value="true" />
        <preference name="AndroidInsecureFileModeEnabled" value="true" />
    </platform>
    <platform name="ios">
        <preference name="allowFileAccessFromFileURLs" value="true" />
        <preference name="allowUniversalAccessFromFileURLs" value="true" />
    </platform>

</widget>

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>

js/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) ;
    }
}

css/index.css is simply:

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

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

Finally, I have a background image in img/home_bg.jpg that is copied over to cordova.file.dataDirectory . After this copy, I can read the file correctly from cordova.file.dataDirectory.

Environment, Platform, Device

I'm developing on a Mac mini (2020) with the M1 chip, running MacOS 12.3 . I'm trying to run the app on a new iPhone SE (2022).

Version information

cordova 11.0.0 cordova-ios 6.2.0 cordova-plugin-file 7.0.0 Mac mini (2020) with M1 chip running MacOS 12.3 iPhone SE (2022) with iOS 15.4 Xcode 13.3

Checklist

jmarshall-com commented 2 years ago

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.

sven513 commented 1 year ago

I managed to fix a similar error with this snippet if (device.platform == 'iOS'){ var loc = window.WkWebView.convertFilePath(store + bannerDir); }

I guess this might work for your let full_url

ghenry22 commented 1 year ago

or just install the ionic webview plugin which provides a way to access files using the same url scheme as the app uses working around this problem.