Merge mozilla-release to esr60. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 01 May 2018 20:51:06 -0400
changeset 790950 e71bb842a52f3e7170fa19417fdaf8b18b169e3e
parent 790949 a51793b8f67b1c4fab8572947fb829b0f1e77f2a (current diff)
parent 790462 5754290305728aa8a2ba8da7ef48dd4f2e5dd51f (diff)
child 790953 9b26d84db03451fa8b65acb29de30af1c706d4df
push id108656
push userbmo:jlorenzo@mozilla.com
push dateThu, 03 May 2018 08:03:49 +0000
reviewersmerge
milestone60.0
Merge mozilla-release to esr60. a=merge
testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.dmg
testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.tar.bz2
testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.zip
testing/mozbase/mozinstall/tests/test.py
--- a/.hgtags
+++ b/.hgtags
@@ -1033,8 +1033,10 @@ f2ac3383fb97a55d11a876f17189613a852a0077
 f2ac3383fb97a55d11a876f17189613a852a0077 FIREFOX_60_0b15_RELEASE
 f2ac3383fb97a55d11a876f17189613a852a0077 FENNEC_60_0b15_BUILD1
 f2ac3383fb97a55d11a876f17189613a852a0077 FENNEC_60_0b15_RELEASE
 42d311bd68789edd7fea323f6e2bc5bbbfd86760 FIREFOX_RELEASE_60_BASE
 6c1e7178ca23655d400b4895858354fb2b9fb509 FIREFOX_RELEASE_59_END
 96503bab218f493499d9355f30450553517e3ab4 FIREFOX_ESR_60_BASE
 96503bab218f493499d9355f30450553517e3ab4 FIREFOX_ESR_60_BASE
 3ce05c13f187c5627795195a08f0d605d03d8add FIREFOX_ESR_60_BASE
+6b51784853e47e091d213d421a19cb623af718f0 FIREFOX_59_0_3_BUILD1
+6b51784853e47e091d213d421a19cb623af718f0 FIREFOX_59_0_3_RELEASE
--- a/browser/components/resistfingerprinting/test/browser/browser_navigator.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_navigator.js
@@ -114,17 +114,17 @@ async function testWorkerNavigator() {
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({"set":
     [["privacy.resistFingerprinting", true]]
   });
 
   let appVersion = parseInt(Services.appinfo.version);
-  let spoofedVersion = appVersion - ((appVersion - 3) % 7);
+  let spoofedVersion = appVersion - ((appVersion - 4) % 7);
   spoofedUserAgent = `Mozilla/5.0 (${SPOOFED_UA_OS[AppConstants.platform]}; rv:${spoofedVersion}.0) Gecko/20100101 Firefox/${spoofedVersion}.0`;
 });
 
 add_task(async function runNavigatorTest() {
   await testNavigator();
 });
 
 add_task(async function runWorkerNavigatorTest() {
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -1375,17 +1375,16 @@ class CssGridHighlighter extends AutoRef
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     linePos = Math.round(linePos);
     startPos = Math.round(startPos);
     endPos = Math.round(endPos);
 
     this.ctx.save();
     this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
-    this.ctx.beginPath();
     this.ctx.translate(offset - canvasX, offset - canvasY);
 
     let lineOptions = {
       matrix: this.currentMatrix
     };
 
     if (this.options.showInfiniteLines) {
       lineOptions.extendToBoundaries = [canvasX, canvasY, canvasX + CANVAS_SIZE,
--- a/devtools/server/actors/highlighters/utils/canvas.js
+++ b/devtools/server/actors/highlighters/utils/canvas.js
@@ -166,17 +166,16 @@ function drawLine(ctx, x1, y1, x2, y2, o
       y2 = options.extendToBoundaries[3];
       x2 = (p2[0] - p1[0]) * (y2 - p1[1]) / (p2[1] - p1[1]) + p1[0];
     }
   }
 
   ctx.beginPath();
   ctx.moveTo(Math.round(x1), Math.round(y1));
   ctx.lineTo(Math.round(x2), Math.round(y2));
-  ctx.closePath();
 }
 
 /**
  * Draws a rect to the context given and applies a transformation matrix if passed.
  * The coordinates are the start and end points of the rectangle's diagonal.
  *
  * @param  {CanvasRenderingContext2D} ctx
  *         The 2D canvas context.
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
@@ -38,16 +38,19 @@ import java.util.List;
 /**
  * An activity-lifecycle delegate for uploading the core ping.
  */
 public class TelemetryCorePingDelegate extends BrowserAppDelegateWithReference
         implements SearchEngineManager.SearchEngineCallback, AttributionHelperListener {
     private static final String LOGTAG = StringUtils.safeSubstring(
             "Gecko" + TelemetryCorePingDelegate.class.getSimpleName(), 0, 23);
 
+    private static final String TELEMETRY_EXTRA_ONRESUME_ALREADY_CALLED = "onResumeAlreadyCalled";
+    private static final String TELEMETRY_EXTRA_ONPAUSE_CALLED_BEFORE_ONRESUME = "onPauseCalledBeforeOnResume";
+
     private boolean isOnResumeCalled = false;
     private TelemetryDispatcher telemetryDispatcher; // lazy
     private final SessionMeasurements sessionMeasurements = new SessionMeasurements();
 
     @Override
     public void onStart(final BrowserApp browserApp) {
         TelemetryPreferences.initPreferenceObserver(browserApp, GeckoThread.getActiveProfile().getName());
 
@@ -84,24 +87,28 @@ public class TelemetryCorePingDelegate e
 
     private void uploadPing(final BrowserApp browserApp) {
         final SearchEngineManager searchEngineManager = browserApp.getSearchEngineManager();
         searchEngineManager.getEngine(this);
     }
 
     @Override
     public void onResume(BrowserApp browserApp) {
+        if (isOnResumeCalled) {
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SYSTEM, TELEMETRY_EXTRA_ONRESUME_ALREADY_CALLED);
+            return;
+        }
         isOnResumeCalled = true;
         sessionMeasurements.recordSessionStart();
     }
 
     @Override
     public void onPause(BrowserApp browserApp) {
         if (!isOnResumeCalled) {
-            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SYSTEM, "onPauseCalledBeforeOnResume");
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SYSTEM, TELEMETRY_EXTRA_ONPAUSE_CALLED_BEFORE_ONRESUME);
             return;
         }
         isOnResumeCalled = false;
         // onStart/onStop is ideal over onResume/onPause. However, onStop is not guaranteed to be called and
         // dealing with that possibility adds a lot of complexity that we don't want to handle at this point.
         sessionMeasurements.recordSessionEnd(browserApp);
     }
 
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -899,26 +899,38 @@ public:
     length = lastPageEnd - firstPage;
     uintptr_t start = reinterpret_cast<uintptr_t>(firstPage);
     uintptr_t end;
 
     prot = getProt(start, &end);
     if (prot == -1 || (start + length) > end)
       MOZ_CRASH();
 
-    if (prot & PROT_WRITE)
+    if (prot & PROT_WRITE) {
+      success = true;
       return;
+    }
 
     page = firstPage;
-    mprotect(page, length, prot | PROT_WRITE);
+    int ret = mprotect(page, length, prot | PROT_WRITE);
+    success = ret == 0;
+    if (!success) {
+      ERROR("mprotect(%p, %zu, %d) = %d (errno=%d; %s)",
+            page, length, prot | PROT_WRITE, ret,
+            errno, strerror(errno));
+    }
+  }
+
+  bool IsWritable() const {
+    return success;
   }
 
   ~EnsureWritable()
   {
-    if (page != MAP_FAILED) {
+    if (success && page != MAP_FAILED) {
       mprotect(page, length, prot);
 }
   }
 
 private:
   int getProt(uintptr_t addr, uintptr_t *end)
   {
     /* The interesting part of the /proc/self/maps format looks like:
@@ -948,16 +960,17 @@ private:
       return result;
     }
     return -1;
   }
 
   int prot;
   void *page;
   size_t length;
+  bool success;
 };
 
 /**
  * The system linker maintains a doubly linked list of library it loads
  * for use by the debugger. Unfortunately, it also uses the list pointers
  * in a lot of operations and adding our data in the list is likely to
  * trigger crashes when the linker tries to use data we don't provide or
  * that fall off the amount data we allocated. Fortunately, the linker only
@@ -971,54 +984,73 @@ private:
  * program executable, so the system linker should not be changing
  * r_debug::r_map.
  */
 void
 ElfLoader::DebuggerHelper::Add(ElfLoader::link_map *map)
 {
   if (!dbg->r_brk)
     return;
+
   dbg->r_state = r_debug::RT_ADD;
   dbg->r_brk();
-  map->l_prev = nullptr;
-  map->l_next = dbg->r_map;
+
   if (!firstAdded) {
-    firstAdded = map;
     /* When adding a library for the first time, r_map points to data
      * handled by the system linker, and that data may be read-only */
     EnsureWritable w(&dbg->r_map->l_prev);
+    if (!w.IsWritable()) {
+      dbg->r_state = r_debug::RT_CONSISTENT;
+      dbg->r_brk();
+      return;
+    }
+
+    firstAdded = map;
     dbg->r_map->l_prev = map;
   } else
     dbg->r_map->l_prev = map;
+
+  map->l_prev = nullptr;
+  map->l_next = dbg->r_map;
+
   dbg->r_map = map;
   dbg->r_state = r_debug::RT_CONSISTENT;
   dbg->r_brk();
 }
 
 void
 ElfLoader::DebuggerHelper::Remove(ElfLoader::link_map *map)
 {
   if (!dbg->r_brk)
     return;
+
   dbg->r_state = r_debug::RT_DELETE;
   dbg->r_brk();
+
+  if (map == firstAdded) {
+    /* When removing the first added library, its l_next is going to be
+     * data handled by the system linker, and that data may be read-only */
+    EnsureWritable w(&map->l_next->l_prev);
+    if (!w.IsWritable()) {
+      dbg->r_state = r_debug::RT_CONSISTENT;
+      dbg->r_brk();
+      return;
+    }
+
+    firstAdded = map->l_prev;
+    map->l_next->l_prev = map->l_prev;
+  } else if (map->l_next) {
+    map->l_next->l_prev = map->l_prev;
+  }
+
   if (dbg->r_map == map)
     dbg->r_map = map->l_next;
   else if (map->l_prev) {
     map->l_prev->l_next = map->l_next;
   }
-  if (map == firstAdded) {
-    firstAdded = map->l_prev;
-    /* When removing the first added library, its l_next is going to be
-     * data handled by the system linker, and that data may be read-only */
-    EnsureWritable w(&map->l_next->l_prev);
-    map->l_next->l_prev = map->l_prev;
-  } else if (map->l_next) {
-    map->l_next->l_prev = map->l_prev;
-  }
   dbg->r_state = r_debug::RT_CONSISTENT;
   dbg->r_brk();
 }
 
 #if defined(ANDROID) && defined(__NR_sigaction)
 /* As some system libraries may be calling signal() or sigaction() to
  * set a SIGSEGV handler, effectively breaking MappableSeekableZStream,
  * or worse, restore our SIGSEGV handler with wrong flags (which using
--- a/testing/mozbase/mozinstall/mozinstall/mozinstall.py
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -98,17 +98,16 @@ def get_binary(path, app_name):
 def install(src, dest):
     """Install a zip, exe, tar.gz, tar.bz2 or dmg file, and return the path of
     the installation folder.
 
     :param src: Path to the install file
     :param dest: Path to install to (to ensure we do not overwrite any existent
                  files the folder should not exist yet)
     """
-
     if not is_installer(src):
         msg = "{} is not a valid installer file".format(src)
         if '://' in src:
             try:
                 return _install_url(src, dest)
             except Exception:
                 exc, val, tb = sys.exc_info()
                 msg = "{} ({})".format(msg, val)
@@ -306,17 +305,17 @@ def _install_dmg(src, dest):
         # copytree() would fail if dest already exists.
         if os.path.exists(dest):
             raise InstallError('App bundle "%s" already exists.' % dest)
 
         shutil.copytree(mounted_path, dest, False)
 
     finally:
         if appDir:
-            subprocess.call('hdiutil detach %s -quiet' % appDir,
+            subprocess.call('hdiutil detach "%s" -quiet' % appDir,
                             shell=True)
 
     return dest
 
 
 def _install_exe(src, dest):
     """Run the MSI installer to silently install the application into the
     destination folder. Return the folder path.
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/conftest.py
@@ -0,0 +1,16 @@
+from __future__ import absolute_import
+
+import pytest
+
+
+@pytest.fixture
+def get_installer(request):
+    def _get_installer(extension):
+        """Get path to the installer for the specified extension."""
+        stub_dir = request.node.fspath.dirpath('installer_stubs')
+
+        # We had to remove firefox.exe since it is not valid for mozinstall 1.12 and higher
+        # Bug 1157352 - We should grab a firefox.exe from the build process or download it
+        return stub_dir.join('firefox.{}'.format(extension)).strpath
+
+    return _get_installer
rename from testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.dmg
rename to testing/mozbase/mozinstall/tests/installer_stubs/firefox.dmg
index f7f36f6318ace2e4adcbff5d1e2c1129987530f3..dd9c779dfa988b582555e85d43e1199529575722
GIT binary patch
literal 27309
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
bFaM2BeCSxfli;g=qYIn%YitireVzC}<~BW;
rename from testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.tar.bz2
rename to testing/mozbase/mozinstall/tests/installer_stubs/firefox.tar.bz2
rename from testing/mozbase/mozinstall/tests/Installer-Stubs/firefox.zip
rename to testing/mozbase/mozinstall/tests/installer_stubs/firefox.zip
--- a/testing/mozbase/mozinstall/tests/manifest.ini
+++ b/testing/mozbase/mozinstall/tests/manifest.ini
@@ -1,3 +1,7 @@
 [DEFAULT]
 subsuite = mozbase, os == "linux"
-[test.py]
+
+[test_binary.py]
+[test_install.py]
+[test_is_installer.py]
+[test_uninstall.py]
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_binary.py
@@ -0,0 +1,46 @@
+from __future__ import absolute_import
+
+import os
+
+import mozinfo
+import mozinstall
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+    mozinfo.isWin, reason='Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.')
+def test_get_binary(tmpdir, get_installer):
+    """Test to retrieve binary from install path."""
+    if mozinfo.isLinux:
+        installdir = mozinstall.install(get_installer('tar.bz2'), tmpdir.strpath)
+        binary = os.path.join(installdir, 'firefox')
+
+        assert mozinstall.get_binary(installdir, 'firefox') == binary
+
+    elif mozinfo.isWin:
+        installdir_exe = mozinstall.install(get_installer('exe'), tmpdir.join('exe').strpath)
+        binary_exe = os.path.join(installdir_exe, 'core', 'firefox.exe')
+
+        assert mozinstall.get_binary(installdir_exe, 'firefox') == binary_exe
+
+        installdir_zip = mozinstall.install(get_installer('zip'), tmpdir.join('zip').strpath)
+        binary_zip = os.path.join(installdir_zip, 'firefox.exe')
+
+        assert mozinstall.get_binary(installdir_zip, 'firefox') == binary_zip
+
+    elif mozinfo.isMac:
+        installdir = mozinstall.install(get_installer('dmg'), tmpdir.strpath)
+        binary = os.path.join(installdir, 'Contents', 'MacOS', 'firefox')
+
+        assert mozinstall.get_binary(installdir, 'firefox') == binary
+
+
+def test_get_binary_error(tmpdir):
+    """Test that an InvalidBinary error is raised."""
+    with pytest.raises(mozinstall.InvalidBinary):
+        mozinstall.get_binary(tmpdir.strpath, 'firefox')
+
+
+if __name__ == '__main__':
+    mozunit.main()
rename from testing/mozbase/mozinstall/tests/test.py
rename to testing/mozbase/mozinstall/tests/test_install.py
--- a/testing/mozbase/mozinstall/tests/test.py
+++ b/testing/mozbase/mozinstall/tests/test_install.py
@@ -1,178 +1,81 @@
-#!/usr/bin/env python
+from __future__ import absolute_import
 
-# 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/.
-
-from __future__ import absolute_import, print_function
+import subprocess
 
 import mozinfo
 import mozinstall
-import mozfile
-import os
-import tempfile
-import unittest
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+    mozinfo.isWin, reason='Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.')
+def test_is_installer(request, get_installer):
+    """Test that we can identify a correct installer."""
+    if mozinfo.isLinux:
+        assert mozinstall.is_installer(get_installer('tar.bz2'))
+
+    if mozinfo.isWin:
+        # test zip installer
+        assert mozinstall.is_installer(get_installer('zip'))
 
-import mozunit
+        # test exe installer
+        assert mozinstall.is_installer(get_installer('exe'))
 
-# Store file location at load time
-here = os.path.dirname(os.path.abspath(__file__))
+        try:
+            # test stub browser file
+            # without pefile on the system this test will fail
+            import pefile  # noqa
+            stub_exe = request.node.fspath.dirpath('build_stub').join('firefox.exe').strpath
+            assert not mozinstall.is_installer(stub_exe)
+        except ImportError:
+            pass
+
+    if mozinfo.isMac:
+        assert mozinstall.is_installer(get_installer('dmg'))
 
 
-class TestMozInstall(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        """ Setting up stub installers """
-        cls.dmg = os.path.join(here, 'Installer-Stubs', 'firefox.dmg')
-        # XXX: We have removed firefox.exe since it is not valid for mozinstall 1.12 and higher
-        # Bug 1157352 - We should grab a firefox.exe from the build process or download it
-        cls.exe = os.path.join(here, 'Installer-Stubs', 'firefox.exe')
-        cls.zipfile = os.path.join(here, 'Installer-Stubs', 'firefox.zip')
-        cls.bz2 = os.path.join(here, 'Installer-Stubs', 'firefox.tar.bz2')
-
-    def setUp(self):
-        self.tempdir = tempfile.mkdtemp()
-
-    def tearDown(self):
-        mozfile.rmtree(self.tempdir)
-
-    @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
-                     "for mozinstall 1.12 and higher.")
-    def test_get_binary(self):
-        """ Test mozinstall's get_binary method """
-
-        if mozinfo.isLinux:
-            installdir = mozinstall.install(self.bz2, self.tempdir)
-            binary = os.path.join(installdir, 'firefox')
-            self.assertEqual(binary, mozinstall.get_binary(installdir, 'firefox'))
+def test_invalid_source_error(get_installer):
+    """Test that InvalidSource error is raised with an incorrect installer."""
+    if mozinfo.isLinux:
+        with pytest.raises(mozinstall.InvalidSource):
+            mozinstall.install(get_installer('dmg'), 'firefox')
 
-        elif mozinfo.isWin:
-            installdir_exe = mozinstall.install(self.exe,
-                                                os.path.join(self.tempdir, 'exe'))
-            binary_exe = os.path.join(installdir_exe, 'core', 'firefox.exe')
-            self.assertEqual(binary_exe, mozinstall.get_binary(installdir_exe,
-                                                               'firefox'))
-
-            installdir_zip = mozinstall.install(self.zipfile,
-                                                os.path.join(self.tempdir, 'zip'))
-            binary_zip = os.path.join(installdir_zip, 'firefox.exe')
-            self.assertEqual(binary_zip, mozinstall.get_binary(installdir_zip,
-                                                               'firefox'))
-
-        elif mozinfo.isMac:
-            installdir = mozinstall.install(self.dmg, self.tempdir)
-            binary = os.path.join(installdir, 'Contents', 'MacOS', 'firefox')
-            self.assertEqual(binary, mozinstall.get_binary(installdir, 'firefox'))
+    elif mozinfo.isWin:
+        with pytest.raises(mozinstall.InvalidSource):
+            mozinstall.install(get_installer('tar.bz2'), 'firefox')
 
-    def test_get_binary_error(self):
-        """ Test an InvalidBinary error is raised """
-
-        tempdir_empty = tempfile.mkdtemp()
-        self.assertRaises(mozinstall.InvalidBinary, mozinstall.get_binary,
-                          tempdir_empty, 'firefox')
-        mozfile.rmtree(tempdir_empty)
+    elif mozinfo.isMac:
+        with pytest.raises(mozinstall.InvalidSource):
+            mozinstall.install(get_installer('tar.bz2'), 'firefox')
 
-    @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
-                     "for mozinstall 1.12 and higher.")
-    def test_is_installer(self):
-        """ Test we can identify a correct installer """
+    # Test an invalid url handler
+    with pytest.raises(mozinstall.InvalidSource):
+        mozinstall.install('file://foo.bar', 'firefox')
 
-        if mozinfo.isLinux:
-            self.assertTrue(mozinstall.is_installer(self.bz2))
-
-        if mozinfo.isWin:
-            # test zip installer
-            self.assertTrue(mozinstall.is_installer(self.zipfile))
-
-            # test exe installer
-            self.assertTrue(mozinstall.is_installer(self.exe))
 
-            try:
-                # test stub browser file
-                # without pefile on the system this test will fail
-                import pefile  # noqa
-                stub_exe = os.path.join(here, 'build_stub', 'firefox.exe')
-                self.assertFalse(mozinstall.is_installer(stub_exe))
-            except ImportError:
-                pass
-
-        if mozinfo.isMac:
-            self.assertTrue(mozinstall.is_installer(self.dmg))
-
-    def test_invalid_source_error(self):
-        """ Test InvalidSource error is raised with an incorrect installer """
-
-        if mozinfo.isLinux:
-            self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
-                              self.dmg, 'firefox')
-
-        elif mozinfo.isWin:
-            self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
-                              self.bz2, 'firefox')
-
-        elif mozinfo.isMac:
-            self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
-                              self.bz2, 'firefox')
-
-        # Test an invalid url handler
-        self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
-                          'file://foo.bar', 'firefox')
-
-    @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
-                     "for mozinstall 1.12 and higher.")
-    def test_install(self):
-        """ Test mozinstall's install capability """
-
-        if mozinfo.isLinux:
-            installdir = mozinstall.install(self.bz2, self.tempdir)
-            self.assertEqual(os.path.join(self.tempdir, 'firefox'), installdir)
+@pytest.mark.skipif(
+    mozinfo.isWin, reason='Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.')
+def test_install(tmpdir, get_installer):
+    """Test to install an installer."""
+    if mozinfo.isLinux:
+        installdir = mozinstall.install(get_installer('tar.bz2'), tmpdir.strpath)
+        assert installdir == tmpdir.join('firefox').strpath
 
-        elif mozinfo.isWin:
-            installdir_exe = mozinstall.install(self.exe,
-                                                os.path.join(self.tempdir, 'exe'))
-            self.assertEqual(os.path.join(self.tempdir, 'exe', 'firefox'),
-                             installdir_exe)
-
-            installdir_zip = mozinstall.install(self.zipfile,
-                                                os.path.join(self.tempdir, 'zip'))
-            self.assertEqual(os.path.join(self.tempdir, 'zip', 'firefox'),
-                             installdir_zip)
+    elif mozinfo.isWin:
+        installdir_exe = mozinstall.install(get_installer('exe'), tmpdir.join('exe').strpath)
+        assert installdir_exe == tmpdir.join('exe', 'firefox').strpath
 
-        elif mozinfo.isMac:
-            installdir = mozinstall.install(self.dmg, self.tempdir)
-            self.assertEqual(os.path.join(os.path.realpath(self.tempdir),
-                                          'FirefoxStub.app'), installdir)
-
-    @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
-                     "for mozinstall 1.12 and higher.")
-    def test_uninstall(self):
-        """ Test mozinstall's uninstall capabilites """
-        # Uninstall after installing
+        installdir_zip = mozinstall.install(get_installer('zip'), tmpdir.join('zip').strpath)
+        assert installdir_zip == tmpdir.join('zip', 'firefox').strpath
 
-        if mozinfo.isLinux:
-            installdir = mozinstall.install(self.bz2, self.tempdir)
-            mozinstall.uninstall(installdir)
-            self.assertFalse(os.path.exists(installdir))
+    elif mozinfo.isMac:
+        installdir = mozinstall.install(get_installer('dmg'), tmpdir.strpath)
+        assert installdir == tmpdir.realpath().join('Firefox Stub.app').strpath
 
-        elif mozinfo.isWin:
-            # Exe installer for Windows
-            installdir_exe = mozinstall.install(self.exe,
-                                                os.path.join(self.tempdir, 'exe'))
-            mozinstall.uninstall(installdir_exe)
-            self.assertFalse(os.path.exists(installdir_exe))
-
-            # Zip installer for Windows
-            installdir_zip = mozinstall.install(self.zipfile,
-                                                os.path.join(self.tempdir, 'zip'))
-            mozinstall.uninstall(installdir_zip)
-            self.assertFalse(os.path.exists(installdir_zip))
-
-        elif mozinfo.isMac:
-            installdir = mozinstall.install(self.dmg, self.tempdir)
-            mozinstall.uninstall(installdir)
-            self.assertFalse(os.path.exists(installdir))
+        mounted_images = subprocess.check_output(['hdiutil', 'info'])
+        assert get_installer('dmg') not in mounted_images
 
 
 if __name__ == '__main__':
     mozunit.main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_is_installer.py
@@ -0,0 +1,37 @@
+from __future__ import absolute_import
+
+import mozinfo
+import mozinstall
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+    mozinfo.isWin, reason='Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.')
+def test_is_installer(request, get_installer):
+    """Test that we can identify a correct installer."""
+    if mozinfo.isLinux:
+        assert mozinstall.is_installer(get_installer('tar.bz2'))
+
+    if mozinfo.isWin:
+        # test zip installer
+        assert mozinstall.is_installer(get_installer('zip'))
+
+        # test exe installer
+        assert mozinstall.is_installer(get_installer('exe'))
+
+        try:
+            # test stub browser file
+            # without pefile on the system this test will fail
+            import pefile  # noqa
+            stub_exe = request.node.fspath.dirpath('build_stub').join('firefox.exe').strpath
+            assert not mozinstall.is_installer(stub_exe)
+        except ImportError:
+            pass
+
+    if mozinfo.isMac:
+        assert mozinstall.is_installer(get_installer('dmg'))
+
+
+if __name__ == '__main__':
+    mozunit.main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_uninstall.py
@@ -0,0 +1,35 @@
+from __future__ import absolute_import
+
+import mozinfo
+import mozinstall
+import mozunit
+import py
+import pytest
+
+
+@pytest.mark.skipif(
+    mozinfo.isWin, reason='Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.')
+def test_uninstall(tmpdir, get_installer):
+    """Test to uninstall an installed binary."""
+    if mozinfo.isLinux:
+        installdir = mozinstall.install(get_installer('tar.bz2'), tmpdir.strpath)
+        mozinstall.uninstall(installdir)
+        assert not py.path.local(installdir).check()
+
+    elif mozinfo.isWin:
+        installdir_exe = mozinstall.install(get_installer('exe'), tmpdir.join('exe').strpath)
+        mozinstall.uninstall(installdir_exe)
+        assert not py.path.local(installdir).check()
+
+        installdir_zip = mozinstall.install(get_installer('zip'), tmpdir.join('zip').strpath)
+        mozinstall.uninstall(installdir_zip)
+        assert not py.path.local(installdir).check()
+
+    elif mozinfo.isMac:
+        installdir = mozinstall.install(get_installer('dmg'), tmpdir.strpath)
+        mozinstall.uninstall(installdir)
+        assert not py.path.local(installdir).check()
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -684,36 +684,29 @@ nsRFPService::GetSpoofedUserAgent(nsACSt
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The browser version will be spoofed as the last ESR version.
   // By doing so, the anonymity group will cover more versions instead of one
   // version.
   uint32_t firefoxVersion = appVersion.ToInteger(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Starting from Firefox 10, Firefox ESR was released once every seven
-  // Firefox releases, e.g. Firefox 10, 17, 24, 31, and so on.
-  // We infer the last and closest ESR version based on this rule.
-  nsCOMPtr<nsIXULRuntime> runtime =
-    do_GetService("@mozilla.org/xre/runtime;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoCString updateChannel;
-  rv = runtime->GetDefaultUpdateChannel(updateChannel);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // If we are running in Firefox ESR, determine whether the formula of ESR
   // version has changed.  Once changed, we must update the formula in this
   // function.
-  if (updateChannel.EqualsLiteral("esr")) {
-    MOZ_ASSERT(((firefoxVersion % 7) == 3),
+  if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "esr")) {
+    MOZ_ASSERT(((firefoxVersion % 7) == 4),
       "Please udpate ESR version formula in nsRFPService.cpp");
   }
 
-  uint32_t spoofedVersion = firefoxVersion - ((firefoxVersion - 3) % 7);
+  // Starting from Firefox 10, Firefox ESR was released once every seven
+  // Firefox releases, e.g. Firefox 10, 17, 24, 31, and so on.
+  // Except we used 60 as an ESR instead of 59.
+  // We infer the last and closest ESR version based on this rule.
+  uint32_t spoofedVersion = firefoxVersion - ((firefoxVersion - 4) % 7);
   userAgent.Assign(nsPrintfCString(
     "Mozilla/5.0 (%s; rv:%d.0) Gecko/%s Firefox/%d.0",
     SPOOFED_UA_OS, spoofedVersion, LEGACY_BUILD_ID, spoofedVersion));
 
   return rv;
 }
 
 nsresult