Bug 1522137 - Make resource://android return a nsIJARURI. r=snorp,mayhemer,bzbarsky
authorAgi Sferro <agi@mozilla.com>
Mon, 25 Feb 2019 15:38:33 +0000
changeset 460919 54102692d3852d6e42a3cf24828750682eaf91d9
parent 460918 c79d6bd85dd8b07ac3e6f1cf7c02d5d2b708302f
child 460920 51467afba45054b651e009f531b67247a4943471
push id35613
push usernerli@mozilla.com
push dateTue, 26 Feb 2019 03:52:35 +0000
treeherdermozilla-central@faec87a80ed1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, mayhemer, bzbarsky
bugs1522137
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522137 - Make resource://android return a nsIJARURI. r=snorp,mayhemer,bzbarsky resource://android URIs always point to a "jar:" URI so it doesn't make sense that the returned URL object implements nsIFileURL. This also makes it so extensions can be loaded from a resource://android URI. Audited all places where we use `nsIJARURI` and check for places where we assume it looks like `jar:`: the only (!) place where we do that is here: https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/dom/xhr/XMLHttpRequestMainThread.cpp#1852 Where we have special handling for `jar:` URIs. It looks like we would have less special handling for such a request to a `resource://android` and it could be fixed by just checking for the interface instead, but that's what's already happening today so it should work. That code is never reached for `resource://android` URIs as `mIsMappedArrayBuffer` is false for those URIs (see #2822). And the code is consistent in checking for the scheme instead of the interface (the other check is here: https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/dom/xhr/XMLHttpRequestMainThread.cpp#2822) Audited all places where we do `EqualsLiteral("jar")`: https://searchfox.org/mozilla-central/search?q=.EqualsLiteral%28%22jar%22%29&path= `SubstituteRemoteChannel`: looks interesting, but uses `nsISubstitutingProtocolHandler::ResolveURI` to first get the real URI (which is a `jar:`) so it works for our case. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/protocol/res/ExtensionProtocolHandler.cpp#414 `SubstitutingProtocolHandler.cpp` This case is explicitly fixed by this change, making it account for both `"jar"` and `"resource"`. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/protocol/res/SubstitutingProtocolHandler.cpp#299 `ReadSourceFromFileName`: this also looks interesting, but uses the channel to get the URI which returns the real `"jar"` URI and not the mapped `"resource"`. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/js/xpconnect/src/XPCJSRuntime.cpp#2837 `nsStringBundle.cpp` Accounts for both `"jar"` and `"resource"`, so it should work the same. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/intl/strres/nsStringBundle.cpp#437 Audited all places where we use `nsINestedURI` to make sure they would work for a `nsIJARURI` which does not implement `nsINestedURI`. `BrowserContentHandler.jsm` Uses `nsINestedURI` to figure out if a URI is local or not and then it checks whether it's a `"file"`, `"resource"` or `"chrome"` URI, for a `nsIJARURI & nsINestedURI` it would return a `"file"` which passes the test, for a `nsIJARURI` alone it would return `"resource"` which is also considered local by this code, so the result wouldn't change. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/browser/components/BrowserContentHandler.jsm#395-399 `nsScriptSecurityManager.cpp` `GetOriginFromURI`: This is the reason why `SubstitutingJARURI` doesn't implement `nsINestedURI`, the origin is computed starting from the innermost URI, which in our case would be a file. The behavior in our case is that the origin from a `resource://android` URI behaves like other `resource://` URIs, which is what we expect. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/caps/nsScriptSecurityManager.cpp#169 `CheckLoadURIWithPrincipal`: for `nsIJARURI & nsINestedURI` this code will only allow pages from the same jar to be in the same origin (which is correct), for `nsIJARURI` this code is unreachable and it would allow every `resource://android` to load every other `resource://android` URI (which is essentially the same thing). https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/caps/nsScriptSecurityManager.cpp#874-876 `nsDocShell.cpp` `DisplayLoadError`: Just looping through the nested URI chain to build an error message, no concerns here (it would just ignore the `jar:` part, which is fine). https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/docshell/base/nsDocShell.cpp#3986 `DoURILoad`: Looking for `view-source`, no concerns for `resource://android`. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/docshell/base/nsDocShell.cpp#9645 `nsObjectLoadingContent.cpp` Also looking for `view-source`, no concerns. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/dom/base/nsObjectLoadingContent.cpp#2004 `nsIconURI.cpp`/`nsIconURI.h` Exposing `nsINestedURI`. No concerns. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/image/decoders/icon/nsIconURI.cpp#58 `nsJARURI.cpp`/`nsJARURI.h` Exposing `nsINestedURI`, the subject of this audit. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/modules/libjar/nsJARURI.cpp#45 `nsIOService.cpp` `URIChainHasFlags`: This code looks at the chain of nested URIs to figure out if the chain of protocols has a certain flags. for `nsIJARURI & nsINestedURI` it would look at both `jar:` and `file:` protocols, while in `nsIJARURI` it would only look at the `resource:` protocol, which is our intention, since we want this URI to be treated like a `resource:` URI and nothing else. Note the `resource:` URI is always local (enforced by `NewURI`), so this should be ok. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/base/nsIOService.cpp#1494 `nsNetUtil.cpp`/`nsNetUtil.h` Implementation of `NS_ImplGetInnermostURI`, which wouldn't work for `nsIJARURI` alone, as expected. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/base/nsNetUtil.h#704 `nsSimpleNestedURI.cpp`/`nsSimpleNestedURI.h` Implementing `nsINestedURI` for `nsSimpleNestedURI`, no concerns. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/base/nsSimpleNestedURI.cpp#19 `nsViewSourceHandler.cpp` Looking at `view-source` inner URI to get the flags, no concerns. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/protocol/viewsource/nsViewSourceHandler.cpp#49 `nsHtml5StreamParser.cpp`/`nsHtml5TreeOpExecutor.cpp` More `view-source` handling code. No concerns. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/parser/html/nsHtml5StreamParser.cpp#310 https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/parser/html/nsHtml5TreeOpExecutor.cpp#884 `DownloadPlatform.cpp` `IsURLPossiblyFromWeb`: This line is unreachable as the method would return true because resource has `URI_IS_UI_RESOURCE`. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/toolkit/components/downloads/DownloadPlatform.cpp#314 `getAnonymizedPath`: This code looks a little scary, and it's the first one that seems to conflate the fact that `jar: == nsINestedURI`. On the android case (line 2130) this code manually replaces the `resource://android` URI with a `jar:` URI, so it wouldn't matter whether `resource://android` implements `nsINestedURI` or not. Actually this code could be a lot easier by using `nsISubstitutingURL::resolveURI`, maybe I should open a bug. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/toolkit/components/search/SearchService.jsm#2148 `getRemoteTypeForURIObject`: This also looks interesting. The switch at line 157 would go straight to line 218 for `resource:` URIs (irrespective of `nsINestedURI`) and then for `nsINestedURI` we would look at the `jar:` URI (and eventually the `file:` URI). While for not `nsINestedURI` we would call straight to `validatedWebRemoteType`. This might return `WEB_REMOTE_TYPE` instead of `FILE_REMOTE_TYPE`, but since it's already happening it should be ok (`resource://android` maps _today_ to a `jar:` file that return `WEB_RETURN_TYPE`) https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/toolkit/modules/E10SUtils.jsm#150 `getURIHost`: This is another piece of code that benefits from not implementing `nsINestedURI` as the host would be correctly `"android"` instead of the apk path. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/toolkit/mozapps/downloads/DownloadUtils.jsm#390 `initDialog`: Download dialog would show the `resource://android` URI instead of the actual `jar:` URI, kind of expected. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/toolkit/mozapps/downloads/HelperAppDlg.jsm#423 There are no places in the codebase where we do `== "jar"`. Looks like I've already looked at all hits for "jar" too. Checked for `"jar:` too and It looks like they are all from code that tries to build a `jar:` URI manually which is fine for this change. Audited all `schemeIs("jar")` occurrences: https://searchfox.org/mozilla-central/search?q=schemeIs(%22jar%22)&path= `browser-identity.js` Uses the channel URI which is always resolved to `jar:`, so that works regardless. See also: https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/netwerk/protocol/res/SubstitutingProtocolHandler.cpp#229 https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/browser/base/content/browser-siteIdentity.js#812 `tabbrowser.js` Only for `scheme == "about"` URIs. https://searchfox.org/mozilla-central/rev/b36e97fc776635655e84f2048ff59f38fa8a4626/browser/base/content/tabbrowser.js#789 `HelperAppDialog.js` Treats "jar:" and "resource" the same way. https://searchfox.org/mozilla-central/rev/dc0adc07db3df9431a0876156f50c65d580010cb/mobile/android/components/HelperAppDialog.js#63 `WebNavigationContent.js` This code checks if the scheme is "jar" and if the original URI is "resource" it will use that instead, so in our case it will use the "resource" URI either way. https://searchfox.org/mozilla-central/rev/dc0adc07db3df9431a0876156f50c65d580010cb/toolkit/modules/addons/WebNavigationContent.js#158 Depends on D18740 Differential Revision: https://phabricator.services.mozilla.com/D16913
mobile/android/tests/browser/chrome/test_resource_substitutions.html
netwerk/build/components.conf
netwerk/build/nsNetCID.h
netwerk/protocol/res/SubstitutingJARURI.h
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.h
netwerk/protocol/res/moz.build
netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
netwerk/protocol/res/nsResProtocolHandler.cpp
--- a/mobile/android/tests/browser/chrome/test_resource_substitutions.html
+++ b/mobile/android/tests/browser/chrome/test_resource_substitutions.html
@@ -41,16 +41,21 @@ Migrated from Robocop: https://bugzilla.
     let protocolHandler = Services.io
        .getProtocolHandler("resource")
        .QueryInterface(Ci.nsIResProtocolHandler);
 
     ok(protocolHandler.hasSubstitution("app"), "Should have app substitution");
     ok(protocolHandler.hasSubstitution("gre"), "Should have gre substitution");
     ok(protocolHandler.hasSubstitution("android"), "Should have android substitution");
 
+    let uri = Services.io.newURI("resource://android/package-name.txt");
+    ok(uri instanceof Ci.nsIJARURI);
+    ok(uri.JARFile instanceof Ci.nsIFileURL);
+    is(uri.JAREntry, "package-name.txt");
+
     // This can be any file that we know exists in the root of every APK.
     let packageName = await readChannel("resource://android/package-name.txt");
     info(packageName);
 
     // It's difficult to fish ANDROID_PACKAGE_NAME from JavaScript, so we test the
     // following weaker condition.
     let expectedPrefix = "org.mozilla.";
     is(packageName.substring(0, expectedPrefix.length), expectedPrefix);
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -535,16 +535,23 @@ Classes = [
     },
     {
         'cid': '{dea9657c-18cf-4984-bde9-ccef5d8ab473}',
         'contract_ids': [],
         'type': 'mozilla::net::SubstitutingURL::Mutator',
         'headers': ['mozilla/net/SubstitutingURL.h'],
     },
     {
+        'cid': '{50d50ddf-f16a-4652-8705-936b19c3763b}',
+        'contract_ids': [],
+        'type': 'mozilla::net::SubstitutingJARURI',
+        'headers': ['mozilla/net/SubstitutingJARURI.h'],
+        'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+    },
+    {
         'cid': '{e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3}',
         'contract_ids': [],
         'type': 'mozilla::net::nsSimpleURI::Mutator',
         'headers': ['nsSimpleURI.h'],
         'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
     },
 ]
 
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -530,16 +530,23 @@
 
 #define NS_SUBSTITUTINGURLMUTATOR_CID                \
   {                                                  \
     0xb3cfeb91, 0x332a, 0x46c9, {                    \
       0xad, 0x97, 0x93, 0xff, 0x39, 0x84, 0x14, 0x94 \
     }                                                \
   }
 
+#define NS_SUBSTITUTINGJARURI_CID                    \
+  { /* 50d50ddf-f16a-4652-8705-936b19c3763b */       \
+    0x50d50ddf, 0xf16a, 0x4652, {                    \
+      0x87, 0x05, 0x93, 0x6b, 0x19, 0xc3, 0x76, 0x3b \
+    }                                                \
+  }
+
 /******************************************************************************
  * netwerk/protocol/file/ classes
  */
 
 #define NS_FILEPROTOCOLHANDLER_CID                   \
   { /* fbc81170-1f69-11d3-9344-00104ba0fd40 */       \
     0xfbc81170, 0x1f69, 0x11d3, {                    \
       0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40 \
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SubstitutingJARURI_h
+#define SubstitutingJARURI_h
+
+#include "nsIURL.h"
+#include "nsJARURI.h"
+#include "nsISerializable.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_SUBSTITUTINGJARURI_IMPL_CID               \
+  { /* 8f8c54ed-aba7-4ebf-ba6f-e58aec0aba4c */       \
+    0x8f8c54ed, 0xaba7, 0x4ebf, {                    \
+      0xba, 0x6f, 0xe5, 0x8a, 0xec, 0x0a, 0xba, 0x4c \
+    }                                                \
+  }
+
+// Provides a Substituting URI for resource://-like substitutions which
+// allows consumers to access the underlying jar resource.
+class SubstitutingJARURI : public nsIJARURI,
+                           public nsIStandardURL,
+                           public nsISerializable {
+ protected:
+  // Contains the resource://-like URI to be mapped. nsIURI and nsIURL will
+  // forward to this.
+  nsCOMPtr<nsIURL> mSource;
+  // Contains the resolved jar resource, nsIJARURI forwards to this to let
+  // consumer acccess the underlying jar resource.
+  nsCOMPtr<nsIJARURI> mResolved;
+  virtual ~SubstitutingJARURI() = default;
+
+ public:
+  SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved);
+  // For deserializing
+  explicit SubstitutingJARURI() = default;
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSISERIALIZABLE
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+  NS_FORWARD_SAFE_NSIURL(mSource)
+  NS_FORWARD_SAFE_NSIJARURI(mResolved)
+
+  // enum used in a few places to specify how .ref attribute should be handled
+  enum RefHandlingEnum { eIgnoreRef, eHonorRef };
+
+  // We cannot forward Equals* methods to |mSource| so we override them here
+  NS_IMETHOD EqualsExceptRef(nsIURI* aOther, bool* aResult) override;
+  NS_IMETHOD Equals(nsIURI* aOther, bool* aResult) override;
+
+  // Helper to share code between Equals methods.
+  virtual nsresult EqualsInternal(nsIURI* aOther,
+                                  RefHandlingEnum aRefHandlingMode,
+                                  bool* aResult);
+
+  // Forward the rest of nsIURI to mSource
+  NS_IMETHOD GetSpec(nsACString& aSpec) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetSpec(aSpec);
+  }
+  NS_IMETHOD GetPrePath(nsACString& aPrePath) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPrePath(aPrePath);
+  }
+  NS_IMETHOD GetScheme(nsACString& aScheme) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetScheme(aScheme);
+  }
+  NS_IMETHOD GetUserPass(nsACString& aUserPass) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUserPass(aUserPass);
+  }
+  NS_IMETHOD GetUsername(nsACString& aUsername) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetUsername(aUsername);
+  }
+  NS_IMETHOD GetPassword(nsACString& aPassword) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPassword(aPassword);
+  }
+  NS_IMETHOD GetHostPort(nsACString& aHostPort) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHostPort(aHostPort);
+  }
+  NS_IMETHOD GetHost(nsACString& aHost) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHost(aHost);
+  }
+  NS_IMETHOD GetPort(int32_t* aPort) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetPort(aPort);
+  }
+  NS_IMETHOD GetPathQueryRef(nsACString& aPathQueryRef) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetPathQueryRef(aPathQueryRef);
+  }
+  NS_IMETHOD SchemeIs(const char* scheme, bool* _retval) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->SchemeIs(scheme, _retval);
+  }
+  NS_IMETHOD Resolve(const nsACString& relativePath,
+                     nsACString& _retval) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->Resolve(relativePath, _retval);
+  }
+  NS_IMETHOD GetAsciiSpec(nsACString& aAsciiSpec) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiSpec(aAsciiSpec);
+  }
+  NS_IMETHOD GetAsciiHostPort(nsACString& aAsciiHostPort) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetAsciiHostPort(aAsciiHostPort);
+  }
+  NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetAsciiHost(aAsciiHost);
+  }
+  NS_IMETHOD GetRef(nsACString& aRef) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetRef(aRef);
+  }
+  NS_IMETHOD GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetSpecIgnoringRef(aSpecIgnoringRef);
+  }
+  NS_IMETHOD GetHasRef(bool* aHasRef) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetHasRef(aHasRef);
+  }
+  NS_IMETHOD GetFilePath(nsACString& aFilePath) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetFilePath(aFilePath);
+  }
+  NS_IMETHOD GetQuery(nsACString& aQuery) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->GetQuery(aQuery);
+  }
+  NS_IMETHOD GetDisplayHost(nsACString& aDisplayHost) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetDisplayHost(aDisplayHost);
+  }
+  NS_IMETHOD GetDisplayHostPort(nsACString& aDisplayHostPort) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetDisplayHostPort(aDisplayHostPort);
+  }
+  NS_IMETHOD GetDisplaySpec(nsACString& aDisplaySpec) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetDisplaySpec(aDisplaySpec);
+  }
+  NS_IMETHOD GetDisplayPrePath(nsACString& aDisplayPrePath) override {
+    return !mSource ? NS_ERROR_NULL_POINTER
+                    : mSource->GetDisplayPrePath(aDisplayPrePath);
+  }
+  NS_IMETHOD Mutate(nsIURIMutator** _retval) override {
+    return !mSource ? NS_ERROR_NULL_POINTER : mSource->Mutate(_retval);
+  }
+  NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override {
+    MOZ_ASSERT(mSource);
+    mSource->Serialize(aParams);
+  }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SubstitutingJARURI,
+                              NS_SUBSTITUTINGJARURI_IMPL_CID)
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif /* SubstitutingJARURI_h */
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -1,39 +1,46 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Unused.h"
 #include "mozilla/chrome/RegistryMessageUtils.h"
 #include "mozilla/dom/ContentParent.h"
-#include "mozilla/Unused.h"
 
 #include "SubstitutingProtocolHandler.h"
 #include "SubstitutingURL.h"
+#include "SubstitutingJARURI.h"
 #include "nsIChannel.h"
 #include "nsIIOService.h"
 #include "nsIFile.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsURLHelper.h"
 #include "nsEscape.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIClassInfoImpl.h"
 
 using mozilla::dom::ContentParent;
 
 namespace mozilla {
 namespace net {
 
 // Log module for Substituting Protocol logging. We keep the pre-existing module
 // name of "nsResProtocol" to avoid disruption.
 static LazyLogModule gResLog("nsResProtocol");
 
 static NS_DEFINE_CID(kSubstitutingURLCID, NS_SUBSTITUTINGURL_CID);
+static NS_DEFINE_CID(kSubstitutingJARURIImplCID,
+                     NS_SUBSTITUTINGJARURI_IMPL_CID);
 
 //---------------------------------------------------------------------------------
 // SubstitutingURL : overrides nsStandardURL::GetFile to provide nsIFile
 // resolution
 //---------------------------------------------------------------------------------
 
 // The list of interfaces should be in sync with nsStandardURL
 // Queries this list of interfaces. If none match, it queries mURI.
@@ -81,16 +88,118 @@ nsresult SubstitutingURL::EnsureFile() {
 }
 
 NS_IMETHODIMP
 SubstitutingURL::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
   *aClassIDNoAlloc = kSubstitutingURLCID;
   return NS_OK;
 }
 
+// SubstitutingJARURI
+
+SubstitutingJARURI::SubstitutingJARURI(nsIURL* source, nsIJARURI* resolved)
+    : mSource(source), mResolved(resolved) {}
+
+// SubstitutingJARURI::nsIURI
+
+NS_IMETHODIMP
+SubstitutingJARURI::Equals(nsIURI* aOther, bool* aResult) {
+  return EqualsInternal(aOther, eHonorRef, aResult);
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::EqualsExceptRef(nsIURI* aOther, bool* aResult) {
+  return EqualsInternal(aOther, eIgnoreRef, aResult);
+}
+
+nsresult SubstitutingJARURI::EqualsInternal(nsIURI* aOther,
+                                            RefHandlingEnum aRefHandlingMode,
+                                            bool* aResult) {
+  *aResult = false;
+  if (!aOther) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+  RefPtr<SubstitutingJARURI> other;
+  rv =
+      aOther->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(other));
+  if (NS_FAILED(rv)) {
+    return NS_OK;
+  }
+
+  // We only need to check the source as the resolved URI is the same for a
+  // given source
+  return aRefHandlingMode == eHonorRef
+             ? mSource->Equals(other->mSource, aResult)
+             : mSource->EqualsExceptRef(other->mSource, aResult);
+}
+
+// SubstitutingJARURI::nsISerializable
+
+NS_IMETHODIMP
+SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
+  MOZ_ASSERT(!mSource);
+  MOZ_ASSERT(!mResolved);
+  NS_ENSURE_ARG_POINTER(aStream);
+
+  nsresult rv;
+  nsCOMPtr<nsISupports> source;
+  rv = aStream->ReadObject(true, getter_AddRefs(source));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mSource = do_QueryInterface(source, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISupports> resolved;
+  rv = aStream->ReadObject(true, getter_AddRefs(resolved));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mResolved = do_QueryInterface(resolved, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SubstitutingJARURI::Write(nsIObjectOutputStream* aStream) {
+  NS_ENSURE_ARG_POINTER(aStream);
+
+  nsresult rv;
+  rv = aStream->WriteCompoundObject(mSource, NS_GET_IID(nsISupports), true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aStream->WriteCompoundObject(mResolved, NS_GET_IID(nsISupports), true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, nsIClassInfo::MAIN_THREAD_ONLY,
+                  NS_SUBSTITUTINGJARURI_CID)
+
+NS_IMPL_ADDREF(SubstitutingJARURI)
+NS_IMPL_RELEASE(SubstitutingJARURI)
+
+NS_INTERFACE_MAP_BEGIN(SubstitutingJARURI)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
+  NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+  NS_INTERFACE_MAP_ENTRY(nsIURL)
+  NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+  NS_INTERFACE_MAP_ENTRY(nsISerializable)
+  if (aIID.Equals(kSubstitutingJARURIImplCID)) {
+    foundInterface = static_cast<nsIURI*>(this);
+  } else
+    NS_INTERFACE_MAP_ENTRY(nsIURI)
+  NS_IMPL_QUERY_CLASSINFO(SubstitutingJARURI)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(SubstitutingJARURI, nsIURI, nsIJARURI, nsIURL,
+                            nsIStandardURL, nsISerializable)
+
 SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
                                                          uint32_t aFlags,
                                                          bool aEnforceFileOrJar)
     : mScheme(aScheme),
       mSubstitutions(16),
       mEnforceFileOrJar(aEnforceFileOrJar) {
   mFlags.emplace(aFlags);
   ConstructInternal();
@@ -182,20 +291,21 @@ nsresult SubstitutingProtocolHandler::Ge
 
   *result = mFlags.ref();
   return NS_OK;
 }
 
 nsresult SubstitutingProtocolHandler::NewURI(const nsACString& aSpec,
                                              const char* aCharset,
                                              nsIURI* aBaseURI,
-                                             nsIURI** result) {
+                                             nsIURI** aResult) {
   // unescape any %2f and %2e to make sure nsStandardURL coalesces them.
   // Later net_GetFileFromURLSpec() will do a full unescape and we want to
   // treat them the same way the file system will. (bugs 380994, 394075)
+  nsresult rv;
   nsAutoCString spec;
   const char* src = aSpec.BeginReading();
   const char* end = aSpec.EndReading();
   const char* last = src;
 
   spec.SetCapacity(aSpec.Length() + 1);
   for (; src < end; ++src) {
     if (*src == '%' && (src < end - 2) && *(src + 1) == '2') {
@@ -214,21 +324,76 @@ nsresult SubstitutingProtocolHandler::Ne
         src += 2;
         last = src + 1;  // src will be incremented by the loop
       }
     }
   }
   if (last < src) spec.Append(last, src - last);
 
   nsCOMPtr<nsIURI> base(aBaseURI);
-  return NS_MutateURI(new SubstitutingURL::Mutator())
-      .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
-                              nsIStandardURL::URLTYPE_STANDARD, -1, spec,
-                              aCharset, base, nullptr))
-      .Finalize(result);
+  nsCOMPtr<nsIURL> uri;
+  rv = NS_MutateURI(new SubstitutingURL::Mutator())
+           .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+                                   nsIStandardURL::URLTYPE_STANDARD, -1, spec,
+                                   aCharset, base, nullptr))
+           .Finalize(uri);
+  if (NS_FAILED(rv)) return rv;
+
+  nsAutoCString host;
+  rv = uri->GetHost(host);
+  if (NS_FAILED(rv)) return rv;
+
+  uint32_t flags;
+  rv = GetSubstitutionFlags(host, &flags);
+  if (NS_FAILED(rv)) {
+    // This protocol has not been registered yet or does not exist,
+    // we can assume we don't expose nsIJARURI
+    uri.forget(aResult);
+    return NS_OK;
+  }
+
+  if (flags & nsISubstitutingProtocolHandler::RESOLVE_JAR_URI) {
+    rv = ResolveJARURI(uri, aResult);
+  } else {
+    uri.forget(aResult);
+  }
+
+  return rv;
+}
+
+nsresult SubstitutingProtocolHandler::ResolveJARURI(nsIURL* aURL,
+                                                    nsIURI** aResult) {
+  nsAutoCString spec;
+  nsresult rv = ResolveURI(aURL, spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> resolvedURI;
+  rv = NS_NewURI(getter_AddRefs(resolvedURI), spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(resolvedURI);
+  nsAutoCString scheme;
+  innermostURI->GetScheme(scheme);
+
+  // We only ever want to resolve to a local jar.
+  NS_ENSURE_TRUE(scheme.EqualsLiteral("file"), NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(resolvedURI));
+  if (!jarURI) {
+    // This substitution does not resolve to a jar: URL, so we just
+    // return the plain SubstitutionURL
+    *aResult = aURL;
+    NS_ADDREF(*aResult);
+    return rv;
+  }
+
+  nsCOMPtr<nsIJARURI> result = new SubstitutingJARURI(aURL, jarURI);
+  result.forget(aResult);
+
+  return rv;
 }
 
 nsresult SubstitutingProtocolHandler::NewChannel(nsIURI* uri,
                                                  nsILoadInfo* aLoadInfo,
                                                  nsIChannel** result) {
   NS_ENSURE_ARG_POINTER(uri);
   NS_ENSURE_ARG_POINTER(aLoadInfo);
 
@@ -291,17 +456,18 @@ nsresult SubstitutingProtocolHandler::Se
 
   // If baseURI isn't a same-scheme URI, we can set the substitution
   // immediately.
   nsAutoCString scheme;
   nsresult rv = baseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!scheme.Equals(mScheme)) {
     if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") &&
-        !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app")) {
+        !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app") &&
+        !scheme.EqualsLiteral("resource")) {
       NS_WARNING("Refusing to create substituting URI to non-file:// target");
       return NS_ERROR_INVALID_ARG;
     }
 
     SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
     entry.baseURI = baseURI;
     entry.flags = flags;
     NotifyObservers(root, baseURI);
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -6,16 +6,18 @@
 
 #ifndef SubstitutingProtocolHandler_h___
 #define SubstitutingProtocolHandler_h___
 
 #include "nsISubstitutingProtocolHandler.h"
 
 #include "nsISubstitutionObserver.h"
 #include "nsDataHashtable.h"
+#include "nsStandardURL.h"
+#include "nsJARURI.h"
 #include "mozilla/chrome/RegistryMessageUtils.h"
 #include "mozilla/Maybe.h"
 
 class nsIIOService;
 
 namespace mozilla {
 namespace net {
 
@@ -97,16 +99,20 @@ class SubstitutingProtocolHandler {
   Maybe<uint32_t> mFlags;
   nsDataHashtable<nsCStringHashKey, SubstitutionEntry> mSubstitutions;
   nsCOMPtr<nsIIOService> mIOService;
 
   // The list of observers added with AddObserver that will be
   // notified when substitutions are set or unset.
   nsTArray<nsCOMPtr<nsISubstitutionObserver>> mObservers;
 
+  // Returns a SubstitutingJARURI if |aUrl| maps to a |jar:| URI,
+  // otherwise will return |aURL|
+  nsresult ResolveJARURI(nsIURL* aURL, nsIURI** aResult);
+
   // In general, we expect the principal of a document loaded from a
   // substituting URI to be a codebase principal for that URI (rather than
   // a principal for whatever is underneath). However, this only works if
   // the protocol handler for the underlying URI doesn't set an explicit
   // owner (which chrome:// does, for example). So we want to require that
   // substituting URIs only map to other URIs of the same type, or to
   // file:// and jar:// URIs.
   //
--- a/netwerk/protocol/res/moz.build
+++ b/netwerk/protocol/res/moz.build
@@ -9,16 +9,17 @@ XPIDL_SOURCES += [
     'nsISubstitutingProtocolHandler.idl',
     'nsISubstitutionObserver.idl',
 ]
 
 XPIDL_MODULE = 'necko_res'
 
 EXPORTS.mozilla.net += [
     'ExtensionProtocolHandler.h',
+    'SubstitutingJARURI.h',
     'SubstitutingProtocolHandler.h',
     'SubstitutingURL.h',
 ]
 
 UNIFIED_SOURCES += [
     'ExtensionProtocolHandler.cpp',
     'nsResProtocolHandler.cpp',
     'SubstitutingProtocolHandler.cpp',
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -12,17 +12,22 @@ interface nsISubstitutionObserver;
  * from URIs of its scheme to URIs of another scheme.
  */
 [scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
 interface nsISubstitutingProtocolHandler : nsIProtocolHandler
 {
   /**
    * Content script may access files in this package.
    */
-  const short ALLOW_CONTENT_ACCESS = 1;
+  const short ALLOW_CONTENT_ACCESS = 1 << 0;
+  /**
+   * This substitution exposes nsIJARURI instead of a nsIFileURL.  By default
+   * NewURI will always return a nsIFileURL even when the URL is jar:
+   */
+  const short RESOLVE_JAR_URI = 1 << 1;
 
   /**
    * Sets the substitution for the root key:
    *   resource://root/path ==> baseURI.resolve(path)
    *
    * A null baseURI removes the specified substitution.
    *
    * The root key will be converted to lower-case to conform to
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -115,17 +115,21 @@ nsresult nsResProtocolHandler::GetSubsti
                                                        uint32_t* aFlags) {
   nsAutoCString uri;
 
   if (!ResolveSpecialCases(aRoot, NS_LITERAL_CSTRING("/"),
                            NS_LITERAL_CSTRING("/"), uri)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  *aFlags = 0;  // No content access.
+  if (aRoot.Equals(kAndroid)) {
+    *aFlags = nsISubstitutingProtocolHandler::RESOLVE_JAR_URI;
+  } else {
+    *aFlags = 0;  // No content access.
+  }
   return NS_NewURI(aResult, uri);
 }
 
 bool nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
                                                const nsACString& aPath,
                                                const nsACString& aPathname,
                                                nsACString& aResult) {
   if (aHost.EqualsLiteral("") || aHost.EqualsLiteral(kAPP)) {