Closed Bug 948465 Opened 8 years ago Closed 7 years ago

[GeckoView] Allow loading from assets/ via URL, e.g. file://android_asset/

Categories

(Firefox for Android Graveyard :: General, defect)

All
Android
defect
Not set
normal

Tracking

(Not tracked)

RESOLVED FIXED
Firefox 34

People

(Reporter: BenB, Assigned: nalexander)

References

Details

(Keywords: dev-doc-needed)

Attachments

(3 files)

Reproduction:
1. Embed GeckoView in your Android app
2. Place assets/myapp/index.html in your app's source, and build
3. Load URI "file://android_asset/myapp/index.html" into the GeckoView browser.

Actual result:
File not found

Expected result:
The file from assets/ is being loaded.
Relative URLs from there on also work.

Why:
WebView supports this, and it's highly useful for very common usecases where you want to ship part of your Android app as webapp, and bundle your runtime (GeckoView) and your app (HTML+JS, in assets).

Related bugs:
Bug 948325 is more generic and powerful, but requires custom code from the embedder. I think we need both that bug and this one.
Bug 880107 - GeckoView tracker
Until we have this, this can be achieved in the embedding app with:

URI apkURI = (new File(MyActivity.this.getPackageResourcePath())).toURI();
String assetsURL = "jar:" + apkURI + "!/assets/";
String myURL = assetsURL + "test.html";

But the fact that it's so easy also means that it should be easy to implement within GeckoView. file://android_asset/ is well-documented for WebView, and that would be the first thing developers would try.
(In reply to Ben Bucksch (:BenB) from comment #1)
> Until we have this, this can be achieved in the embedding app with:
> 
> URI apkURI = (new File(MyActivity.this.getPackageResourcePath())).toURI();
> String assetsURL = "jar:" + apkURI + "!/assets/";
> String myURL = assetsURL + "test.html";
> 
> But the fact that it's so easy also means that it should be easy to
> implement within GeckoView. file://android_asset/ is well-documented for
> WebView, and that would be the first thing developers would try.

So we could add some code to our loadUrl API to replace "file://android_asset/" with "jar:" + apkURI + "!/assets/"  ?
I think this would fail this part:
> Expected result: ...
> Relative URLs from there on also work.

So, I would have to be deeper, in the URL resolver. I think this bug and bug 948325 could be implemented in the same code place.
In other words: If myapp/test.html loads <script src="test.js" />, it needs to resolve to "file://android_asset/myapp/test.js", which then loads file <apk>/assets/myapp/test.js.
(In reply to Ben Bucksch (:BenB) from comment #4)
> In other words: If myapp/test.html loads <script src="test.js" />, it needs
> to resolve to "file://android_asset/myapp/test.js", which then loads file
> <apk>/assets/myapp/test.js.

Right

Android impl notes:
http://androidxref.com/4.4_r1/xref/frameworks/base/core/java/android/webkit/URLUtil.java#35
Actually..., I'm wrong. If you just replace the file:// URL with "jar:" in loadURI, then of course relative URL would resolve to "jar:", too. The app would see a difference when checking window.location, but it should work. For me, that's good enough. I'd personally place it deeper. Up to you to decide.
So, I dug into this over the weekend.  It's not that hard to arrange in Gecko itself for resource://android_asset/foo to load assets/foo.  It is a little tricky, because the correct resource substitution needs the current APK path, which changes over time (updates, for example, bump the APK path).

I followed BenB, with:

+      printWriter.println("resource android_asset jar:file://" + apkPath + "!/assets/");

The trick is to load this *really* early in the registration process.  Ideally, we want it as part of the chrome.manifest in omni.ja, but I wasn't able to figure out how to get that.  (The reason I want it early is to override GRE module loads, but I haven't figure out how to do this yet.)

I did manage to dynamically write an appOmni.ja and arrange for it to be installed via -appomni.  This works, but it's using a big hammer for a small feature.  (appomni is a *really* valuable extension point in future; we shouldn't camp it without good reason.)
> The trick is to load this *really* early in the registration process. 
> Ideally, we want it as part of the chrome.manifest in omni.ja, but I wasn't
> able to figure out how to get that.  (The reason I want it early is to
> override GRE module loads, but I haven't figure out how to do this yet.)

I haven't tried this, but it might be possible to include a static link to a chrome.manifest in omni.ja, and then to write that chrome.manifest dynamically with the appropriate resource override.

Or we might be able to call nsIResProtocolHandler's setSubstitution around the same time as omni.ja's chrome.manifest is registered.
Nick, personally, I think we should go the the 3-liner Java code I posted above.

As far as "early": If you want to do advanced stuff like replacing certain Gecko DLLs/so with your own, then I think it's fair to say you have to do extra work. Once you figured out a good way, you can document this on the wiki.

I think the common usecase here is to load HTML assets, or data that the app needs. Most GeckoView users will need this.
Maybe even JSMs or extra JS components or similar.
Blocks: 888391
No longer blocks: 888391
Consumers can access Android assets (and resources) by using
resource://android/assets/FILENAME.

The resource substitution is registered at app-startup time so that the
substitution is in place before any add-on has started up: as documented
at [1], "[an] add-on will not receive xpcom-startup or app-startup
notifications".

[1] https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Guide/Receiving_startup_notifications
Attachment #8460637 - Flags: review?(mark.finkle)
I have not yet verified that this does the right thing for GeckoView
embedders, but I have a hard time imagining it doesn't, since we hook
into the XPCOM start-up process very early.
Attachment #8460640 - Flags: review?(mark.finkle)
OS: Linux → Android
Hardware: x86_64 → All
Assignee: nobody → nalexander
Keywords: dev-doc-needed
Attached patch Example commit.Splinter Review
This packs example.{html,js} into the APK assets/.  The following loads
an example.html that loads a relative example.js; if everything works,
you should get a red body background:

adb shell am start -a android.intent.action.VIEW -n org.mozilla.fennec_nalexander/.App -d resource://android/assets/example.html
Comment on attachment 8460637 [details] [diff] [review]
Part 1: Register resource://android/ point to Fennec APK root. r=mfinkle

>diff --git a/mobile/android/components/BrowserCLH.js b/mobile/android/components/BrowserCLH.js

>+  setResourceSubstitutions: function () {
>+    let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);

nit: use " not '

>+    let protocolHandler = Services.io
>+       .getProtocolHandler("resource")
>+       .QueryInterface(Ci.nsIResProtocolHandler);

nit: it's ok for this to be one line
Attachment #8460637 - Flags: review?(mark.finkle) → review+
Attachment #8460640 - Flags: review?(mark.finkle) → review+
https://hg.mozilla.org/mozilla-central/rev/f9e3fc8632a3
https://hg.mozilla.org/mozilla-central/rev/99151ee98fd4
Status: ASSIGNED → RESOLVED
Closed: 7 years ago
Resolution: --- → FIXED
Target Milestone: --- → Firefox 34
Blocks: 1522137
Product: Firefox for Android → Firefox for Android Graveyard
You need to log in before you can comment on or make changes to this bug.