cordova-ios icon indicating copy to clipboard operation
cordova-ios copied to clipboard

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

Open jmarshall-com opened this issue 2 years ago • 3 comments

Issue Type

  • [x] Bug Report
  • [ ] Feature Request
  • [x] Support Question

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="[email protected]" 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

  • [x] I searched for already existing GitHub issues about this
  • [x] I updated all Cordova tooling to their most recent version
  • [x] I included all the necessary information above

jmarshall-com avatar Apr 03 '22 21:04 jmarshall-com

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.

jmarshall-com avatar May 30 '22 03:05 jmarshall-com

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

sven513 avatar Feb 17 '23 15:02 sven513

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.

ghenry22 avatar Apr 02 '23 06:04 ghenry22