Bug 1532253 - Add NS_NewURIOnAnyThread r=baku!
☠☠ backed out by 949fbfb190a2 ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Tue, 05 Mar 2019 14:01:58 +0100
changeset 521352 5384779a3b1f8835ebd2fbe16103744111d3896d
parent 521350 85952c9c8c02e28de331622831432ef3c0f0d66a
child 521353 0491d225285c9ed0228228fb785b99a3fddc8f36
push id10866
push usernerli@mozilla.com
push dateTue, 12 Mar 2019 18:59:09 +0000
treeherdermozilla-beta@445c24a51727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1532253
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 1532253 - Add NS_NewURIOnAnyThread r=baku! Differential Revision: https://phabricator.services.mozilla.com/D22137
chrome/moz.build
chrome/nsChromeProtocolHandler.cpp
chrome/nsChromeProtocolHandler.h
dom/jsurl/nsJSProtocolHandler.cpp
dom/jsurl/nsJSProtocolHandler.h
mfbt/ThreadLocal.h
netwerk/base/nsNetUtil.cpp
netwerk/base/nsNetUtil.h
netwerk/protocol/data/moz.build
netwerk/protocol/data/nsDataHandler.cpp
netwerk/protocol/data/nsDataHandler.h
netwerk/test/gtest/TestURIMutator.cpp
xpcom/threads/ThreadLocalVariables.cpp
xpcom/threads/moz.build
xpcom/threads/nsThread.cpp
--- a/chrome/moz.build
+++ b/chrome/moz.build
@@ -8,16 +8,20 @@ TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'nsIChromeRegistry.idl',
     'nsIToolkitChromeRegistry.idl',
 ]
 
 XPIDL_MODULE = 'chrome'
 
+EXPORTS += [
+    'nsChromeProtocolHandler.h',
+]
+
 EXPORTS.mozilla.chrome += [
     'RegistryMessageUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsChromeProtocolHandler.cpp',
     'nsChromeRegistry.cpp',
     'nsChromeRegistryChrome.cpp',
--- a/chrome/nsChromeProtocolHandler.cpp
+++ b/chrome/nsChromeProtocolHandler.cpp
@@ -61,16 +61,23 @@ NS_IMETHODIMP
 nsChromeProtocolHandler::GetProtocolFlags(uint32_t *result) {
   *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsChromeProtocolHandler::NewURI(const nsACString &aSpec, const char *aCharset,
                                 nsIURI *aBaseURI, nsIURI **result) {
+  return nsChromeProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+                                               result);
+}
+
+/* static */ nsresult nsChromeProtocolHandler::CreateNewURI(
+    const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI,
+    nsIURI **result) {
   // Chrome: URLs (currently) have no additional structure beyond that provided
   // by standard URLs, so there is no "outer" given to CreateInstance
   nsresult rv;
   nsCOMPtr<nsIURI> surl;
   nsCOMPtr<nsIURI> base(aBaseURI);
   rv = NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
            .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
                                    nsIStandardURL::URLTYPE_STANDARD, -1,
--- a/chrome/nsChromeProtocolHandler.h
+++ b/chrome/nsChromeProtocolHandler.h
@@ -22,14 +22,16 @@ class nsChromeProtocolHandler final : pu
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // nsIProtocolHandler methods:
   NS_DECL_NSIPROTOCOLHANDLER
 
   // nsChromeProtocolHandler methods:
   nsChromeProtocolHandler() {}
+  static nsresult CreateNewURI(const nsACString &aSpec, const char *aCharset,
+                               nsIURI *aBaseURI, nsIURI **result);
 
  private:
   ~nsChromeProtocolHandler() {}
 };
 
 #endif /* nsChromeProtocolHandler_h___ */
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -37,16 +37,17 @@
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 #include "nsIScriptChannel.h"
 #include "mozilla/dom/Document.h"
 #include "nsILoadInfo.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
+#include "nsITextToSubURI.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsSandboxFlags.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/PopupBlocker.h"
 #include "nsILoadInfo.h"
 #include "nsContentSecurityManager.h"
@@ -1039,30 +1040,29 @@ nsresult nsJSProtocolHandler::Create(nsI
   nsresult rv = ph->Init();
   if (NS_SUCCEEDED(rv)) {
     rv = ph->QueryInterface(aIID, aResult);
   }
   NS_RELEASE(ph);
   return rv;
 }
 
-nsresult nsJSProtocolHandler::EnsureUTF8Spec(const nsCString& aSpec,
-                                             const char* aCharset,
-                                             nsACString& aUTF8Spec) {
+/* static */ nsresult nsJSProtocolHandler::EnsureUTF8Spec(
+    const nsCString& aSpec, const char* aCharset, nsACString& aUTF8Spec) {
   aUTF8Spec.Truncate();
 
   nsresult rv;
 
-  if (!mTextToSubURI) {
-    mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  nsCOMPtr<nsITextToSubURI> txtToSubURI =
+      do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsAutoString uStr;
-  rv = mTextToSubURI->UnEscapeNonAsciiURI(nsDependentCString(aCharset), aSpec,
-                                          uStr);
+  rv = txtToSubURI->UnEscapeNonAsciiURI(nsDependentCString(aCharset), aSpec,
+                                        uStr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!IsASCII(uStr)) {
     rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(uStr),
                       esc_AlwaysCopy | esc_OnlyNonASCII, aUTF8Spec,
                       mozilla::fallible);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -1087,20 +1087,26 @@ nsJSProtocolHandler::GetDefaultPort(int3
 
 NS_IMETHODIMP
 nsJSProtocolHandler::GetProtocolFlags(uint32_t* result) {
   *result = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT |
             URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE |
             URI_OPENING_EXECUTES_SCRIPT;
   return NS_OK;
 }
-
 NS_IMETHODIMP
 nsJSProtocolHandler::NewURI(const nsACString& aSpec, const char* aCharset,
                             nsIURI* aBaseURI, nsIURI** result) {
+  return nsJSProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, result);
+}
+
+/* static */ nsresult nsJSProtocolHandler::CreateNewURI(const nsACString& aSpec,
+                                                        const char* aCharset,
+                                                        nsIURI* aBaseURI,
+                                                        nsIURI** result) {
   nsresult rv = NS_OK;
 
   // javascript: URLs (currently) have no additional structure beyond that
   // provided by standard URLs, so there is no "outer" object given to
   // CreateInstance.
 
   NS_MutateURI mutator(new nsJSURI::Mutator());
   nsCOMPtr<nsIURI> base(aBaseURI);
--- a/dom/jsurl/nsJSProtocolHandler.h
+++ b/dom/jsurl/nsJSProtocolHandler.h
@@ -3,17 +3,16 @@
  * 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 nsJSProtocolHandler_h___
 #define nsJSProtocolHandler_h___
 
 #include "mozilla/Attributes.h"
 #include "nsIProtocolHandler.h"
-#include "nsITextToSubURI.h"
 #include "nsIURI.h"
 #include "nsIMutable.h"
 #include "nsISerializable.h"
 #include "nsIClassInfo.h"
 #include "nsSimpleURI.h"
 #include "nsINestedURI.h"
 
 #define NS_JSPROTOCOLHANDLER_CID                     \
@@ -49,23 +48,24 @@ class nsJSProtocolHandler : public nsIPr
 
   // nsJSProtocolHandler methods:
   nsJSProtocolHandler();
 
   static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 
   nsresult Init();
 
+  static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+                               nsIURI* aBaseURI, nsIURI** result);
+
  protected:
   virtual ~nsJSProtocolHandler();
 
-  nsresult EnsureUTF8Spec(const nsCString& aSpec, const char* aCharset,
-                          nsACString& aUTF8Spec);
-
-  nsCOMPtr<nsITextToSubURI> mTextToSubURI;
+  static nsresult EnsureUTF8Spec(const nsCString& aSpec, const char* aCharset,
+                                 nsACString& aUTF8Spec);
 };
 
 class nsJSURI final : public mozilla::net::nsSimpleURI {
  public:
   using mozilla::net::nsSimpleURI::Read;
   using mozilla::net::nsSimpleURI::Write;
 
   nsIURI* GetBaseURI() const { return mBaseURI; }
--- a/mfbt/ThreadLocal.h
+++ b/mfbt/ThreadLocal.h
@@ -173,16 +173,18 @@ class ThreadLocal : public Storage<T> {
 
   void infallibleInit() {
     MOZ_RELEASE_ASSERT(init(), "Infallible TLS initialization failed");
   }
 
   inline T get() const;
 
   inline void set(const T aValue);
+
+  using Type = T;
 };
 
 template <typename T, template <typename U> class Storage>
 inline bool ThreadLocal<T, Storage>::init() {
   static_assert(mozilla::IsPointer<T>::value || mozilla::IsIntegral<T>::value,
                 "mozilla::ThreadLocal must be used with a pointer or "
                 "integral type");
   static_assert(sizeof(T) <= sizeof(void*),
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -80,16 +80,20 @@
 #include "nsHttpHandler.h"
 #include "nsNSSComponent.h"
 #include "nsIRedirectHistoryEntry.h"
 #include "nsICertBlocklist.h"
 #include "nsICertOverrideService.h"
 #include "nsQueryObject.h"
 #include "mozIThirdPartyUtil.h"
 #include "../mime/nsMIMEHeaderParamImpl.h"
+#include "nsStandardURL.h"
+#include "nsChromeProtocolHandler.h"
+#include "nsJSProtocolHandler.h"
+#include "nsDataHandler.h"
 
 #include <limits>
 
 using namespace mozilla;
 using namespace mozilla::net;
 using mozilla::dom::BlobURLProtocolHandler;
 using mozilla::dom::ClientInfo;
 using mozilla::dom::PerformanceStorage;
@@ -1669,16 +1673,138 @@ nsresult NS_NewURI(
     nsIURI **result, const char *spec, nsIURI *baseURI /* = nullptr */,
     nsIIOService
         *ioService /* = nullptr */)  // pass in nsIIOService to optimize callers
 {
   return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI,
                    ioService);
 }
 
+static nsresult NewStandardURI(const nsACString &aSpec, const char *aCharset,
+                               nsIURI *aBaseURI, int32_t aDefaultPort,
+                               nsIURI **aURI) {
+  nsCOMPtr<nsIURI> base(aBaseURI);
+  return NS_MutateURI(new nsStandardURL::Mutator())
+      .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+                              nsIStandardURL::URLTYPE_AUTHORITY, aDefaultPort,
+                              nsCString(aSpec), aCharset, base, nullptr))
+      .Finalize(aURI);
+}
+
+extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+template <typename T>
+class TlsAutoIncrement {
+ public:
+  explicit TlsAutoIncrement(T &var) : mVar(var) {
+    mValue = mVar.get();
+    mVar.set(mValue + 1);
+  }
+  ~TlsAutoIncrement() {
+    typename T::Type value = mVar.get();
+    MOZ_ASSERT(value == mValue + 1);
+    mVar.set(value - 1);
+  }
+
+  typename T::Type value() { return mValue; }
+
+ private:
+  typename T::Type mValue;
+  T &mVar;
+};
+
+nsresult NS_NewURIOnAnyThread(nsIURI **aURI, const nsACString &aSpec,
+                              const char *aCharset /* = nullptr */,
+                              nsIURI *aBaseURI /* = nullptr */,
+                              nsIIOService *aIOService /* = nullptr */) {
+  TlsAutoIncrement<typeof(gTlsURLRecursionCount)> inc(gTlsURLRecursionCount);
+  if (inc.value() >= MAX_RECURSION_COUNT) {
+    return NS_ERROR_MALFORMED_URI;
+  }
+
+  nsAutoCString scheme;
+  nsresult rv = net_ExtractURLScheme(aSpec, scheme);
+  if (NS_FAILED(rv)) {
+    // then aSpec is relative
+    if (!aBaseURI) {
+      return NS_ERROR_MALFORMED_URI;
+    }
+
+    if (!aSpec.IsEmpty() && aSpec[0] == '#') {
+      // Looks like a reference instead of a fully-specified URI.
+      // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+      return NS_GetURIWithNewRef(aBaseURI, aSpec, aURI);
+    }
+
+    rv = aBaseURI->GetScheme(scheme);
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("ws")) {
+    return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT,
+                          aURI);
+  }
+  if (scheme.EqualsLiteral("https") || scheme.EqualsLiteral("wss")) {
+    return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT,
+                          aURI);
+  }
+  if (scheme.EqualsLiteral("ftp")) {
+    return NewStandardURI(aSpec, aCharset, aBaseURI, 21, aURI);
+  }
+
+  if (scheme.EqualsLiteral("file")) {
+    nsAutoCString buf(aSpec);
+#if defined(XP_WIN)
+    buf.Truncate();
+    if (!net_NormalizeFileURL(aSpec, buf)) {
+      buf = aSpec;
+    }
+#endif
+
+    nsCOMPtr<nsIURI> base(aBaseURI);
+    return NS_MutateURI(new nsStandardURL::Mutator())
+        .Apply(NS_MutatorMethod(&nsIFileURLMutator::MarkFileURL))
+        .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+                                nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, buf,
+                                aCharset, base, nullptr))
+        .Finalize(aURI);
+  }
+
+  if (scheme.EqualsLiteral("data")) {
+    return nsDataHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+  }
+
+  if (scheme.EqualsLiteral("moz-safe-about") ||
+      scheme.EqualsLiteral("page-icon") || scheme.EqualsLiteral("moz") ||
+      scheme.EqualsLiteral("moz-anno") ||
+      scheme.EqualsLiteral("moz-page-thumb") ||
+      scheme.EqualsLiteral("moz-fonttable")) {
+    return NS_MutateURI(new nsSimpleURI::Mutator())
+        .SetSpec(aSpec)
+        .Finalize(aURI);
+  }
+
+  if (scheme.EqualsLiteral("chrome")) {
+    return nsChromeProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+                                                 aURI);
+  }
+
+  if (scheme.EqualsLiteral("javascript")) {
+    return nsJSProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+  }
+
+  if (NS_IsMainThread()) {
+    // XXX (valentin): this fallback should be removed once we get rid of
+    // nsIProtocolHandler.newURI
+    return NS_NewURI(aURI, aSpec, aCharset, aBaseURI, aIOService);
+  }
+
+  return NS_ERROR_UNKNOWN_PROTOCOL;
+}
+
 nsresult NS_GetSanitizedURIStringFromURI(nsIURI *aUri,
                                          nsAString &aSanitizedSpec) {
   aSanitizedSpec.Truncate();
 
   nsCOMPtr<nsISensitiveInfoHiddenURI> safeUri = do_QueryInterface(aUri);
   nsAutoCString cSpec;
   nsresult rv;
   if (safeUri) {
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -95,16 +95,29 @@ nsresult NS_NewURI(nsIURI **result, cons
                    nsIURI *baseURI = nullptr,
                    nsIIOService *ioService =
                        nullptr);  // pass in nsIIOService to optimize callers
 
 nsresult NS_NewURI(nsIURI **result, const char *spec, nsIURI *baseURI = nullptr,
                    nsIIOService *ioService =
                        nullptr);  // pass in nsIIOService to optimize callers
 
+// This function attempts to create an nsIURI on any thread. This implies we
+// can't instantiate a protcol handler, since protocol handers may have a JS
+// implementation so they can't work off-main-thread.
+// When called off the main thread, if the nsIURI can't be created without
+// instantiating protocol handlers, the method will return
+// NS_ERROR_UNKNOWN_PROTOCOL. The caller may retry on the main thread.
+// When called on the main thread, this function will fall back on calling
+// nsIProtocolHandler.newURI
+nsresult NS_NewURIOnAnyThread(nsIURI **aResult, const nsACString &aSpec,
+                              const char *aCharset = nullptr,
+                              nsIURI *aBaseURI = nullptr,
+                              nsIIOService *aIOService = nullptr);
+
 nsresult NS_NewFileURI(
     nsIURI **result, nsIFile *spec,
     nsIIOService *ioService =
         nullptr);  // pass in nsIIOService to optimize callers
 
 // These methods will only mutate the URI if the ref of aInput doesn't already
 // match the ref we are trying to set.
 // If aInput has no ref, and we are calling NS_GetURIWithoutRef, or
--- a/netwerk/protocol/data/moz.build
+++ b/netwerk/protocol/data/moz.build
@@ -3,16 +3,20 @@
 # 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/.
 
 EXPORTS.mozilla.net += [
     'DataChannelParent.h',
 ]
 
+EXPORTS += [
+  'nsDataHandler.h',
+]
+
 UNIFIED_SOURCES += [
     'DataChannelChild.cpp',
     'DataChannelParent.cpp',
     'nsDataChannel.cpp',
     'nsDataHandler.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
--- a/netwerk/protocol/data/nsDataHandler.cpp
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -46,16 +46,23 @@ nsDataHandler::GetProtocolFlags(uint32_t
             URI_IS_LOCAL_RESOURCE | URI_SYNC_LOAD_IS_OK;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDataHandler::NewURI(const nsACString& aSpec,
                       const char* aCharset,  // ignore charset info
                       nsIURI* aBaseURI, nsIURI** result) {
+  return nsDataHandler::CreateNewURI(aSpec, aCharset, aBaseURI, result);
+}
+
+/* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec,
+                                                  const char* aCharset,
+                                                  nsIURI* aBaseURI,
+                                                  nsIURI** result) {
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
 
   nsCString spec(aSpec);
 
   if (aBaseURI && !spec.IsEmpty() && spec[0] == '#') {
     // Looks like a reference instead of a fully-specified URI.
     // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
--- a/netwerk/protocol/data/nsDataHandler.h
+++ b/netwerk/protocol/data/nsDataHandler.h
@@ -17,16 +17,19 @@ class nsDataHandler : public nsIProtocol
   NS_DECL_ISUPPORTS
 
   // nsIProtocolHandler methods:
   NS_DECL_NSIPROTOCOLHANDLER
 
   // nsDataHandler methods:
   nsDataHandler() = default;
 
+  static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+                               nsIURI* aBaseURI, nsIURI** result);
+
   // Define a Create method to be used with a factory:
   static MOZ_MUST_USE nsresult Create(nsISupports* aOuter, const nsIID& aIID,
                                       void** aResult);
 
   // Parse a data: URI and return the individual parts
   // (the given spec will temporarily be modified but will be returned
   //  to the original before returning)
   // contentCharset and dataBuffer can be nullptr if they are not needed.
--- a/netwerk/test/gtest/TestURIMutator.cpp
+++ b/netwerk/test/gtest/TestURIMutator.cpp
@@ -1,13 +1,15 @@
 #include "gtest/gtest.h"
 #include "nsCOMPtr.h"
 #include "nsNetCID.h"
 #include "nsIURL.h"
 #include "nsIURIMutator.h"
+#include "nsThreadPool.h"
+#include "nsNetUtil.h"
 
 TEST(TestURIMutator, Mutator) {
   nsAutoCString out;
 
   // This test instantiates a new nsStandardURL::Mutator (via contractID)
   // and uses it to create a new URI.
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
@@ -92,8 +94,44 @@ TEST(TestURIMutator, Mutator) {
   ASSERT_EQ(rv, NS_OK);
   ASSERT_EQ(uri2->GetSpec(out), NS_OK);
   ASSERT_TRUE(out == NS_LITERAL_CSTRING("https://mozilla.org/path#newref"));
   nsCOMPtr<nsIURI> uri3;
   rv = mutator.Finalize(uri3);
   ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
   ASSERT_TRUE(uri3 == nullptr);
 }
+
+extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+TEST(TestURIMutator, NS_NewURIOnAnyThread) {
+  nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+  pool->SetThreadLimit(60);
+
+  pool = new nsThreadPool();
+  for (int i = 0; i < 1000; ++i) {
+    nsCOMPtr<nsIRunnable> task =
+        NS_NewRunnableFunction("gtest-NS_NewURIOnAnyThread", []() {
+          nsCOMPtr<nsIURI> uri;
+          nsresult rv = NS_NewURIOnAnyThread(
+              getter_AddRefs(uri), NS_LITERAL_CSTRING("http://example.com"));
+          ASSERT_EQ(rv, NS_OK);
+          nsAutoCString out;
+          ASSERT_EQ(uri->GetSpec(out), NS_OK);
+          ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/"));
+        });
+    EXPECT_TRUE(task);
+
+    pool->Dispatch(task, NS_DISPATCH_NORMAL);
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURIOnAnyThread(getter_AddRefs(uri),
+                                     NS_LITERAL_CSTRING("http://example.com"));
+  ASSERT_EQ(rv, NS_OK);
+  nsAutoCString out;
+  ASSERT_EQ(uri->GetSpec(out), NS_OK);
+  ASSERT_TRUE(out == NS_LITERAL_CSTRING("http://example.com/"));
+
+  pool->Shutdown();
+
+  ASSERT_EQ(gTlsURLRecursionCount.get(), 0u);
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadLocalVariables.cpp
@@ -0,0 +1,14 @@
+/* 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/. */
+
+// This variable is used to ensure creating new URI doesn't put us in an
+// infinite loop
+MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+void InitThreadLocalVariables() {
+  if (!gTlsURLRecursionCount.init()) {
+    MOZ_CRASH("Could not init gTlsURLRecursionCount");
+  }
+  gTlsURLRecursionCount.set(0);
+}
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -102,16 +102,17 @@ UNIFIED_SOURCES += [
     'RWLock.cpp',
     'SchedulerGroup.cpp',
     'SharedThreadPool.cpp',
     'SynchronizedEventQueue.cpp',
     'SystemGroup.cpp',
     'TaskQueue.cpp',
     'ThreadEventQueue.cpp',
     'ThreadEventTarget.cpp',
+    'ThreadLocalVariables.cpp',
     'ThrottledEventQueue.cpp',
     'TimerThread.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../build',
     '/caps',
     '/tools/profiler',
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -567,16 +567,17 @@ void nsThread::InitCommon() {
       ULONG_PTR stackBottom, stackTop;
       sGetStackLimits(&stackBottom, &stackTop);
       mStackBase = reinterpret_cast<void*>(stackBottom);
       mStackSize = stackTop - stackBottom;
     }
 #endif
   }
 
+  InitThreadLocalVariables();
   AddToThreadList();
 }
 
 //-----------------------------------------------------------------------------
 
 #ifdef MOZ_CANARY
 int sCanaryOutputFD = -1;
 #endif