Bug 1069401 - UserAgent cannot be changed for specific websites in workers, r=khuey, r=bz
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 24 Sep 2014 17:09:50 +0100
changeset 207060 4f72287c59caee404fd91b64d08306a85a228ccf
parent 207059 c9d5d07e1a63bd9d83ccafe28d9c661ae41bbf1d
child 207061 218764a2e57b2daa9e7f6dfad7b985a64ad1fa48
push id27544
push userryanvm@gmail.com
push dateWed, 24 Sep 2014 21:10:36 +0000
treeherdermozilla-central@1735ff2bb23e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, bz
bugs1069401
milestone35.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 1069401 - UserAgent cannot be changed for specific websites in workers, r=khuey, r=bz
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsGlobalWindow.cpp
dom/base/test/mochitest.ini
dom/base/test/test_navigatorPrefOverride.html
dom/webidl/Navigator.webidl
dom/workers/Navigator.cpp
dom/workers/Navigator.h
dom/workers/RuntimeService.cpp
dom/workers/RuntimeService.h
dom/workers/test/test_bug1062920.html
dom/workers/test/test_bug1062920.xul
netwerk/test/mochitests/test_user_agent_overrides.html
netwerk/test/mochitests/test_user_agent_updates.html
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -308,40 +308,29 @@ Navigator::Invalidate()
 
 //*****************************************************************************
 //    Navigator::nsIDOMNavigator
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetUserAgent(nsAString& aUserAgent)
 {
-  nsresult rv = NS_GetNavigatorUserAgent(aUserAgent);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!mWindow || !mWindow->GetDocShell()) {
-    return NS_OK;
-  }
-
-  nsIDocument* doc = mWindow->GetExtantDoc();
-  if (!doc) {
-    return NS_OK;
+  nsCOMPtr<nsIURI> codebaseURI;
+  nsCOMPtr<nsPIDOMWindow> window;
+
+  if (mWindow && mWindow->GetDocShell()) {
+    window = mWindow;
+    nsIDocument* doc = mWindow->GetExtantDoc();
+    if (doc) {
+      doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+    }
   }
 
-  nsCOMPtr<nsIURI> codebaseURI;
-  doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
-  if (!codebaseURI) {
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsISiteSpecificUserAgent> siteSpecificUA =
-    do_GetService("@mozilla.org/dom/site-specific-user-agent;1");
-  NS_ENSURE_TRUE(siteSpecificUA, NS_OK);
-
-  return siteSpecificUA->GetUserAgentForURIAndWindow(codebaseURI, mWindow,
-                                                     aUserAgent);
+  return GetUserAgent(window, codebaseURI, nsContentUtils::IsCallerChrome(),
+                      aUserAgent);
 }
 
 NS_IMETHODIMP
 Navigator::GetAppCodeName(nsAString& aAppCodeName)
 {
   nsresult rv;
 
   nsCOMPtr<nsIHttpProtocolHandler>
@@ -2390,16 +2379,18 @@ Navigator::GetPlatform(nsAString& aPlatf
 #endif
 
   return rv;
 }
 
 /* static */ nsresult
 Navigator::GetAppVersion(nsAString& aAppVersion, bool aUsePrefOverriddenValue)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
     const nsAdoptingString& override =
       mozilla::Preferences::GetString("general.appversion.override");
 
     if (override) {
       aAppVersion = override;
       return NS_OK;
     }
@@ -2425,39 +2416,72 @@ Navigator::GetAppVersion(nsAString& aApp
   aAppVersion.Append(char16_t(')'));
 
   return rv;
 }
 
 /* static */ void
 Navigator::AppName(nsAString& aAppName, bool aUsePrefOverriddenValue)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
     const nsAdoptingString& override =
       mozilla::Preferences::GetString("general.appname.override");
 
     if (override) {
       aAppName = override;
       return;
     }
   }
 
   aAppName.AssignLiteral("Netscape");
 }
 
-} // namespace dom
-} // namespace mozilla
-
 nsresult
-NS_GetNavigatorUserAgent(nsAString& aUserAgent)
+Navigator::GetUserAgent(nsPIDOMWindow* aWindow, nsIURI* aURI,
+                        bool aIsCallerChrome,
+                        nsAString& aUserAgent)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aIsCallerChrome) {
+    const nsAdoptingString& override =
+      mozilla::Preferences::GetString("general.useragent.override");
+
+    if (override) {
+      aUserAgent = override;
+      return NS_OK;
+    }
+  }
+
   nsresult rv;
-
   nsCOMPtr<nsIHttpProtocolHandler>
     service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   nsAutoCString ua;
   rv = service->GetUserAgent(ua);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   CopyASCIItoUTF16(ua, aUserAgent);
 
-  return rv;
+  if (!aWindow || !aURI) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(aWindow->GetDocShell());
+
+  nsCOMPtr<nsISiteSpecificUserAgent> siteSpecificUA =
+    do_GetService("@mozilla.org/dom/site-specific-user-agent;1");
+  if (!siteSpecificUA) {
+    return NS_OK;
+  }
+
+  return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
 }
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -22,16 +22,17 @@
 class nsPluginArray;
 class nsMimeTypeArray;
 class nsPIDOMWindow;
 class nsIDOMNavigatorSystemMessages;
 class nsDOMCameraManager;
 class nsDOMDeviceStorage;
 class nsIDOMBlob;
 class nsIPrincipal;
+class nsIURI;
 
 namespace mozilla {
 namespace dom {
 class Geolocation;
 class systemMessageCallback;
 struct MediaStreamConstraints;
 class WakeLock;
 class ArrayBufferViewOrBlobOrStringOrFormData;
@@ -162,16 +163,21 @@ public:
   static void AppName(nsAString& aAppName, bool aUsePrefOverriddenValue);
 
   static nsresult GetPlatform(nsAString& aPlatform,
                               bool aUsePrefOverriddenValue);
 
   static nsresult GetAppVersion(nsAString& aAppVersion,
                                 bool aUsePrefOverriddenValue);
 
+  static nsresult GetUserAgent(nsPIDOMWindow* aWindow,
+                               nsIURI* aURI,
+                               bool aIsCallerChrome,
+                               nsAString& aUserAgent);
+
   already_AddRefed<Promise> GetDataStores(const nsAString& aName,
                                           const nsAString& aOwner,
                                           ErrorResult& aRv);
 
   // Feature Detection API
   already_AddRefed<Promise> GetFeature(const nsAString& aName,
                                        ErrorResult& aRv);
 
@@ -351,11 +357,9 @@ private:
   // we'd need to figure out exactly how to trace that, and that seems to be
   // rocket science.  :(
   nsInterfaceHashtable<nsStringHashKey, nsISupports> mCachedResolveResults;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-nsresult NS_GetNavigatorUserAgent(nsAString& aUserAgent);
-
 #endif // mozilla_dom_Navigator_h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11506,16 +11506,17 @@ nsGlobalWindow::Observe(nsISupports* aSu
     // The user preferred languages have changed, we need to fire an event on
     // Window object and invalidate the cache for navigator.languages. It is
     // done for every change which can be a waste of cycles but those should be
     // fairly rare.
     // We MUST invalidate navigator.languages before sending the event in the
     // very likely situation where an event handler will try to read its value.
 
     if (mNavigator) {
+      NavigatorBinding::ClearCachedLanguageValue(mNavigator);
       NavigatorBinding::ClearCachedLanguagesValue(mNavigator);
     }
 
     nsCOMPtr<nsIDOMEvent> event;
     NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
     nsresult rv = event->InitEvent(NS_LITERAL_STRING("languagechange"), false, false);
     NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -82,8 +82,9 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_urlutils_stringify.html]
 [test_window_constructor.html]
 [test_window_cross_origin_props.html]
 [test_window_enumeration.html]
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_window_named_frame_enumeration.html]
 [test_writable-replaceable.html]
+[test_navigatorPrefOverride.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_navigatorPrefOverride.html
@@ -0,0 +1,54 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for navigator property override</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  ok(navigator.appName, "This is used just to populate the cache");
+  ok(navigator.appVersion, "This is used just to populate the cache");
+
+  // B2G could have an empty platform.
+  info(navigator.platform);
+
+  ok(navigator.userAgent, "This is used just to populate the cache");
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["general.appname.override", "appName overridden"],
+    ["general.appversion.override", "appVersion overridden"],
+    ["general.platform.override", "platform overridden"],
+    ["general.useragent.override", "userAgent overridden"],
+    ]},
+    function() {
+      var ifr = document.createElement('IFRAME');
+      ifr.src = "about:blank";
+
+      ifr.addEventListener('load', function() {
+        var nav = ifr.contentWindow.navigator;
+        isnot(navigator.appName, nav.appName, "appName should match");
+        isnot(navigator.appVersion, nav.appVersion, "appVersion should match");
+        isnot(navigator.platform, nav.platform, "platform should match");
+        isnot(navigator.userAgent, nav.userAgent, "userAgent should match");
+        SimpleTest.finish();
+      }, false);
+
+      document.getElementById('content').appendChild(ifr);
+    }
+  );
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -28,35 +28,40 @@ Navigator implements NavigatorLanguage;
 Navigator implements NavigatorOnLine;
 Navigator implements NavigatorContentUtils;
 Navigator implements NavigatorStorageUtils;
 Navigator implements NavigatorFeatures;
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorID {
   // WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla").
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString appCodeName; // constant "Mozilla"
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString appName;
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString appVersion;
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString platform;
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString userAgent;
-  [Constant]
+  [Constant, Cached]
   readonly attribute DOMString product; // constant "Gecko"
 
   // Everyone but WebKit/Blink supports this.  See bug 679971.
   boolean taintEnabled(); // constant false
 };
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorLanguage {
+
+  // These 2 values are cached. They are updated when pref
+  // intl.accept_languages is changed.
+
+  [Pure, Cached]
   readonly attribute DOMString? language;
   [Pure, Cached, Frozen]
   readonly attribute sequence<DOMString> languages;
 };
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorOnLine {
   readonly attribute boolean onLine;
--- a/dom/workers/Navigator.cpp
+++ b/dom/workers/Navigator.cpp
@@ -10,16 +10,18 @@
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/WorkerNavigatorBinding.h"
 
 #include "Navigator.h"
 #include "nsProxyRelease.h"
 #include "RuntimeService.h"
 
+#include "nsIDocument.h"
+
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 BEGIN_WORKERS_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerNavigator)
 
@@ -320,9 +322,64 @@ WorkerNavigator::GetPlatform(nsString& a
   if (!mProperties.mPlatformOverridden.IsEmpty() &&
       !workerPrivate->UsesSystemPrincipal()) {
     aPlatform = mProperties.mPlatformOverridden;
   } else {
     aPlatform = mProperties.mPlatform;
   }
 }
 
+namespace {
+
+class GetUserAgentRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+  nsString& mUA;
+
+public:
+  GetUserAgentRunnable(WorkerPrivate* aWorkerPrivate, nsString& aUA)
+    : WorkerMainThreadRunnable(aWorkerPrivate)
+    , mUA(aUA)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  virtual bool MainThreadRun() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
+    nsCOMPtr<nsIURI> uri;
+    if (window && window->GetDocShell()) {
+      nsIDocument* doc = window->GetExtantDoc();
+      if (doc) {
+        doc->NodePrincipal()->GetURI(getter_AddRefs(uri));
+      }
+    }
+
+    bool isCallerChrome = mWorkerPrivate->UsesSystemPrincipal();
+    nsresult rv = dom::Navigator::GetUserAgent(window, uri,
+                                               isCallerChrome, mUA);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to retrieve user-agent from the worker thread.");
+    }
+
+    return true;
+  }
+};
+
+} // anonymous namespace
+
+void
+WorkerNavigator::GetUserAgent(nsString& aUserAgent) const
+{
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+
+  nsRefPtr<GetUserAgentRunnable> runnable =
+    new GetUserAgentRunnable(workerPrivate, aUserAgent);
+
+  if (!runnable->Dispatch(workerPrivate->GetJSContext())) {
+    JS_ReportPendingException(workerPrivate->GetJSContext());
+  }
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/Navigator.h
+++ b/dom/workers/Navigator.h
@@ -88,20 +88,17 @@ public:
     }
   }
 
   void GetLanguages(nsTArray<nsString>& aLanguages) const
   {
     aLanguages = mProperties.mLanguages;
   }
 
-  void GetUserAgent(nsString& aUserAgent) const
-  {
-    aUserAgent = mProperties.mUserAgent;
-  }
+  void GetUserAgent(nsString& aUserAgent) const;
 
   bool OnLine() const
   {
     return mOnline;
   }
 
   // Worker thread only!
   void SetOnLine(bool aOnline)
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -1510,18 +1510,17 @@ RuntimeService::RegisterWorker(JSContext
   }
   else {
     if (!mNavigatorPropertiesLoaded) {
       Navigator::AppName(mNavigatorProperties.mAppName,
                          false /* aUsePrefOverriddenValue */);
       if (NS_FAILED(Navigator::GetAppVersion(mNavigatorProperties.mAppVersion,
                                              false /* aUsePrefOverriddenValue */)) ||
           NS_FAILED(Navigator::GetPlatform(mNavigatorProperties.mPlatform,
-                                           false /* aUsePrefOverriddenValue */)) ||
-          NS_FAILED(NS_GetNavigatorUserAgent(mNavigatorProperties.mUserAgent))) {
+                                           false /* aUsePrefOverriddenValue */))) {
         JS_ReportError(aCx, "Failed to load navigator strings!");
         UnregisterWorker(aCx, aWorkerPrivate);
         return false;
       }
 
       // The navigator overridden properties should have already been read.
 
       Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages);
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -105,17 +105,16 @@ public:
   struct NavigatorProperties
   {
     nsString mAppName;
     nsString mAppNameOverridden;
     nsString mAppVersion;
     nsString mAppVersionOverridden;
     nsString mPlatform;
     nsString mPlatformOverridden;
-    nsString mUserAgent;
     nsTArray<nsString> mLanguages;
   };
 
 private:
   NavigatorProperties mNavigatorProperties;
 
   // True when the observer service holds a reference to this object.
   bool mObserved;
--- a/dom/workers/test/test_bug1062920.html
+++ b/dom/workers/test/test_bug1062920.html
@@ -14,31 +14,40 @@
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
 
   function checkValues() {
     var worker = new Worker("bug1062920_worker.js");
 
     worker.onmessage = function(event) {
-      is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match");
-      is(event.data.appName, navigator.appName, "appName should match");
-      is(event.data.appVersion, navigator.appVersion, "appVersion should match");
-      is(event.data.platform, navigator.platform, "platform should match");
-      is(event.data.userAgent, navigator.userAgent, "userAgent should match");
-      is(event.data.product, navigator.product, "product should match");
-      runTests();
+      var ifr = document.createElement('IFRAME');
+      ifr.src = "about:blank";
+
+      ifr.addEventListener('load', function() {
+        var nav = ifr.contentWindow.navigator;
+        is(event.data.appCodeName, nav.appCodeName, "appCodeName should match");
+        is(event.data.appName, nav.appName, "appName should match");
+        is(event.data.appVersion, nav.appVersion, "appVersion should match");
+        is(event.data.platform, nav.platform, "platform should match");
+        is(event.data.userAgent, nav.userAgent, "userAgent should match");
+        is(event.data.product, nav.product, "product should match");
+        runTests();
+      }, false);
+
+      document.getElementById('content').appendChild(ifr);
     };
   }
 
   function replaceAndCheckValues() {
     SpecialPowers.pushPrefEnv({"set": [
       ["general.appname.override", "appName overridden"],
       ["general.appversion.override", "appVersion overridden"],
-      ["general.platform.override", "platform overridden"]
+      ["general.platform.override", "platform overridden"],
+      ["general.useragent.override", "userAgent overridden"]
     ]}, checkValues);
   }
 
   var tests = [
     checkValues,
     replaceAndCheckValues
   ];
 
--- a/dom/workers/test/test_bug1062920.xul
+++ b/dom/workers/test/test_bug1062920.xul
@@ -30,23 +30,24 @@
       runTests();
     };
   }
 
   function replaceAndCheckValues() {
     SpecialPowers.pushPrefEnv({"set": [
       ["general.appname.override", "appName overridden"],
       ["general.appversion.override", "appVersion overridden"],
-      ["general.platform.override", "platform overridden"]
+      ["general.platform.override", "platform overridden"],
+      ["general.useragent.override", "userAgent overridden"]
     ]}, checkValues);
   }
 
   var tests = [
-    checkValues,
-    replaceAndCheckValues
+    replaceAndCheckValues,
+    checkValues
   ];
 
   function runTests() {
     if (tests.length == 0) {
       SimpleTest.finish();
       return;
     }
 
--- a/netwerk/test/mochitests/test_user_agent_overrides.html
+++ b/netwerk/test/mochitests/test_user_agent_overrides.html
@@ -63,43 +63,63 @@ function testUA(options, callback) {
   }
   test_hosts.forEach(function (test_host) {
     test_ua.push(getUA(test_host));
   });
   // set the override pref to override the UA
   SpecialPowers.pushPrefEnv({
     set: [[PREF_OVERRIDES_BRANCH + domain, override]],
   }, function () {
-    // check that the UA has changed after pref change
-    if (overrideNavigator) {
-      is(navigator.userAgent, expected,
-        'Navigator UA not overridden at step ' + (++step));
-    } else {
-      is(navigator.userAgent, DEFAULT_UA,
-        'Navigator UA should not be overridden at step ' + (++step));
-    }
-    test_hosts.forEach(function (test_host) {
-      is(getUA(test_host), expected,
-        'Header UA not overridden at step ' + (++step));
-    });
-    // clear the override pref to undo overriding the UA
-    SpecialPowers.pushPrefEnv({
-      clear: [[PREF_OVERRIDES_BRANCH + domain]],
-    }, function () {
-      // check that the UA has changed back
+    var ifr = document.createElement('IFRAME');
+    ifr.src = "about:blank";
+
+    ifr.addEventListener('load', function() {
+      var nav = ifr.contentWindow.navigator;
+
+      // check that the UA has changed after pref change
       if (overrideNavigator) {
-        is(navigator.userAgent, navigator_ua,
-          'Navigator UA not restored at step ' + (++step));
+        is(nav.userAgent, expected,
+          'Navigator UA not overridden at step ' + (++step));
+      } else {
+        is(nav.userAgent, DEFAULT_UA,
+          'Navigator UA should not be overridden at step ' + (++step));
       }
-      test_hosts.forEach(function (test_host, i) {
-        is(getUA(test_host), test_ua[i],
-          'Header UA not restored at step ' + (++step));
+
+      test_hosts.forEach(function (test_host) {
+        is(getUA(test_host), expected,
+          'Header UA not overridden at step ' + (++step));
       });
-      callback();
-    });
+
+      // clear the override pref to undo overriding the UA
+      SpecialPowers.pushPrefEnv({
+        clear: [[PREF_OVERRIDES_BRANCH + domain]],
+      }, function () {
+        var ifr = document.createElement('IFRAME');
+        ifr.src = "about:blank";
+
+        ifr.addEventListener('load', function() {
+          var nav = ifr.contentWindow.navigator;
+
+          // check that the UA has changed back
+          if (overrideNavigator) {
+            is(nav.userAgent, navigator_ua,
+              'Navigator UA not restored at step ' + (++step));
+          }
+          test_hosts.forEach(function (test_host, i) {
+            is(getUA(test_host), test_ua[i],
+              'Header UA not restored at step ' + (++step));
+          });
+          callback();
+        });
+
+        document.getElementById('content').appendChild(ifr);
+      });
+    }, false);
+
+    document.getElementById('content').appendChild(ifr);
   });
 }
 
 
 var step = 0; // for logging
 var tests = [
   // should override both header and navigator.userAgent
   {
--- a/netwerk/test/mochitests/test_user_agent_updates.html
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -116,50 +116,66 @@ function testDownload(callback) {
   SpecialPowers.pushPrefEnv({
     set: [
       [PREF_UPDATES_ENABLED, true],
       [PREF_UPDATES_URL, url],
       [PREF_UPDATES_TIMEOUT, 10000],
       [PREF_UPDATES_INTERVAL, 1] // 1 second interval
     ]
   }, function waitForUpdate() setTimeout(function () {
-    if (navigator.userAgent !== UA_OVERRIDE) {
-      waitForUpdate();
-      return;
-    }
-    info('Overrode navigator UA');
-    is(getUA(location.origin), UA_OVERRIDE, 'Header UA not overridden');
+    var ifr = document.createElement('IFRAME');
+    ifr.src = "about:blank";
+
+    ifr.addEventListener('load', function() {
+      var nav = ifr.contentWindow.navigator;
+      if (nav.userAgent !== UA_OVERRIDE) {
+        waitForUpdate();
+        return;
+      }
+
+      info('Overrode navigator UA');
+      is(getUA(location.origin), UA_OVERRIDE, 'Header UA not overridden');
 
-    var updateTime = parseInt(getUA('http://example.org'));
-    ok(startTime <= updateTime, 'Update was before start time');
-    ok(updateTime <= Date.now(), 'Update was after present time');
+      var updateTime = parseInt(getUA('http://example.org'));
+      ok(startTime <= updateTime, 'Update was before start time');
+      ok(updateTime <= Date.now(), 'Update was after present time');
 
-    OVERRIDES.forEach(function (val) {
-      val.expected && is(getUA(val.host), val.expected,
-        'Incorrect URL parameter: ' + val.override);
-    });
-    callback();
+      OVERRIDES.forEach(function (val) {
+        val.expected && is(getUA(val.host), val.expected,
+          'Incorrect URL parameter: ' + val.override);
+      });
+      callback();
+    }, false);
+
+    document.getElementById('content').appendChild(ifr);
   }, 100));
 }
 
 function testBadUpdate(callback) {
   var url = getServerURL() + 'invalid-json';
   var prevOverride = navigator.userAgent;
   SpecialPowers.pushPrefEnv({
     set: [
       [PREF_UPDATES_URL, url],
       [PREF_UPDATES_INTERVAL, 1] // 1 second interval
     ]
   }, function () setTimeout(function () {
-    // We want to make sure a bad update doesn't cancel out previous overrides.
-    // We do this by waiting for 5 seconds (assuming the update occurs within 5
-    // seconds), and check that the previous override hasn't changed.
-    is(navigator.userAgent, prevOverride,
-      'Invalid update deleted previous override');
-    callback();
+    var ifr = document.createElement('IFRAME');
+    ifr.src = "about:blank";
+
+    ifr.addEventListener('load', function() {
+      // We want to make sure a bad update doesn't cancel out previous
+      // overrides. We do this by waiting for 5 seconds (assuming the update
+      // occurs within 5 seconds), and check that the previous override hasn't
+      // changed.
+      is(navigator.userAgent, prevOverride,
+        'Invalid update deleted previous override');
+      callback();
+    }, false);
+    document.getElementById('content').appendChild(ifr);
   }, 5000));
 }
 
 function testProfileLoad(callback) {
   var file = FU.getFile(KEY_APPDIR, [FILE_UPDATES]).path;
   var encoder = SpecialPowers.wrap(new TextEncoder());
   var overrides = {};
   overrides[location.hostname] = UA_ALT_OVERRIDE;
@@ -174,23 +190,31 @@ function testProfileLoad(callback) {
     () => {
       SpecialPowers.pushPrefEnv({
         set: [[PREF_UPDATES_ENABLED, true]]
       }, function () {
         // initialize UserAgentOverrides.jsm and
         // UserAgentUpdates.jsm and load saved file
         UAO.init();
         (function waitForLoad() {
-          if (navigator.userAgent !== UA_ALT_OVERRIDE) {
-            setTimeout(waitForLoad, 100);
-            return;
-          }
-          is(getUA(location.origin), UA_ALT_OVERRIDE, 'Did not apply saved override');
-          saveFilePreviousSize = file.fileSize;
-          callback();
+          var ifr = document.createElement('IFRAME');
+          ifr.src = "about:blank";
+
+          ifr.addEventListener('load', function() {
+            var nav = ifr.contentWindow.navigator;
+            if (nav.userAgent !== UA_ALT_OVERRIDE) {
+              setTimeout(waitForLoad, 100);
+              return;
+            }
+            is(getUA(location.origin), UA_ALT_OVERRIDE, 'Did not apply saved override');
+            saveFilePreviousSize = file.fileSize;
+            callback();
+          }, false);
+
+          document.getElementById('content').appendChild(ifr);
         })();
       });
     },
     (reason) => {
       throw reason
     }
   );
 }
@@ -264,9 +288,8 @@ SpecialPowers.pushPrefEnv({
     )
   );
 });
 
 </script>
 </pre>
 </body>
 </html>
-