flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
162.19k stars 26.64k forks source link

Fetch API cannot load file:///xxxxx/assets/FontManifest.json. URL scheme “file” is not supported (on android device) #128760

Open JiaoLiu opened 11 months ago

JiaoLiu commented 11 months ago

Is there an existing issue for this?

Steps to reproduce

  1. run flutter build web.
  2. copy build/web dir to android phone.
  3. use a webview load web/index.html

Expected results

show the main.dart screen.

Actual results

inspect the device, found error "Fetch API cannot load file:///xxx/assets/FontManifest.json. URL scheme “file” is not supported" & blank screen.

build web under flutter 3.7.0 everything is ok. flutter 3.10.x error & not work.

Code sample

Code sample ```dart [Paste your code here] ```

Screenshots or Video

Screenshots / Video demonstration [Upload media here]

Logs

Logs ```console [Paste your logs here] ```

Flutter Doctor output

Doctor output ```console [Paste your output here] ```
huycozy commented 11 months ago

Hi @JiaoLiu Are you using webview_flutter to load that web page? If so, which version are you using?

Also, that would be helpful if you can provide a minimal sample code for this. And please provide output of flutter doctor -v as well. Thanks!

JiaoLiu commented 11 months ago

Hi @JiaoLiu Are you using webview_flutter to load that web page? If so, which version are you using?

Also, that would be helpful if you can provide a minimal sample code for this. And please provide output of flutter doctor -v as well. Thanks!

we are not using webview_flutter. we use native webview to load the html source with 'file://' scheme. The web source is stored in the sandbox(ios)&SD card(android). It works on iphone, but android show the error.

[✓] Flutter (Channel stable, 3.10.2, on macOS 12.6.3 21G419 darwin-x64, locale zh-Hans-CN) [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). If the Android SDK has been installed to a custom location, please use flutter config --android-sdk to update to that location.

[✓] Xcode - develop for iOS and macOS (Xcode 14.2) [✓] Chrome - develop for the web [!] Android Studio (not installed) [✓] VS Code (version 1.78.2) [✓] Connected device (3 available)

huycozy commented 11 months ago

Can you please share a minimal sample code for android native part? (The flutter web build output can be skipped as I can do it)

It would be appreciated if you could provide a GitHub repository in this case. Thank you!

JiaoLiu commented 11 months ago

Can you please share a minimal sample code for android native part? (The flutter web build output can be skipped as I can do it)

It would be appreciated if you could provide a GitHub repository in this case. Thank you!

just use a native webview load this web source. you can create a activity to wrap the webview.

this.webview?.loadUrl(Uri.fromFile(File("file:///sdcard/index.html"))

BTW, we found the reason maybe flutter(3.10.0) using 'fetch' instead of 'XMLHttpRequest' (flutter 3.7.0). 'fetch' method only support https/http on android?

huycozy commented 11 months ago

Unfortunately, I'm hitting ERR_ACCESS_DENIED error when doing this. Below is my settings to load web on Android:

        val flutterWebFile = File("${extStoragePath}/web/index.html")
        Log.d("TAG", "flutterWebFile path: ${flutterWebFile.path}") // /storage/emulated/0/web/index.html
        Log.d("TAG", "pathFlutterWeb is existed: ${flutterWebFile.exists()}")   // true
        val pathFlutterWeb = "file:///$flutterWebFile"

        binding.webview.settings.allowFileAccess = true
        binding.webview.settings.allowContentAccess = true
        binding.webview.loadUrl(pathFlutterWeb)

Is there missing something to replicate this issue? (checking this on Pixel 3a, Android 12)

JiaoLiu commented 11 months ago

Unfortunately, I'm hitting ERR_ACCESS_DENIED error when doing this. Below is my settings to load web on Android:

        val flutterWebFile = File("${extStoragePath}/web/index.html")
        Log.d("TAG", "flutterWebFile path: ${flutterWebFile.path}") // /storage/emulated/0/web/index.html
        Log.d("TAG", "pathFlutterWeb is existed: ${flutterWebFile.exists()}")   // true
        val pathFlutterWeb = "file:///$flutterWebFile"

        binding.webview.settings.allowFileAccess = true
        binding.webview.settings.allowContentAccess = true
        binding.webview.loadUrl(pathFlutterWeb)

Is there missing something to replicate this issue? (checking this on Pixel 3a, Android 12)

1、put the builded web source to android device, for example, go to source dir(/xxx/flutter/build/web/), adb push all files to where you will load later:

cd /xxx/flutter/build/web/
adb push  *   /storage/emulated/0/Download/

2、add<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />to AndroidManifest.xml

3、 add method:

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                when {
                    ContextCompat.checkSelfPermission(
                        MainActivity@this,
                        android.Manifest.permission.READ_EXTERNAL_STORAGE
                    ) == PackageManager.PERMISSION_GRANTED -> {
                        val outdir =    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                        if (!outdir.exists()) {
                            outdir.mkdirs()
                        }
                        // eg: /storage/emulated/0/Download/
                        this.webview?.loadUrl("file://${File(outdir.absolutePath,"index.html").absoluteFile}")

                    }
                    shouldShowRequestPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE) -> {

                }
                    else -> {
                        requestPermissions(
                            arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
                            0x100)
                    }
                }
            }
huycozy commented 11 months ago

Thanks but that error still persists with your updated code. I'm checking this on Pixel 3a, Android 12.

Screenshot ![Screenshot 2023-06-16 at 11 36 06](https://github.com/flutter/flutter/assets/104349824/66a0f115-a822-445b-93ff-e78b5bb4ad47)

I was thinking to storage permission on Android version change, so I checked this on Android 11 device and add requestLegacyExternalStorage = true to bypass scope storage if any. But the result is still the same.

Also, the web path to index.html is existing (path.exists() is true). I added these settings too, but not working as well:

binding.webview.settings.allowContentAccess = true
binding.webview.settings.allowFileAccess = true

Maybe this is a new issue with native webview recently that I'm not aware?

Can you check this sample code if it's missing anything? load_flutter_web_128760

JiaoLiu commented 11 months ago

Thanks but that error still persists with your updated code. I'm checking this on Pixel 3a, Android 12.

Screenshot I was thinking to storage permission on Android version change, so I checked this on Android 11 device and add requestLegacyExternalStorage = true to bypass scope storage if any. But the result is still the same.

Also, the web path to index.html is existing (path.exists() is true). I added these settings too, but not working as well:

binding.webview.settings.allowContentAccess = true
binding.webview.settings.allowFileAccess = true

Maybe this is a new issue with native webview recently that I'm not aware?

Can you check this sample code if it's missing anything? load_flutter_web_128760

https://github.com/huycozy/Android-Native-Sample/blob/load_flutter_web_128760/app/build.gradle

line 13.

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
android {
    namespace 'com.example.androidnativesample'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.androidnativesample"
        minSdk 24
        targetSdk 33

build.gradle change targetSdk 33 to targetSdk 28.

huycozy commented 11 months ago

It's only displaying a white screen instead. In order to make this issue actionable and faster, please provide a sample code and affected Android devices (OS, model) so I can just clone and check it. Thanks!

JiaoLiu commented 11 months ago

It's only displaying a white screen instead. In order to make this issue actionable and faster, please provide a sample code and affected Android devices (OS, model) so I can just clone and check it. Thanks!

can you connect android device to chrome & inspect the web?

maybe you should modify your /web/index.html file.

JiaoLiu commented 11 months ago

It's only displaying a white screen instead. In order to make this issue actionable and faster, please provide a sample code and affected Android devices (OS, model) so I can just clone and check it. Thanks!

can you connect android device to chrome & inspect the web?

maybe you should modify your /web/index.html file.


<head>
  <!-- <base href="/"> -->   
</head>
huycozy commented 11 months ago

When I inspect the webview, I got another error:

Access to internal resource at 'file:///storage/emulated/0/Download/manifest.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https, chrome-untrusted.
Inspector ![Screenshot 2023-06-20 at 11 11 52](https://github.com/flutter/flutter/assets/104349824/c1beef0c-3888-44fa-9d17-f5a5618708ff)

Can you share a complete and workable sample code along with device info that you are seeing this issue?

JiaoLiu commented 11 months ago

you have repoduced this error successfully('Cross origin requests are only supported for protocol schemes: http, data, chrome, https, chrome-untrusted.' similar reason, all source is loaded with 'file' scheme, there should be no Cross origin requests). this error will block the first view. but if you build with flutter 3.7.x, everything is fine. all android device will show this error, but iphone works. our company has security policy, we can't publish source code on outside web.

JiaoLiu commented 10 months ago

same flutter code build web.

flutter 3.7.x build web. 4201687319377_ pic

flutter 3.10.2 build web. 4211687319377_ pic

huycozy commented 10 months ago

Unfortunately, I get a similar result as my previous comment when using Flutter 3.7.0 (it's not worked on 3.7.0 as your result). Also, I see a similar error on desktop browser which also uses file scheme as mobile.

Access to XMLHttpRequest at 'file:///Users/huynq/Desktop/file_scheme_128760/build/web/assets/FontManifest.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.

There is a difference between XMLHttpRequest and internal resource (my result)/Fetch API (your result) in the error message.

Based on what you share at https://github.com/flutter/flutter/issues/128760#issuecomment-1592301391, it may be related to https://github.com/flutter/engine/pull/39657. Keeping this issue open and labeling it for further investigation.

Sample code: load_flutter_web_128760

yjbanov commented 10 months ago

Is there anything Flutter Web does that prevents this? It seems to be a restriction on Android webivews that disallows loading local files as HTTP requests. Does fetch work if you just load a plain HTML+JS page without Flutter Web?

JiaoLiu commented 10 months ago

Unfortunately, I get a similar result as my previous comment when using Flutter 3.7.0 (it's not worked on 3.7.0 as your result). Also, I see a similar error on desktop browser which also uses file scheme as mobile.

Access to XMLHttpRequest at 'file:///Users/huynq/Desktop/file_scheme_128760/build/web/assets/FontManifest.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.

There is a difference between XMLHttpRequest and internal resource (my result)/Fetch API (your result) in the error message.

Based on what you share at #128760 (comment), it may be related to flutter/engine#39657. Keeping this issue open and labeling it for further investigation.

Sample code: load_flutter_web_128760

Our Android developer has reviewed your Sample code: load_flutter_web_128760, and has found that you need to change some WebView settings.

https://github.com/huycozy/Android-Native-Sample/blob/load_flutter_web_128760/app/src/main/java/com/example/androidnativesample/FirstFragment.kt

line 55

        binding.btn.setOnClickListener {
            when {
                ContextCompat.checkSelfPermission(
                    requireActivity(),
                    android.Manifest.permission.READ_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED -> {
                    val outdir =    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                    if (!outdir.exists()) {
                        outdir.mkdirs()
                    }
                    // eg: /storage/emulated/0/Download/
                    val path = File(outdir.absolutePath,"index.html").absoluteFile
                    Log.d("TAG", "is existed: ${path.exists()}")
                    val webSetting: WebSettings =  binding.webview.getSettings()
                    webSetting.allowContentAccess = true
                    webSetting.javaScriptEnabled = true
                    webSetting.javaScriptCanOpenWindowsAutomatically = true
                    webSetting.allowFileAccess = true
                    webSetting.setSupportZoom(false)
                    webSetting.builtInZoomControls = true
                    webSetting.useWideViewPort = false //##千万不能用
                    webSetting.loadWithOverviewMode = true
                    webSetting.databaseEnabled = true
                    webSetting.domStorageEnabled = true
                    webSetting.setGeolocationEnabled(true)
                    webSetting.pluginState = WebSettings.PluginState.ON
                    webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH)
                    webSetting.cacheMode = WebSettings.LOAD_DEFAULT
                    webSetting.allowFileAccessFromFileURLs = true
                    webSetting.allowUniversalAccessFromFileURLs = true
                    webSetting.savePassword = false
                    webSetting.mediaPlaybackRequiresUserGesture = false
                    WebView.setWebContentsDebuggingEnabled(true)
                    binding.webview.settings.allowContentAccess = true
                    binding.webview.settings.allowFileAccess = true
                    this.binding.webview.loadUrl("file://$path")

                }
                shouldShowRequestPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE) -> {}
                else -> {
                    requestPermissions(
                        arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
                        0x100)
                }
            }
        }

BTW, you should comment out this line in the index.html file of the website source.

<head>
  <!-- <base href="/"> -->   
</head>
JiaoLiu commented 10 months ago

Is there anything Flutter Web does that prevents this? It seems to be a restriction on Android webivews that disallows loading local files as HTTP requests. Does fetch work if you just load a plain HTML+JS page without Flutter Web?

Fetching to load the file:// schema does not work on the Android WebView, but it works on the iOS WebView.

https://fetch.spec.whatwg.org/

image
yjbanov commented 10 months ago

https://github.com/huycozy/Android-Native-Sample/blob/load_flutter_web_128760/app/src/main/java/com/example/androidnativesample/FirstFragment.kt

This is not part of the Flutter framework code. I think it's just an example that you can copy and paste into your project and then fix it to make it work. There's nothing the Flutter team can do to fix it.

Fetching to load the file:// schema does not work on the Android WebView, but it works on the iOS WebView.

I'm still not sure what we'd need to change in Flutter to make it work. Is there a different method for loading the manifest file that Android WebView would accept?

JiaoLiu commented 10 months ago

https://github.com/huycozy/Android-Native-Sample/blob/load_flutter_web_128760/app/src/main/java/com/example/androidnativesample/FirstFragment.kt

This is not part of the Flutter framework code. I think it's just an example that you can copy and paste into your project and then fix it to make it work. There's nothing the Flutter team can do to fix it.

The android part is just a demo for loading web source built from Flutter.

Fetching to load the file:// schema does not work on the Android WebView, but it works on the iOS WebView.

I'm still not sure what we'd need to change in Flutter to make it work. Is there a different method for loading the manifest file that Android WebView would accept?

https://github.com/flutter/flutter/issues/128760#issuecomment-1600034131

The same Android code that loads web source built with Flutter 3.7.x will work, but it will not work with Flutter 3.10.x.

I believe the 'main.dart.js' generated by the engine is different. Maybe the reason is that they changed XMLHttpRequest to Fetch.

yjbanov commented 10 months ago

One option is to monkey-patch fetch to operate on top of XHR such that the webview would accept it. I'm going to keep this issue in case we can find a volunteer to implement this.

ditman commented 10 months ago

Not sure how current this docs are, but here's some documentation about CORS, and the android web_view:

https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/cors-and-webview-api.md#file_android_asset_res_urls

(It seems the file: needs to be an asset or a resource of the app for this to work?)

JiaoLiu commented 10 months ago

One option is to monkey-patch fetch to operate on top of XHR such that the webview would accept it. I'm going to keep this issue in case we can find a volunteer to implement this. @yjbanov Why can't we roll back to using the old way to load the resources(FontManifest.json etc) that were used in Flutter 3.7.0?

JiaoLiu commented 10 months ago

https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/cors-and-webview-api.md#file_android_asset_res_urls

(It seems the file: needs to be an asset or a resource of the app for this to work?)

@ditman You are correct. We have added webSetting.allowFileAccessFromFileURLs = true, so there is no CORS problem.

HePeng11 commented 8 months ago

guys , any update on this issue ? I also encountered that ~_~

_"Access to script at 'file:///android_asset/flutter_assets/static/services/xxxxxx.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.", source: file:///android_asset/flutterassets/static/index.html (0)

Really appreciate if you comment , it's important for me , thanks

@JiaoLiu @yjbanov @ditman @huycozy

JiaoLiu commented 8 months ago

guys , any update on this issue ? I also encountered that ~_~

_"Access to script at 'file:///android_asset/flutter_assets/static/services/xxxxxx.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.", source: file:///android_asset/flutterassets/static/index.html (0)

Really appreciate if you comment , it's important for me , thanks

@JiaoLiu @yjbanov @ditman @huycozy

use flutter branch 3.7.0 to build web

JiaoLiu commented 2 weeks ago

@yjbanov Is there an update? Has the latest version of Flutter fixed this issue?