Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 29 Jun 2016 16:22:44 +0200
changeset 343131 0c56fe904602ed999c82f46a8dde0bfac2e2ae9b
parent 343130 5126bee4abb913d3fd495a52de11e0198bcb05d3 (current diff)
parent 343121 b69a5bbb5e40bd426e35222baa600b481e50d265 (diff)
child 343132 ed7d7a895962ca78cdad5200b052e73850c4b807
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.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
Merge mozilla-central to autoland
devtools/client/responsive.html/devices.js
dom/base/URL.cpp
dom/base/URL.h
dom/base/URLSearchParams.cpp
dom/base/URLSearchParams.h
dom/base/test/file_url.jsm
dom/base/test/test_unknown_url_origin.html
dom/base/test/test_url.html
dom/base/test/test_url.xul
dom/base/test/test_urlExceptions.html
dom/base/test/test_urlSearchParams.html
dom/base/test/test_urlSearchParams_utf8.html
dom/base/test/test_url_data.html
dom/base/test/test_url_empty_port.html
dom/base/test/test_url_malformedHost.html
dom/base/test/test_urlutils_stringify.html
dom/workers/URL.cpp
dom/workers/URL.h
dom/workers/test/file_url.jsm
dom/workers/test/jsm_url_worker.js
dom/workers/test/test_bug883784.jsm
dom/workers/test/test_bug883784.xul
dom/workers/test/test_url.html
dom/workers/test/test_url.xul
dom/workers/test/test_urlApi.html
dom/workers/test/test_urlSearchParams.html
dom/workers/test/test_url_exceptions.html
dom/workers/test/urlApi_worker.js
dom/workers/test/urlSearchParams_worker.js
dom/workers/test/url_exceptions_worker.js
dom/workers/test/url_worker.js
js/src/vm/Monitor.cpp
layout/generic/nsFloatManager.cpp
taskcluster/taskgraph/types.py
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-003a.html.ini
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-003b.html.ini
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-003c.html.ini
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-007a.html.ini
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-007b.html.ini
testing/web-platform/meta/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters/dir-isolation-007c.html.ini
testing/web-platform/meta/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-another-bdi-2.html.ini
testing/web-platform/meta/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-following-2.html.ini
testing/web-platform/meta/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-letter-preceding-2.html.ini
testing/web-platform/meta/html/semantics/text-level-semantics/the-bdi-element/bdi-neutral-to-number-following-2.html.ini
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -27,17 +27,16 @@ builtin(include, build/autoconf/zlib.m4)
 builtin(include, build/autoconf/linux.m4)dnl
 builtin(include, build/autoconf/winsdk.m4)dnl
 builtin(include, build/autoconf/icu.m4)dnl
 builtin(include, build/autoconf/ffi.m4)dnl
 builtin(include, build/autoconf/clang-plugin.m4)dnl
 builtin(include, build/autoconf/alloc.m4)dnl
 builtin(include, build/autoconf/ios.m4)dnl
 builtin(include, build/autoconf/jemalloc.m4)dnl
-builtin(include, build/autoconf/rust.m4)dnl
 
 MOZ_PROG_CHECKMSYS()
 
 # Read the user's .mozconfig script.  We can't do this in
 # configure.in: autoconf puts the argument parsing code above anything
 # expanded from configure.in, and we need to get the configure options
 # from .mozconfig in place before that argument parsing code.
 MOZ_READ_MOZCONFIG(.)
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -210,27 +210,16 @@ int main(int argc, _CONST char* argv[])
   // If the b2g app is launched from adb shell, then the shell will wind
   // up being the process group controller. This means that we can't send
   // signals to the process group (useful for profiling).
   // We ignore the return value since setsid() fails if we're already the
   // process group controller (the normal situation).
   (void)setsid();
 #endif
 
-  int gotCounters;
-#if defined(XP_UNIX)
-  struct rusage initialRUsage;
-  gotCounters = !getrusage(RUSAGE_SELF, &initialRUsage);
-#elif defined(XP_WIN)
-  IO_COUNTERS ioCounters;
-  gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
-#else
-  #error "Unknown platform"  // having this here keeps cppcheck happy
-#endif
-
 #ifdef HAS_DLL_BLOCKLIST
   DllBlocklist_Initialize();
 #endif
 
   // B2G loader has already initialized Gecko so we can't initialize
   // it again here.
 #ifndef MOZ_B2G_LOADER
   // We do this because of data in bug 771745
@@ -246,42 +235,16 @@ int main(int argc, _CONST char* argv[])
 #endif // MOZ_B2G_LOADER
 
   rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
   if (NS_FAILED(rv)) {
     Output("Couldn't load XRE functions.\n");
     return 255;
   }
 
-  if (gotCounters) {
-#if defined(XP_WIN)
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS,
-                            int(ioCounters.ReadOperationCount));
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER,
-                            int(ioCounters.ReadTransferCount / 1024));
-    IO_COUNTERS newIoCounters;
-    if (GetProcessIoCounters(GetCurrentProcess(), &newIoCounters)) {
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_OPS,
-                              int(newIoCounters.ReadOperationCount - ioCounters.ReadOperationCount));
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_TRANSFER,
-                              int((newIoCounters.ReadTransferCount - ioCounters.ReadTransferCount) / 1024));
-    }
-#elif defined(XP_UNIX)
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_HARD_FAULTS,
-                            int(initialRUsage.ru_majflt));
-    struct rusage newRUsage;
-    if (!getrusage(RUSAGE_SELF, &newRUsage)) {
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_HARD_FAULTS,
-                              int(newRUsage.ru_majflt - initialRUsage.ru_majflt));
-    }
-#else
-  #error "Unknown platform"  // having this here keeps cppcheck happy
-#endif
-  }
-
   int result;
   {
     ScopedLogging log;
     char **_argv;
 
     /*
      * Duplicate argument vector to conform non-const argv of
      * do_main() since XRE_main() is very stupid with non-const argv.
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -357,27 +357,16 @@ int main(int argc, char* argv[], char* e
 #endif
 
   mozilla::TimeStamp start = mozilla::TimeStamp::Now();
 
 #ifdef XP_MACOSX
   TriggerQuirks();
 #endif
 
-  int gotCounters;
-#if defined(XP_UNIX)
-  struct rusage initialRUsage;
-  gotCounters = !getrusage(RUSAGE_SELF, &initialRUsage);
-#elif defined(XP_WIN)
-  IO_COUNTERS ioCounters;
-  gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
-#else
-  #error "Unknown platform"  // having this here keeps cppcheck happy
-#endif
-
   nsIFile *xreDirectory;
 
 #ifdef HAS_DLL_BLOCKLIST
   DllBlocklist_Initialize();
 
 #ifdef DEBUG
   // In order to be effective against AppInit DLLs, the blocklist must be
   // initialized before user32.dll is loaded into the process (bug 932100).
@@ -389,42 +378,16 @@ int main(int argc, char* argv[], char* e
 
   nsresult rv = InitXPCOMGlue(argv[0], &xreDirectory);
   if (NS_FAILED(rv)) {
     return 255;
   }
 
   XRE_StartupTimelineRecord(mozilla::StartupTimeline::START, start);
 
-  if (gotCounters) {
-#if defined(XP_WIN)
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS,
-                            int(ioCounters.ReadOperationCount));
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER,
-                            int(ioCounters.ReadTransferCount / 1024));
-    IO_COUNTERS newIoCounters;
-    if (GetProcessIoCounters(GetCurrentProcess(), &newIoCounters)) {
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_OPS,
-                              int(newIoCounters.ReadOperationCount - ioCounters.ReadOperationCount));
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_TRANSFER,
-                              int((newIoCounters.ReadTransferCount - ioCounters.ReadTransferCount) / 1024));
-    }
-#elif defined(XP_UNIX)
-    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_HARD_FAULTS,
-                            int(initialRUsage.ru_majflt));
-    struct rusage newRUsage;
-    if (!getrusage(RUSAGE_SELF, &newRUsage)) {
-      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_HARD_FAULTS,
-                              int(newRUsage.ru_majflt - initialRUsage.ru_majflt));
-    }
-#else
-  #error "Unknown platform"  // having this here keeps cppcheck happy
-#endif
-  }
-
 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
   XRE_EnableSameExecutableForContentProc();
 #endif
 
   int result = do_main(argc, argv, envp, xreDirectory);
 
   NS_LogTerm();
 
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -224,17 +224,17 @@ var gFxAccounts = {
 
       // Reset the button to its original state.
       this.panelUILabel.setAttribute("label", defaultLabel);
       this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
       this.panelUIFooter.removeAttribute("fxastatus");
       this.panelUIFooter.removeAttribute("fxaprofileimage");
       this.panelUIAvatar.style.removeProperty("list-style-image");
       let showErrorBadge = false;
-      if (!this._inCustomizationMode && userData) {
+      if (userData) {
         // At this point we consider the user as logged-in (but still can be in an error state)
         if (this.loginFailed) {
           let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1);
           this.panelUIFooter.setAttribute("fxastatus", "error");
           this.panelUILabel.setAttribute("label", errorLabel);
           this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
           showErrorBadge = true;
         } else if (!userData.verified) {
@@ -255,17 +255,17 @@ var gFxAccounts = {
       if (showErrorBadge) {
         gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
       } else {
         gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
       }
     }
 
     let updateWithProfile = (profile) => {
-      if (!this._inCustomizationMode && profileInfoEnabled) {
+      if (profileInfoEnabled) {
         if (profile.displayName) {
           this.panelUILabel.setAttribute("label", profile.displayName);
         }
         if (profile.avatar) {
           this.panelUIFooter.setAttribute("fxaprofileimage", "set");
           let bgImage = "url(\"" + profile.avatar + "\")";
           this.panelUIAvatar.style.listStyleImage = bgImage;
 
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -982,17 +982,16 @@ var gHistorySwipeAnimation = {
    */
   _takeSnapshot: function HSA__takeSnapshot() {
     if (!this._readyToTakeSnapshots()) {
       return;
     }
 
     let canvas = null;
 
-    TelemetryStopwatch.start("FX_GESTURE_TAKE_SNAPSHOT_OF_PAGE");
     try {
       let browser = gBrowser.selectedBrowser;
       let r = browser.getBoundingClientRect();
       canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
                                         "canvas");
       canvas.mozOpaque = true;
       let scale = window.devicePixelRatio;
       canvas.width = r.width * scale;
@@ -1001,17 +1000,16 @@ var gHistorySwipeAnimation = {
       let zoom = browser.markupDocumentViewer.fullZoom * scale;
       ctx.scale(zoom, zoom);
       ctx.drawWindow(browser.contentWindow,
                      0, 0, canvas.width / zoom, canvas.height / zoom, "white",
                      ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
                      ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
                      ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
     } finally {
-      TelemetryStopwatch.finish("FX_GESTURE_TAKE_SNAPSHOT_OF_PAGE");
     }
 
     TelemetryStopwatch.start("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE");
     try {
       this._installCurrentPageSnapshot(canvas);
       this._assignSnapshotToCurrentBrowser(canvas);
     } finally {
       TelemetryStopwatch.finish("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE");
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -238,16 +238,20 @@ toolbar[customizing] > .overflow-button 
 #main-window[tabsintitlebar] #titlebar-buttonbox {
   position: relative;
 }
 
 #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box;
 }
 
+#personal-bookmarks {
+  -moz-window-dragging: inherit;
+}
+
 %ifdef XP_MACOSX
 #titlebar-fullscreen-button {
   -moz-appearance: -moz-mac-fullscreen-button;
 }
 
 /* Fullscreen and caption buttons don't move with RTL on OS X so override the automatic ordering. */
 #titlebar-secondary-buttonbox:-moz-locale-dir(ltr),
 #titlebar-buttonbox-container:-moz-locale-dir(rtl),
@@ -281,18 +285,18 @@ toolbar[customizing] > .overflow-button 
 }
 
 #main-window[tabletmode] #titlebar-min,
 #main-window[tabletmode] #titlebar-max {
   display: none !important;
 }
 
 #main-window[tabsintitlebar] #TabsToolbar,
-#main-window[tabsintitlebar] #toolbar-menubar:not([autohide=true]),
-#main-window[tabsintitlebar] #navigator-toolbox > toolbar:not(#toolbar-menubar):-moz-lwtheme {
+#main-window[tabsintitlebar] #toolbar-menubar,
+#main-window[tabsintitlebar] #navigator-toolbox > toolbar:-moz-lwtheme {
   -moz-window-dragging: drag;
 }
 %endif
 
 %endif
 
 #main-window[inFullscreen][inDOMFullscreen] #navigator-toolbox,
 #main-window[inFullscreen][inDOMFullscreen] #fullscr-toggler,
@@ -355,26 +359,16 @@ toolbarpaletteitem > #personal-bookmarks
 }
 
 #nav-bar-customization-target > #personal-bookmarks,
 toolbar:not(#TabsToolbar) > #wrapper-personal-bookmarks,
 toolbar:not(#TabsToolbar) > #personal-bookmarks {
   -moz-box-flex: 1;
 }
 
-/* Ensure that empty parts of the bookmarks container can be dragged on OSX, and on other OSes
- * only when a lwtheme is in use. */
-%ifdef XP_MACOSX
-#main-window[tabsintitlebar]:not([customizing]) #personal-bookmarks {
-%else
-#main-window[tabsintitlebar]:not([customizing]) #personal-bookmarks:-moz-lwtheme {
-%endif
-  -moz-window-dragging: drag;
-}
-
 #zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
   display: -moz-box;
 }
 
 #urlbar-reload-button:not([displaystop]) + #urlbar-stop-button,
 #urlbar-reload-button[displaystop] {
   visibility: collapse;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6569,23 +6569,35 @@ var gIdentityHandler = {
   get _identityPopup () {
     delete this._identityPopup;
     return this._identityPopup = document.getElementById("identity-popup");
   },
   get _identityBox () {
     delete this._identityBox;
     return this._identityBox = document.getElementById("identity-box");
   },
+  get _identityPopupMultiView () {
+    delete _identityPopupMultiView;
+    return document.getElementById("identity-popup-multiView");
+  },
   get _identityPopupContentHosts () {
     delete this._identityPopupContentHosts;
-    return this._identityPopupContentHosts = [...document.querySelectorAll(".identity-popup-headline.host")];
+    let selector = ".identity-popup-headline.host";
+    return this._identityPopupContentHosts = [
+      ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+      ...document.querySelectorAll(selector)
+    ];
   },
   get _identityPopupContentHostless () {
     delete this._identityPopupContentHostless;
-    return this._identityPopupContentHostless = [...document.querySelectorAll(".identity-popup-headline.hostless")];
+    let selector = ".identity-popup-headline.hostless";
+    return this._identityPopupContentHostless = [
+      ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+      ...document.querySelectorAll(selector)
+    ];
   },
   get _identityPopupContentOwner () {
     delete this._identityPopupContentOwner;
     return this._identityPopupContentOwner =
       document.getElementById("identity-popup-content-owner");
   },
   get _identityPopupContentSupp () {
     delete this._identityPopupContentSupp;
@@ -6635,17 +6647,17 @@ var gIdentityHandler = {
    */
   handleMoreInfoClick : function(event) {
     displaySecurityInfo();
     event.stopPropagation();
     this._identityPopup.hidePopup();
   },
 
   toggleSubView(name, anchor) {
-    let view = document.getElementById("identity-popup-multiView");
+    let view = this._identityPopupMultiView;
     if (view.showingSubView) {
       view.showMainView();
     } else {
       view.showSubView(`identity-popup-${name}View`, anchor);
     }
 
     // If an element is focused that's not the anchor, clear the focus.
     // Elements of hidden views have -moz-user-focus:ignore but setting that
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5620,20 +5620,16 @@
       <method name="_handleTabTelemetryEnd">
         <parameter name="aTab"/>
         <body>
         <![CDATA[
           if (!aTab._animStartTime) {
             return;
           }
 
-          Services.telemetry.getHistogramById(aTab.closing ?
-                                              "FX_TAB_ANIM_CLOSE_MS" :
-                                              "FX_TAB_ANIM_OPEN_MS")
-                            .add(Date.now() - aTab._animStartTime);
           aTab._animStartTime = 0;
 
           // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
           if (!("_recordingHandle" in aTab)) {
             return;
           }
 
           let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -762,17 +762,16 @@ function assertMixedContentBlockingState
   is(popupAttr.includes("passive-loaded"), passiveLoaded,
       "identity-popup has expected attr for passiveLoaded");
   is(bodyAttr.includes("passive-loaded"), passiveLoaded,
       "securityView-body has expected attr for passiveLoaded");
 
   // Make sure the correct icon is visible in the Control Center.
   // This logic is controlled with CSS, so this helps prevent regressions there.
   let securityView = doc.getElementById("identity-popup-securityView");
-  let securityContent = doc.getElementById("identity-popup-security-content");
   let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
                        getPropertyValue("background-image");
   let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
                           getPropertyValue("background-image");
 
   if (stateInsecure) {
     is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
       "CC using 'not secure' icon");
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -13,16 +13,18 @@ Cu.import("resource://gre/modules/FileUt
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 
+const kBookmarksFileName = "360sefav.db";
+
 function copyToTempUTF8File(file, charset) {
   let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                       .createInstance(Ci.nsIFileInputStream);
   inputStream.init(file, -1, -1, 0);
   let inputStr = NetUtil.readInputStreamToString(
     inputStream, inputStream.available(), { charset });
 
   // Use random to reduce the likelihood of a name collision in createUnique.
@@ -90,17 +92,17 @@ function getHash(aStr) {
 
   // convert the binary hash data to a hex string.
   let binary = hasher.finish(false);
   return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i))).join("").toLowerCase();
 }
 
 function Bookmarks(aProfileFolder) {
   let file = aProfileFolder.clone();
-  file.append("360sefav.db");
+  file.append(kBookmarksFileName);
 
   this._file = file;
 }
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
 
   get exists() {
     return this._file.exists() && this._file.isReadable();
@@ -293,13 +295,30 @@ Qihoo360seProfileMigrator.prototype.getR
   }
 
   let resources = [
     new Bookmarks(profileFolder)
   ];
   return resources.filter(r => r.exists);
 };
 
+Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
+  let bookmarksPaths = this.sourceProfiles.map(({id}) => {
+    return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
+  });
+  if (!bookmarksPaths.length) {
+    return Promise.resolve(new Date(0));
+  }
+  let datePromises = bookmarksPaths.map(path => {
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Qihoo360seProfileMigrator.prototype.classDescription = "360 Secure Browser Profile Migrator";
 Qihoo360seProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=360se";
 Qihoo360seProfileMigrator.prototype.classID = Components.ID("{d0037b95-296a-4a4e-94b2-c3d075d20ab1}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Qihoo360seProfileMigrator]);
--- a/browser/components/migration/AutoMigrate.jsm
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -3,62 +3,73 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["AutoMigrate"];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
+const kAutoMigrateStartedPref = "browser.migrate.automigrate-started";
+const kAutoMigrateFinishedPref = "browser.migrate.automigrate-finished";
+
 Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const AutoMigrate = {
   get resourceTypesToUse() {
-    let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
-    return BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+    let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+    return BOOKMARKS | HISTORY | PASSWORDS;
   },
 
   /**
    * Automatically pick a migrator and resources to migrate,
    * then migrate those and start up.
    *
    * @throws if automatically deciding on migrators/data
    *         failed for some reason.
    */
   migrate(profileStartup, migratorKey, profileToMigrate) {
-    let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_SUCCEEDED");
-    histogram.add("initialized");
+    let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
+    histogram.add(0);
     let migrator = this.pickMigrator(migratorKey);
-    histogram.add("got-browser");
+    histogram.add(5);
 
     profileToMigrate = this.pickProfile(migrator, profileToMigrate);
-    histogram.add("got-profile");
+    histogram.add(10);
 
     let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
     if (!(resourceTypes & this.resourceTypesToUse)) {
       throw new Error("No usable resources were found for the selected browser!");
     }
-    histogram.add("got-data");
+    histogram.add(15);
 
     let sawErrors = false;
     let migrationObserver = function(subject, topic, data) {
       if (topic == "Migration:ItemError") {
         sawErrors = true;
       } else if (topic == "Migration:Ended") {
-        histogram.add(sawErrors ? "finished-with-errors" : "finished");
+        histogram.add(25);
+        if (sawErrors) {
+          histogram.add(26);
+        }
         Services.obs.removeObserver(migrationObserver, "Migration:Ended");
         Services.obs.removeObserver(migrationObserver, "Migration:ItemError");
+        Services.prefs.setCharPref(kAutoMigrateFinishedPref, Date.now().toString());
       }
     };
 
     Services.obs.addObserver(migrationObserver, "Migration:Ended", false);
     Services.obs.addObserver(migrationObserver, "Migration:ItemError", false);
+    Services.prefs.setCharPref(kAutoMigrateStartedPref, Date.now().toString());
     migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
-    histogram.add("migrate-called-without-exceptions");
+    histogram.add(20);
   },
 
   /**
    * Pick and return a migrator to use for automatically migrating.
    *
    * @param {String} migratorKey   optional, a migrator key to prefer/pick.
    * @returns                      the migrator to use for migrating.
    */
@@ -104,10 +115,64 @@ const AutoMigrate = {
       }
       return suggestedProfile.id;
     }
     if (profiles && profiles.length > 1) {
       throw new Error("Don't know how to pick a profile when more than 1 profile is present.");
     }
     return profiles ? profiles[0].id : null;
   },
+
+  getUndoRange() {
+    let start, finish;
+    try {
+      start = parseInt(Services.prefs.getCharPref(kAutoMigrateStartedPref), 10);
+      finish = parseInt(Services.prefs.getCharPref(kAutoMigrateFinishedPref), 10);
+    } catch (ex) {
+      Cu.reportError(ex);
+    }
+    if (!finish || !start) {
+      return null;
+    }
+    return [new Date(start), new Date(finish)];
+  },
+
+  canUndo() {
+    if (!this.getUndoRange()) {
+      return Promise.resolve(false);
+    }
+    // Return a promise resolving to false if we're signed into sync, resolve
+    // to true otherwise.
+    let {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
+    return fxAccounts.getSignedInUser().then(user => !user, () => Promise.resolve(true));
+  },
+
+  undo: Task.async(function* () {
+    if (!(yield this.canUndo())) {
+      throw new Error("Can't undo!");
+    }
+
+    yield PlacesUtils.bookmarks.eraseEverything();
+
+    // NB: we drop the start time of the migration for now. This is because
+    // imported history will always end up being 'backdated' to the actual
+    // visit time recorded by the browser from which we imported. As a result,
+    // a lower bound on this item doesn't really make sense.
+    // Note that for form data this could be different, but we currently don't
+    // support form data import from any non-Firefox browser, so it isn't
+    // imported from other browsers by the automigration code, nor do we
+    // remove it here.
+    let range = this.getUndoRange();
+    yield PlacesUtils.history.removeVisitsByFilter({
+      beginDate: new Date(0),
+      endDate: range[1]
+    });
+
+    try {
+      Services.logins.removeAllLogins();
+    } catch (ex) {
+      // ignore failure.
+    }
+    Services.prefs.clearUserPref("browser.migrate.automigrate-started");
+    Services.prefs.clearUserPref("browser.migrate.automigrate-finished");
+  }),
 };
 
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -19,16 +19,18 @@ const AUTH_TYPE = {
   SCHEME_DIGEST: 2
 };
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Console.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
@@ -131,16 +133,37 @@ ChromeProfileMigrator.prototype.getResou
           possibleResources.push(GetWindowsPasswordsResource(profileFolder));
         }
         return possibleResources.filter(r => r != null);
       }
     }
     return [];
   };
 
+ChromeProfileMigrator.prototype.getLastUsedDate =
+  function Chrome_getLastUsedDate() {
+    let datePromises = this.sourceProfiles.map(profile => {
+      let profileFolder = this._chromeUserDataFolder.clone();
+      let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
+      let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
+        let path = OS.Path.join(basePath, leafName);
+        return OS.File.stat(path).catch(_ => null).then(info => {
+          return info ? info.lastModificationDate : 0;
+        });
+      });
+      return Promise.all(fileDatePromises).then(dates => {
+        return Math.max.apply(Math, dates);
+      });
+    });
+    return Promise.all(datePromises).then(dates => {
+      dates.push(0);
+      return new Date(Math.max.apply(Math, dates));
+    });
+  };
+
 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
   get: function Chrome_sourceProfiles() {
     if ("__sourceProfiles" in this)
       return this.__sourceProfiles;
 
     if (!this._chromeUserDataFolder)
       return [];
 
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -1,18 +1,19 @@
 /* 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/. */
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ESEDBReader",
                                   "resource:///modules/ESEDBReader.jsm");
 
 const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
@@ -415,16 +416,44 @@ EdgeProfileMigrator.prototype.getResourc
   ];
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
+EdgeProfileMigrator.prototype.getLastUsedDate = function() {
+  // Don't do this if we don't have a single profile (see the comment for
+  // sourceProfiles) or if we can't find the database file:
+  if (this.sourceProfiles !== null || !gEdgeDatabase) {
+    return Promise.resolve(new Date(0));
+  }
+  let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
+  let dbPath = gEdgeDatabase.path;
+  let cookieMigrator = MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
+  let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
+  let datePromises = [logFilePath, dbPath, ... cookiePaths].map(path => {
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  datePromises.push(new Promise(resolve => {
+    let typedURLs = new Map();
+    try {
+      typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+    } catch (ex) {}
+    let times = [0, ... typedURLs.values()];
+    resolve(Math.max.apply(Math, times));
+  }));
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
  * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
  * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
  */
 EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
   let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
   return isWin10OrHigher ? null : [];
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -92,16 +92,23 @@ FirefoxProfileMigrator.prototype.getReso
 
   // Surely data cannot be imported from the current profile.
   if (sourceProfileDir.equals(currentProfileDir))
     return null;
 
   return this._getResourcesInternal(sourceProfileDir, currentProfileDir, aProfile);
 };
 
+FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
+  // We always pretend we're really old, so that we don't mess
+  // up the determination of which browser is the most 'recent'
+  // to import from.
+  return Promise.resolve(new Date(0));
+};
+
 FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileDir, currentProfileDir, aProfile) {
   let getFileResource = function(aMigrationType, aFileNames) {
     let files = [];
     for (let fileName of aFileNames) {
       let file = this._getFileObject(sourceProfileDir, fileName);
       if (file)
         files.push(file);
     }
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -8,19 +8,20 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@@ -490,16 +491,36 @@ IEProfileMigrator.prototype.getResources
   }
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
+IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
+  let datePromises = ["Favs", "CookD"].map(dirId => {
+    let {path} = Services.dirsvc.get(dirId, Ci.nsIFile);
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  datePromises.push(new Promise(resolve => {
+    let typedURLs = new Map();
+    try {
+      typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+    } catch (ex) {}
+    let dates = [0, ... typedURLs.values()];
+    resolve(Math.max.apply(Math, dates));
+  }));
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                                       kMainKey, "Default_Page_URL");
     let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
                                                kMainKey, "Start Page");
     // If the user didn't customize the Start Page, he is still on the default
     // page, that may be considered the equivalent of our about:home.  There's
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -133,16 +133,30 @@ this.MigratorPrototype = {
    *        aProfile is a value returned by the sourceProfiles getter (see
    *        above).
    */
   getResources: function MP_getResources(aProfile) {
     throw new Error("getResources must be overridden");
   },
 
   /**
+   * OVERRIDE in order to provide an estimate of when the last time was
+   * that somebody used the browser. It is OK that this is somewhat fuzzy -
+   * history may not be available (or be wiped or not present due to e.g.
+   * incognito mode).
+   *
+   * @return a Promise that resolves to the last used date.
+   *
+   * @note If not overridden, the promise will resolve to the unix epoch.
+   */
+  getLastUsedDate() {
+    return Promise.resolve(new Date(0));
+  },
+
+  /**
    * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
    * that is just the Firefox migrator, see bug 737381).  Default: false.
    *
    * Startup-only migrators are different in two ways:
    * - they may only be used during startup.
    * - the user-profile is half baked during migration.  The folder exists,
    *   but it's only accessible through MigrationUtils.profileStartup.
    *   The migrator can call MigrationUtils.profileStartup.doStartup
--- a/browser/components/migration/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -4,20 +4,21 @@
 
 "use strict";
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyListUtils",
                                   "resource://gre/modules/PropertyListUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
@@ -635,16 +636,34 @@ SafariProfileMigrator.prototype.getResou
     let wfFile = FileUtils.getFile("UsrPrfs", ["com.apple.WebFoundation.plist"]);
     if (wfFile.exists())
       resources.push(new WebFoundationCookieBehavior(wfFile));
   }
 
   return resources;
 };
 
+SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
+  let profileDir;
+  if (AppConstants.platform == "macosx") {
+    profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
+  } else {
+    profileDir = FileUtils.getDir("AppData", ["Apple Computer", "Safari"], false);
+  }
+  let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
+    let path = OS.Path.join(profileDir.path, file);
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
   get: function get_mainPreferencesPropertyList() {
     if (this._mainPreferencesPropertyList === undefined) {
       let file;
       if (AppConstants.platform == "macosx") {
         file = FileUtils.getDir("UsrPrfs", [], false);
       } else {
         file = FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -78,39 +78,39 @@ var MigrationWizard = {
         let migrator = MigrationUtils.getMigrator(group.selectedItem.id);
         visibility = migrator.sourceLocked ? "visible" : "hidden";
       }
       document.getElementById("closeSourceBrowser").style.visibility = visibility;
     }
     this._wiz.canRewind = false;
 
     var selectedMigrator = null;
+    this._availableMigrators = [];
 
     // Figure out what source apps are are available to import from:
     var group = document.getElementById("importSourceGroup");
-    var availableMigratorCount = 0;
     for (var i = 0; i < group.childNodes.length; ++i) {
       var migratorKey = group.childNodes[i].id;
       if (migratorKey != "nothing") {
         var migrator = MigrationUtils.getMigrator(migratorKey);
         if (migrator) {
           // Save this as the first selectable item, if we don't already have
           // one, or if it is the migrator that was passed to us.
           if (!selectedMigrator || this._source == migratorKey)
             selectedMigrator = group.childNodes[i];
-          availableMigratorCount++;
+          this._availableMigrators.push([migratorKey, migrator]);
         } else {
           // Hide this option
           group.childNodes[i].hidden = true;
         }
       }
     }
     if (this.isInitialMigration) {
       Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_BROWSER_COUNT")
-        .add(availableMigratorCount);
+        .add(this._availableMigrators.length);
       let defaultBrowser = MigrationUtils.getMigratorKeyForDefaultBrowser();
       // This will record 0 for unknown default browser IDs.
       defaultBrowser = MigrationUtils.getSourceIdForTelemetry(defaultBrowser);
       Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_EXISTING_DEFAULT_BROWSER")
         .add(defaultBrowser);
     }
 
     group.addEventListener("command", toggleCloseBrowserWarning);
@@ -443,16 +443,19 @@ var MigrationWizard = {
         label.setAttribute("style", "font-weight: bold");
       break;
     case "Migration:ItemAfterMigrate":
       label = document.getElementById(aData + "_migrated");
       if (label)
         label.removeAttribute("style");
       break;
     case "Migration:Ended":
+      if (this.isInitialMigration) {
+        this.reportDataRecencyTelemetry();
+      }
       if (this._autoMigrate) {
         Services.telemetry.getKeyedHistogramById("FX_MIGRATION_HOMEPAGE_IMPORTED")
                           .add(this._source, !!this._newHomePage);
         if (this._newHomePage) {
           try {
             // set homepage properly
             var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
                                     .getService(Components.interfaces.nsIPrefService);
@@ -527,10 +530,36 @@ var MigrationWizard = {
     }
   },
 
   onDonePageShow: function ()
   {
     this._wiz.getButton("cancel").disabled = true;
     this._wiz.canRewind = false;
     this._listItems("doneItems");
-  }
+  },
+
+  reportDataRecencyTelemetry() {
+    let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_DATA_RECENCY");
+    let lastUsedPromises = [];
+    for (let [key, migrator] of this._availableMigrators) {
+      // No block-scoped let in for...of loop conditions, so get the source:
+      let localKey = key;
+      lastUsedPromises.push(migrator.getLastUsedDate().then(date => {
+        const ONE_YEAR = 24 * 365;
+        let diffInHours = Math.round((Date.now() - date) / (60 * 60 * 1000));
+        if (diffInHours > ONE_YEAR) {
+          diffInHours = ONE_YEAR;
+        }
+        histogram.add(localKey, diffInHours);
+        return [localKey, diffInHours];
+      }));
+    }
+    Promise.all(lastUsedPromises).then(migratorUsedTimeDiff => {
+      // Sort low to high.
+      migratorUsedTimeDiff.sort(([keyA, diffA], [keyB, diffB]) => diffA - diffB);
+      let usedMostRecentBrowser = migratorUsedTimeDiff.length && this._source == migratorUsedTimeDiff[0][0];
+      let usedRecentBrowser =
+        Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_USED_RECENT_BROWSER");
+      usedRecentBrowser.add(this._source, usedMostRecentBrowser);
+    });
+  },
 };
--- a/browser/components/migration/nsIBrowserProfileMigrator.idl
+++ b/browser/components/migration/nsIBrowserProfileMigrator.idl
@@ -39,16 +39,22 @@ interface nsIBrowserProfileMigrator : ns
    *          to import
    * @param   aDoingStartup "true" if the profile is not currently being used.
    * @return  bit field containing profile items (see above)
    * @note    a return value of 0 represents no items rather than ALL.
    */
   unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
 
   /**
+   * Get the last time data from this browser was modified
+   * @return a promise that resolves to a JS Date object
+   */
+  jsval getLastUsedDate();
+
+  /**
    * Whether or not there is any data that can be imported from this
    * browser (i.e. whether or not it is installed, and there exists
    * a user profile)
    */
   readonly attribute boolean          sourceExists;
 
 
   /**
--- a/browser/components/migration/tests/unit/test_automigration.js
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -1,14 +1,19 @@
 Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+Cu.import("resource://testing-common/PlacesTestUtils.jsm");
 let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm");
 
 let gShimmedMigratorKeyPicker = null;
 let gShimmedMigrator = null;
 
+const kUsecPerMin = 60 * 1000000;
+
 // This is really a proxy on MigrationUtils, but if we specify that directly,
 // we get in trouble because the object itself is frozen, and Proxies can't
 // return a different value to an object when directly proxying a frozen
 // object.
 AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
   get(target, name) {
     if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
       return gShimmedMigratorKeyPicker;
@@ -107,13 +112,138 @@ add_task(function* checkIntegration() {
   };
   gShimmedMigratorKeyPicker = function() {
     return "gobbledygook";
   };
   AutoMigrate.migrate("startup");
   Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
                      "getMigrateData called with 'null' as a profile");
 
-  let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
-  let expectedTypes = BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+  let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+  let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
+  Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+                   "migrate called with 'null' as a profile");
+});
+
+/**
+ * Test the undo preconditions and a no-op undo in the automigrator.
+ */
+add_task(function* checkUndoPreconditions() {
+  gShimmedMigrator = {
+    get sourceProfiles() {
+      do_print("Read sourceProfiles");
+      return null;
+    },
+    getMigrateData(profileToMigrate) {
+      this._getMigrateDataArgs = profileToMigrate;
+      return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
+    },
+    migrate(types, startup, profileToMigrate) {
+      this._migrateArgs = [types, startup, profileToMigrate];
+      TestUtils.executeSoon(function() {
+        Services.obs.notifyObservers(null, "Migration:Ended", undefined);
+      });
+    },
+  };
+
+  gShimmedMigratorKeyPicker = function() {
+    return "gobbledygook";
+  };
+  AutoMigrate.migrate("startup");
+  let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
+  Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+                     "getMigrateData called with 'null' as a profile");
+
+  let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+  let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
   Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
-                   "getMigrateData called with 'null' as a profile");
+                   "migrate called with 'null' as a profile");
+
+  yield migrationFinishedPromise;
+  Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-started"),
+            "Should have set start time pref");
+  Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
+            "Should have set finish time pref");
+  Assert.ok((yield AutoMigrate.canUndo()), "Should be able to undo migration");
+
+  let [beginRange, endRange] = AutoMigrate.getUndoRange();
+  let stringRange = `beginRange: ${beginRange}; endRange: ${endRange}`;
+  Assert.ok(beginRange <= endRange,
+            "Migration should have started before or when it ended " + stringRange);
+
+  yield AutoMigrate.undo();
+  Assert.ok(true, "Should be able to finish an undo cycle.");
 });
+
+/**
+ * Fake a migration and then try to undo it to verify all data gets removed.
+ */
+add_task(function* checkUndoRemoval() {
+  let startTime = "" + Date.now();
+  Services.prefs.setCharPref("browser.migrate.automigrate-started", startTime);
+
+  // Insert a login and check that that worked.
+  let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+  login.init("www.mozilla.org", "http://www.mozilla.org", null, "user", "pass", "userEl", "passEl");
+  Services.logins.addLogin(login);
+  let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+                                                "http://www.mozilla.org", null);
+  Assert.equal(storedLogins.length, 1, "Should have 1 login");
+
+  // Insert a bookmark and check that we have exactly 1 bookmark for that URI.
+  yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://www.example.org/",
+    title: "Some example bookmark",
+  });
+
+  let bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+  Assert.ok(bookmark, "Should have a bookmark before undo");
+  Assert.equal(bookmark.title, "Some example bookmark", "Should have correct bookmark before undo.");
+
+  // Insert 2 history visits - one in the current migration time, one from before.
+  let now_uSec = Date.now() * 1000;
+  let visitedURI = Services.io.newURI("http://www.example.com/", null, null);
+  yield PlacesTestUtils.addVisits([
+    {uri: visitedURI, visitDate: now_uSec},
+    {uri: visitedURI, visitDate: now_uSec - 100 * kUsecPerMin},
+  ]);
+
+  // Verify that both visits get reported.
+  let opts = PlacesUtils.history.getNewQueryOptions();
+  opts.resultType = opts.RESULTS_AS_VISIT;
+  let query = PlacesUtils.history.getNewQuery();
+  query.uri = visitedURI;
+  let visits = PlacesUtils.history.executeQuery(query, opts);
+  visits.root.containerOpen = true;
+  Assert.equal(visits.root.childCount, 2, "Should have 2 visits");
+  // Clean up:
+  visits.root.containerOpen = false;
+
+  // Now set finished pref:
+  let endTime = "" + Date.now();
+  Services.prefs.setCharPref("browser.migrate.automigrate-finished", endTime);
+
+  // Verify that we can undo, then undo:
+  Assert.ok(yield AutoMigrate.canUndo(), "Should be possible to undo migration");
+  yield AutoMigrate.undo();
+
+  // Check that the undo removed the history visits:
+  visits = PlacesUtils.history.executeQuery(query, opts);
+  visits.root.containerOpen = true;
+  Assert.equal(visits.root.childCount, 0, "Should have no more visits");
+  visits.root.containerOpen = false;
+
+  // Check that the undo removed the bookmarks:
+  bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+  Assert.ok(!bookmark, "Should have no bookmarks after undo");
+
+  // Check that the undo removed the passwords:
+  storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+                                            "http://www.mozilla.org", null);
+  Assert.equal(storedLogins.length, 0, "Should have no logins");
+
+  // Finally check prefs got cleared:
+  Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-started"),
+            "Should no longer have pref for migration start time.");
+  Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
+            "Should no longer have pref for migration finish time.");
+});
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -63,17 +63,17 @@
             <menuitem value="he"              label="&font.langGroup.hebrew;"/>
             <menuitem value="ja"              label="&font.langGroup.japanese;"/>
             <menuitem value="x-knda"          label="&font.langGroup.kannada;"/>
             <menuitem value="x-khmr"          label="&font.langGroup.khmer;"/>
             <menuitem value="ko"              label="&font.langGroup.korean;"/>
             <menuitem value="x-western"       label="&font.langGroup.latin;"/>
             <menuitem value="x-mlym"          label="&font.langGroup.malayalam;"/>
             <menuitem value="x-math"          label="&font.langGroup.math;"/>
-            <menuitem value="x-orya"          label="&font.langGroup.oriya;"/>
+            <menuitem value="x-orya"          label="&font.langGroup.odia;"/>
             <menuitem value="x-sinh"          label="&font.langGroup.sinhala;"/>
             <menuitem value="x-tamil"         label="&font.langGroup.tamil;"/>
             <menuitem value="x-telu"          label="&font.langGroup.telugu;"/>
             <menuitem value="th"              label="&font.langGroup.thai;"/>
             <menuitem value="x-tibt"          label="&font.langGroup.tibetan;"/>
             <menuitem value="x-cans"          label="&font.langGroup.canadian;"/>
             <menuitem value="x-unicode"       label="&font.langGroup.other;"/>
           </menupopup>
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -131,19 +131,19 @@
              value="&trackingProtectionPBMLearnMore.label;"/>
       <spacer flex="1" />
       <button id="changeBlockListPBM"
               label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
               preference="pref.privacy.disable_button.change_blocklist"/>
     </hbox>
   </vbox>
   <vbox>
-    <description>&doNotTrack.pre.label;<label
-    class="text-link" id="doNotTrackSettings" href="#"
-    >&doNotTrack.settings.label;</label>&doNotTrack.post.label;</description>
+    <description>&doNotTrack.pre.label;<html:a
+    class="inline-link" id="doNotTrackSettings" href="#"
+    >&doNotTrack.settings.label;</html:a>&doNotTrack.post.label;</description>
   </vbox>
 </groupbox>
 
 <!-- History -->
 <groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
   <caption><label>&history.label;</label></caption>
   <hbox align="center">
     <label id="historyModeLabel"
@@ -160,32 +160,32 @@
     <label>&historyHeader.post.label;</label>
   </hbox>
   <deck id="historyPane">
     <vbox id="historyRememberPane">
       <hbox align="center" flex="1">
         <vbox flex="1">
           <description>&rememberDescription.label;</description>
           <separator class="thin"/>
-          <description>&rememberActions.pre.label;<label
-          class="text-link" id="historyRememberClear" href="#"
-          >&rememberActions.clearHistory.label;</label>&rememberActions.middle.label;<label
-          class="text-link" id="historyRememberCookies" href="#"
-          >&rememberActions.removeCookies.label;</label>&rememberActions.post.label;</description>
+          <description>&rememberActions.pre.label;<html:a
+          class="inline-link" id="historyRememberClear" href="#"
+          >&rememberActions.clearHistory.label;</html:a>&rememberActions.middle.label;<html:a
+          class="inline-link" id="historyRememberCookies" href="#"
+          >&rememberActions.removeCookies.label;</html:a>&rememberActions.post.label;</description>
         </vbox>
       </hbox>
     </vbox>
     <vbox id="historyDontRememberPane">
       <hbox align="center" flex="1">
         <vbox flex="1">
           <description>&dontrememberDescription.label;</description>
           <separator class="thin"/>
-          <description>&dontrememberActions.pre.label;<label
-          class="text-link" id="historyDontRememberClear" href="#"
-          >&dontrememberActions.clearHistory.label;</label>&dontrememberActions.post.label;</description>
+          <description>&dontrememberActions.pre.label;<html:a
+          class="inline-link" id="historyDontRememberClear" href="#"
+          >&dontrememberActions.clearHistory.label;</html:a>&dontrememberActions.post.label;</description>
         </vbox>
       </hbox>
     </vbox>
     <vbox id="historyCustomPane">
       <separator class="thin"/>
       <vbox>
         <vbox align="start">
           <checkbox id="privateBrowsingAutoStart"
--- a/browser/components/sessionstore/SessionWorker.js
+++ b/browser/components/sessionstore/SessionWorker.js
@@ -339,35 +339,39 @@ var Agent = {
     // Sanity check
     if (typeof prefix == "undefined" || prefix == "") {
       throw new TypeError();
     }
 
     let exn = null;
 
     let iterator = new File.DirectoryIterator(path);
-    if (!iterator.exists()) {
-      return;
-    }
-    for (let entry in iterator) {
-      if (entry.isDir) {
-        continue;
+    try {
+      if (!iterator.exists()) {
+        return;
       }
-      if (!prefix || entry.name.startsWith(prefix)) {
-        try {
-          File.remove(entry.path);
-        } catch (ex) {
-          // Don't stop immediately
-          exn = exn || ex;
+      for (let entry in iterator) {
+        if (entry.isDir) {
+          continue;
+        }
+        if (!prefix || entry.name.startsWith(prefix)) {
+          try {
+            File.remove(entry.path);
+          } catch (ex) {
+            // Don't stop immediately
+            exn = exn || ex;
+          }
         }
       }
-    }
 
-    if (exn) {
-      throw exn;
+      if (exn) {
+        throw exn;
+      }
+    } finally {
+      iterator.close();
     }
   },
 };
 
 function isNoSuchFileEx(aReason) {
   return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
 }
 
--- a/browser/config/mozconfigs/linux64/artifact
+++ b/browser/config/mozconfigs/linux64/artifact
@@ -1,10 +1,9 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
-NO_CACHE=1
-
-. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+. "$topsrcdir/build/mozconfig.common.override"
 
 ac_add_options --enable-artifact-builds
 unset CC
 unset CXX
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -872,26 +872,60 @@ var WindowListener = {
       /**
        * Indicates if tab sharing is paused.
        * Set by tab pause button, startBrowserSharing and stopBrowserSharing.
        * Defaults to false as link generator(owner) enters room we are sharing tabs.
        */
       _browserSharePaused: false, 
 
       /**
+       * Stores details about the last notification.
+       *
+       * @type {Object}
+       */
+      _lastNotification: {}, 
+
+      /**
+       * Used to determine if the browser sharing info bar is currently being
+       * shown or not.
+       */
+      _showingBrowserSharingInfoBar: function _showingBrowserSharingInfoBar() {
+        var browser = gBrowser.selectedBrowser;
+        var box = gBrowser.getNotificationBox(browser);
+        var notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
+
+        return !!notification;}, 
+
+
+      /**
        * Shows an infobar notification at the top of the browser window that warns
        * the user that their browser tabs are being broadcasted through the current
        * conversation.
        * @param  {String} currentRoomToken Room we are currently joined.
        * @return {void}
        */
       _maybeShowBrowserSharingInfoBar: function _maybeShowBrowserSharingInfoBar(currentRoomToken) {var _this13 = this;
-        this._hideBrowserSharingInfoBar();
+        var participantsCount = this.LoopRooms.getNumParticipants(currentRoomToken);
+
+        if (this._showingBrowserSharingInfoBar()) {
+          // When we first open the room, there will be one or zero partipicants
+          // in the room. The notification box changes when there's more than one,
+          // so work that out here.
+          var notAlone = participantsCount > 1;
+          var previousNotAlone = this._lastNotification.participantsCount <= 1;
 
-        var participantsCount = this.LoopRooms.getNumParticipants(currentRoomToken);
+          // If we're not actually changing the notification bar, then don't
+          // re-display it. This avoids the bar sliding in twice.
+          if (notAlone !== previousNotAlone && 
+          this._browserSharePaused === this._lastNotification.paused) {
+            return;}
+
+
+          this._hideBrowserSharingInfoBar();}
+
 
         var initStrings = this._setInfoBarStrings(participantsCount > 1, this._browserSharePaused);
 
         var box = gBrowser.getNotificationBox();
         var bar = box.appendNotification(
         initStrings.message, // label
         kBrowserSharingNotificationId, // value
         // Icon defined in browser theme CSS.
@@ -930,17 +964,20 @@ var WindowListener = {
           type: "stop" }]);
 
 
 
         // Sets 'paused' class if needed.
         bar.classList.toggle("paused", !!this._browserSharePaused);
 
         // Keep showing the notification bar until the user explicitly closes it.
-        bar.persistence = -1;}, 
+        bar.persistence = -1;
+
+        this._lastNotification.participantsCount = participantsCount;
+        this._lastNotification.paused = this._browserSharePaused;}, 
 
 
       /**
        * Hides the infobar, permanantly if requested.
        *
        * @param   {Object}  browser Optional link to the browser we want to
        *                    remove the infobar from. If not present, defaults
        *                    to current browser instance.
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -1100,29 +1100,29 @@ var MozLoopServiceInternal = {
         url: url, 
         remote: MozLoopService.getLoopPref("remote.autostart") }, 
       callback);
       if (!chatboxInstance) {
         resolve(null);
         // It's common for unit tests to overload Chat.open, so check if we actually
         // got a DOM node back.
       } else if (chatboxInstance.setAttribute) {
-          // Set properties that influence visual appearance of the chatbox right
-          // away to circumvent glitches.
-          chatboxInstance.setAttribute("customSize", "loopDefault");
-          chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
-          var buttons = "minimize,";
-          if (MozLoopService.getLoopPref("conversationPopOut.enabled")) {
-            buttons += "swap,";}
-
-          Chat.loadButtonSet(chatboxInstance, buttons + kChatboxHangupButton.id);
-          // Final fall-through in case a unit test overloaded Chat.open. Here we can
-          // immediately resolve the promise.
-        } else {
-            resolve(windowId);}});}, 
+        // Set properties that influence visual appearance of the chatbox right
+        // away to circumvent glitches.
+        chatboxInstance.setAttribute("customSize", "loopDefault");
+        chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
+        var buttons = "minimize,";
+        if (MozLoopService.getLoopPref("conversationPopOut.enabled")) {
+          buttons += "swap,";}
+
+        Chat.loadButtonSet(chatboxInstance, buttons + kChatboxHangupButton.id);
+        // Final fall-through in case a unit test overloaded Chat.open. Here we can
+        // immediately resolve the promise.
+      } else {
+        resolve(windowId);}});}, 
 
 
 
 
   /**
    * Fetch Firefox Accounts (FxA) OAuth parameters from the Loop Server.
    *
    * @return {Promise} resolved with the body of the hawk request for OAuth parameters.
--- a/browser/extensions/loop/chrome/content/panels/js/roomViews.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomViews.js
@@ -180,28 +180,19 @@ loop.roomViews = function (mozL10n) {
     componentWillUpdate: function componentWillUpdate(nextProps, nextState) {
       // The SDK needs to know about the configuration and the elements to use
       // for display. So the best way seems to pass the information here - ideally
       // the sdk wouldn't need to know this, but we can't change that.
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT && 
       nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({ 
           publisherConfig: this.getDefaultPublisherConfig({ 
-            publishVideo: !this.state.videoMuted }) }));}
-
-
+            publishVideo: !this.state.videoMuted }) }));}}, 
 
 
-      // Now that we're ready to share, automatically start sharing a tab only
-      // if we're not already connected to the room via the sdk, e.g. not in the
-      // case a remote participant just left.
-      if (nextState.roomState === ROOM_STATES.SESSION_CONNECTED && 
-      !(this.state.roomState === ROOM_STATES.SESSION_CONNECTED || 
-      this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS)) {
-        this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());}}, 
 
 
 
     /**
      * User clicked on the "Leave" button.
      */
     leaveRoom: function leaveRoom() {
       if (this.state.used) {
--- a/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
@@ -365,52 +365,19 @@ describe("loop.roomViews", function () {
 
 
         activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch, 
         new sharedActions.SetupStreamElements({ 
           publisherConfig: { 
-            fake: "config" } }));});
-
-
-
-
-      it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function () {
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
-        mountTestComponent();
-
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch, 
-        new sharedActions.StartBrowserShare());});
+            fake: "config" } }));});});
 
 
-      it("should not dispatch a `StartBrowserShare` action when the previous state was HAS_PARTICIPANTS", function () {
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.HAS_PARTICIPANTS });
-        mountTestComponent();
-
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
-
-        sinon.assert.notCalled(dispatcher.dispatch);});
-
-
-      it("should not dispatch a `StartBrowserShare` action when the previous state was SESSION_CONNECTED", function () {
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
-        mountTestComponent();
-
-        activeRoomStore.setStoreState({ 
-          roomState: ROOM_STATES.SESSION_CONNECTED, 
-          // Additional change to force an update.
-          screenSharingState: "fake" });
-
-
-        sinon.assert.notCalled(dispatcher.dispatch);});});
 
 
 
     describe("#render", function () {
       it("should set document.title to store.serverData.roomName", function () {
         mountTestComponent();
 
         activeRoomStore.setStoreState({ roomName: "fakeName" });
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -713,18 +713,26 @@ loop.store.ActiveRoomStore = function (m
         sessionId: actionData.sessionId, 
         roomState: ROOM_STATES.JOINED });
 
 
       this._setRefreshTimeout(actionData.expires);
 
       this._sdkDriver.connectSession(actionData);
 
-      loop.request("AddConversationContext", this._storeState.windowId, 
-      actionData.sessionId, "");}, 
+      this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
+
+      // Set up a listener for watching screen shares. This will get notified
+      // with the first windowId when it is added, so we may start off the sharing
+      // from within the listener.
+      loop.subscribe("BrowserSwitch", this._browserSharingListener);
+
+      loop.requestMulti(["AddConversationContext", this._storeState.windowId, 
+      actionData.sessionId, ""], 
+      ["AddBrowserSharingListener", this.getStoreState().windowId]);}, 
 
 
     /**
      * Handles recording when the sdk has connected to the servers.
      */
     connectedToSdkServers: function connectedToSdkServers() {
       this.setStoreState({ 
         roomState: ROOM_STATES.SESSION_CONNECTED });}, 
@@ -775,17 +783,24 @@ loop.store.ActiveRoomStore = function (m
           localSrcMediaElement: actionData.srcMediaElement });
 
         return;}
 
 
       this.setStoreState({ 
         remoteAudioEnabled: actionData.hasAudio, 
         remoteVideoEnabled: actionData.hasVideo, 
-        remoteSrcMediaElement: actionData.srcMediaElement });}, 
+        remoteSrcMediaElement: actionData.srcMediaElement });
+
+
+      // We start browser sharing here so that it starts *after* the audio/video
+      // has connected. This is to attempt to help performance when a room is
+      // initially joined.
+      if (this._isDesktop) {
+        this.startBrowserShare();}}, 
 
 
 
     /**
      * Handles a media stream being destroyed. This may be a local or a remote stream.
      *
      * @param {sharedActions.MediaStreamDestroyed} actionData
      */
@@ -861,29 +876,47 @@ loop.store.ActiveRoomStore = function (m
      * only be used for browser sharing.
      *
      * @param {Number} windowId  The new windowId to start sharing.
      */
     _handleSwitchBrowserShare: function _handleSwitchBrowserShare(windowId) {
       if (Array.isArray(windowId)) {
         windowId = windowId[0];}
 
-      if (!windowId) {
-        return;}
 
-      if (windowId.isError) {
+      // There was an error getting the windowId, so lets just reset things.
+      if (windowId && windowId.isError) {
         console.error("Error getting the windowId: " + windowId.message);
         this.dispatchAction(new sharedActions.ScreenSharingState({ 
           state: SCREEN_SHARE_STATES.INACTIVE }));
 
         return;}
 
 
+      // If there's no windowId, see if we've got one saved.
+      if (!windowId) {
+        // If we really don't have a window Id, then don't do anything.
+        if (!this._savedWindowId) {
+          return;}
+
+
+        windowId = this._savedWindowId;
+        delete this._savedWindowId;}
+
+
       var screenSharingState = this.getStoreState().screenSharingState;
 
+      // If we're inactive, or screen sharing is paused, just save the windowId
+      // for when we're ready.
+      if (screenSharingState === SCREEN_SHARE_STATES.INACTIVE || 
+      this._storeState.sharingPaused) {
+        this._savedWindowId = windowId;
+        return;}
+
+
       if (screenSharingState === SCREEN_SHARE_STATES.PENDING) {
         // Screen sharing is still pending, so assume that we need to kick it off.
         var options = { 
           videoSource: "browser", 
           constraints: { 
             browserWindow: windowId, 
             scrollWithPage: true } };
 
@@ -942,24 +975,20 @@ loop.store.ActiveRoomStore = function (m
       // For the unit test we already set the state here, instead of indirectly
       // via an action, because actions are queued thus depending on the
       // asynchronous nature of `loop.request`.
       this.setStoreState({ screenSharingState: SCREEN_SHARE_STATES.PENDING });
       this.dispatchAction(new sharedActions.ScreenSharingState({ 
         state: SCREEN_SHARE_STATES.PENDING }));
 
 
-      this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
-
-      // Set up a listener for watching screen shares. This will get notified
-      // with the first windowId when it is added, so we start off the sharing
-      // from within the listener.
-      loop.request("AddBrowserSharingListener", this.getStoreState().windowId).
-      then(this._browserSharingListener);
-      loop.subscribe("BrowserSwitch", this._browserSharingListener);}, 
+      // This attempts to start the actual browser sharing - we assume we've
+      // already got a windowId due to having requested it before connecting the
+      // media.
+      this._handleSwitchBrowserShare();}, 
 
 
     /**
      * Ends an active screenshare session.
      */
     endScreenShare: function endScreenShare() {
       if (this._browserSharingListener) {
         // Remove the browser sharing listener as we don't need it now.
@@ -980,16 +1009,26 @@ loop.store.ActiveRoomStore = function (m
      *
      * @param {sharedActions.ToggleBrowserSharing} actionData
      */
     toggleBrowserSharing: function toggleBrowserSharing(actionData) {
       this.setStoreState({ 
         sharingPaused: !actionData.enabled });
 
 
+      // If we've un-paused screen sharing, but we haven't started sharing, then
+      // we need to start that off.
+      if (actionData.enabled && 
+      this._storeState.screenSharingState === SCREEN_SHARE_STATES.PENDING) {
+        this._handleSwitchBrowserShare();} else 
+      {
+        // Otherwise just toggle the stream.
+        this._sdkDriver.toggleBrowserSharing(actionData.enabled);}
+
+
       // If unpausing, check the context as it might have changed.
       if (actionData.enabled) {
         this._checkTabContext();}}, 
 
 
 
     /**
      * Handles recording when a remote peer has connected to the servers.
--- a/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
+++ b/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
@@ -36,18 +36,17 @@ loop.OTSdkDriver = function () {
     this.connections = {};
 
     // Setup the metrics object to keep track of the number of connections we have
     // and the amount of streams.
     this._resetMetrics();
 
     this.dispatcher.register(this, [
     "setupStreamElements", 
-    "setMute", 
-    "toggleBrowserSharing"]);
+    "setMute"]);
 
 
     // Set loop.debug.sdk to true in the browser, or in standalone:
     // localStorage.setItem("debug.sdk", true);
     loop.shared.utils.getBoolPreference("debug.sdk", function (enabled) {
       // We don't bother with the else case - as we only create one instance of
       // OTSdkDriver per window, and hence, we leave the sdk set to its default
       // value.
@@ -232,21 +231,22 @@ loop.OTSdkDriver = function () {
       delete this._mockScreenSharePreviewEl;
       delete this._windowId;
       return true;}, 
 
 
     /**
      * Paused or resumes an active screenshare session as appropriate.
      *
-     * @param {sharedActions.ToggleBrowserSharing} actionData The data associated with the
-     *                                             action. See action.js.
+     * @param {Boolean} enabled  True if browser sharing should be enabled.
      */
-    toggleBrowserSharing: function toggleBrowserSharing(actionData) {
-      this.screenshare.publishVideo(actionData.enabled);}, 
+    toggleBrowserSharing: function toggleBrowserSharing(enabled) {
+      if (this.screenshare) {
+        this.screenshare.publishVideo(enabled);}}, 
+
 
 
     /**
      * Connects a session for the SDK, listening to the required events.
      *
      * sessionData items:
      * - sessionId: The OT session ID
      * - apiKey: The OT API key
@@ -532,20 +532,18 @@ loop.OTSdkDriver = function () {
      * received.
      *
      * @param {Stream} stream The SDK Stream:
      * https://tokbox.com/opentok/libraries/client/js/reference/Stream.html
      */
     _handleRemoteScreenShareCreated: function _handleRemoteScreenShareCreated(stream) {
       // Let the stores know first if the screen sharing is paused or not so
       // they can update the display properly
-      if (!stream[STREAM_PROPERTIES.HAS_VIDEO]) {
-        this.dispatcher.dispatch(new sharedActions.VideoScreenStreamChanged({ 
-          hasVideo: false }));}
-
+      this.dispatcher.dispatch(new sharedActions.VideoScreenStreamChanged({ 
+        hasVideo: stream[STREAM_PROPERTIES.HAS_VIDEO] }));
 
 
       // Let the stores know so they can update the display if needed.
       this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({ 
         receiving: true }));
 
 
       // There's no audio for screen shares so we don't need to worry about mute.
--- a/browser/extensions/loop/chrome/content/shared/js/utils.js
+++ b/browser/extensions/loop/chrome/content/shared/js/utils.js
@@ -356,27 +356,27 @@ if (inChrome) {
 
         callback(result.some(checkForInput));}).
       catch(function () {
         callback(false);});
 
       // MediaStreamTrack is the older version of the API, implemented originally
       // by Google Chrome.
     } else if ("MediaStreamTrack" in rootObject && 
-      "getSources" in rootObject.MediaStreamTrack) {
-        rootObject.MediaStreamTrack.getSources(function (result) {
-          function checkForInput(device) {
-            return device.kind === "audio" || device.kind === "video";}
+    "getSources" in rootObject.MediaStreamTrack) {
+      rootObject.MediaStreamTrack.getSources(function (result) {
+        function checkForInput(device) {
+          return device.kind === "audio" || device.kind === "video";}
 
 
-          callback(result.some(checkForInput));});} else 
+        callback(result.some(checkForInput));});} else 
 
-      {
-        // We don't know, so assume true.
-        callback(true);}}
+    {
+      // We don't know, so assume true.
+      callback(true);}}
 
 
 
   /**
    * Helper to allow getting some of the location data in a way that's compatible
    * with stubbing for unit tests.
    */
   function locationData() {
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -46,16 +46,17 @@ describe("loop.store.ActiveRoomStore", f
 
     fakeSdkDriver = { 
       connectSession: sinon.stub(), 
       disconnectSession: sinon.stub(), 
       forceDisconnectAll: sinon.stub().callsArg(0), 
       retryPublishWithoutVideo: sinon.stub(), 
       startScreenShare: sinon.stub(), 
       switchAcquiredWindow: sinon.stub(), 
+      toggleBrowserSharing: sinon.stub(), 
       endScreenShare: sinon.stub().returns(true) };
 
 
     store = new loop.store.ActiveRoomStore(dispatcher, { 
       sdkDriver: fakeSdkDriver });
 
 
     sandbox.stub(document.mozL10n ? document.mozL10n : navigator.mozL10n, "get", function (x) {
@@ -190,17 +191,22 @@ describe("loop.store.ActiveRoomStore", f
 
       sinon.assert.calledOnce(clearTimeout);});
 
 
     it("should remove the sharing listener", function () {
       sandbox.stub(loop, "unsubscribe");
 
       // Setup the listener.
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
 
       // Now simulate room failure.
       store.roomFailure(new sharedActions.RoomFailure({ 
         error: fakeError, 
         failedJoinRequest: false }));
 
 
       sinon.assert.calledOnce(loop.unsubscribe);
@@ -1201,17 +1207,22 @@ describe("loop.store.ActiveRoomStore", f
       sinon.assert.calledWithExactly(requestStubs["HangupNow"], 
       "fakeToken", "1627384950", "42");});
 
 
     it("should remove the sharing listener", function () {
       sandbox.stub(loop, "unsubscribe");
 
       // Setup the listener.
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
 
       // Now simulate connection failure.
       store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(loop.unsubscribe);
       sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");});
 
 
@@ -1332,17 +1343,35 @@ describe("loop.store.ActiveRoomStore", f
         hasAudio: true, 
         hasVideo: true, 
         isLocal: false, 
         srcMediaElement: fakeStreamElement }));
 
 
       expect(store.getStoreState().localVideoEnabled).eql(false);
       expect(store.getStoreState().remoteVideoEnabled).eql(true);
-      expect(store.getStoreState().remoteAudioEnabled).eql(true);});});
+      expect(store.getStoreState().remoteAudioEnabled).eql(true);});
+
+
+    it("should call startBrowserShare when is desktop", function () {
+      sandbox.stub(store, "startBrowserShare");
+      store._isDesktop = true;
+      store.setStoreState({ 
+        localVideoEnabled: false, 
+        remoteVideoEnabled: false });
+
+
+      store.mediaStreamCreated(new sharedActions.MediaStreamCreated({ 
+        hasAudio: true, 
+        hasVideo: true, 
+        isLocal: false, 
+        srcMediaElement: fakeStreamElement }));
+
+
+      sinon.assert.calledOnce(store.startBrowserShare);});});
 
 
 
   describe("#mediaStreamDestroyed", function () {
     var fakeStreamElement;
 
     beforeEach(function () {
       fakeStreamElement = { name: "fakeStreamElement" };
@@ -1489,42 +1518,32 @@ describe("loop.store.ActiveRoomStore", f
 
       expect(store.getStoreState().remoteVideoDimensions).eql({ 
         camera: { fake: 20 } });});});
 
 
 
 
   describe("#startBrowserShare", function () {
-    var getSelectedTabMetadataStub;
-
     beforeEach(function () {
-      getSelectedTabMetadataStub = sinon.stub();
-      LoopMochaUtils.stubLoopRequest({ 
-        GetSelectedTabMetadata: getSelectedTabMetadataStub.returns({ 
-          title: "fakeTitle", 
-          favicon: "fakeFavicon", 
-          url: "http://www.fakeurl.com" }) });
-
-
-
       store.setStoreState({ 
         roomState: ROOM_STATES.JOINED, 
         roomToken: "fakeToken", 
         sessionToken: "1627384950", 
         participants: [{ 
           displayName: "Owner", 
           owner: true }, 
         { 
           displayName: "Guest", 
           owner: false }] });
 
 
 
-      sandbox.stub(console, "error");});
+      sandbox.stub(console, "error");
+      sandbox.stub(store, "_handleSwitchBrowserShare");});
 
 
     afterEach(function () {
       store.endScreenShare();});
 
 
     it("should log an error if the state is not inactive", function () {
       store.setStoreState({ 
@@ -1551,167 +1570,274 @@ describe("loop.store.ActiveRoomStore", f
       store.startBrowserShare(new sharedActions.StartBrowserShare());
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch, 
       new sharedActions.ScreenSharingState({ 
         state: SCREEN_SHARE_STATES.PENDING }));});
 
 
 
-    it("should add a browser sharing listener for tab sharing", function () {
+    it("should call _handleSwitchBrowserShare", function () {
       store.startBrowserShare(new sharedActions.StartBrowserShare());
-      sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);});
-
-
-    it("should invoke the SDK driver with the correct options for tab sharing", function () {
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+
+      sinon.assert.calledOnce(store._handleSwitchBrowserShare);});});
+
+
+
+  describe("Screen share Events", function () {
+    it("should call _handleSwitchBrowserShare", function () {
+      sandbox.stub(store, "_handleSwitchBrowserShare");
+
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
+
+      LoopMochaUtils.publish("BrowserSwitch", 72);
+
+      sinon.assert.calledOnce(store._handleSwitchBrowserShare);});});
+
+
+
+  describe("#_handleSwitchBrowserShare", function () {
+    var getSelectedTabMetadataStub;
+
+    beforeEach(function () {
+      getSelectedTabMetadataStub = sinon.stub();
+      LoopMochaUtils.stubLoopRequest({ 
+        GetSelectedTabMetadata: getSelectedTabMetadataStub.returns({ 
+          title: "fakeTitle", 
+          favicon: "fakeFavicon", 
+          url: "http://www.fakeurl.com" }) });
+
+
+
+      store.setStoreState({ 
+        roomState: ROOM_STATES.JOINED, 
+        roomToken: "fakeToken", 
+        screenSharingState: SCREEN_SHARE_STATES.ACTIVE, 
+        sessionToken: "1627384950", 
+        participants: [{ 
+          displayName: "Owner", 
+          owner: true }, 
+        { 
+          displayName: "Guest", 
+          owner: false }] });
+
+
+
+      // Stub to prevent errors surfacing in the console.
+      sandbox.stub(console, "error");});
+
+
+    afterEach(function () {
+      store.endScreenShare();});
+
+
+    it("should log an error in the console", function () {
+      var err = new Error("foo");
+      err.isError = true;
+      store._handleSwitchBrowserShare(err);
+
+      sinon.assert.calledOnce(console.error);});
+
+
+    it("should end the screen sharing session when the listener receives an error", function () {
+      var err = new Error("foo");
+      err.isError = true;
+      store._handleSwitchBrowserShare(err);
+
+      // The dispatcher was already called once in beforeEach().
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWith(dispatcher.dispatch, 
+      new sharedActions.ScreenSharingState({ 
+        state: SCREEN_SHARE_STATES.INACTIVE }));
+
+      sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+    it("should save the windowId when the state is INACTIVE", function () {
+      store.setStoreState({ 
+        screenSharingState: SCREEN_SHARE_STATES.INACTIVE });
+
+
+      store._handleSwitchBrowserShare(72);
+
+      expect(store._savedWindowId, 72);});
+
+
+    it("should not do anything else when the state is INACTIVE", function () {
+      store.setStoreState({ 
+        screenSharingState: SCREEN_SHARE_STATES.INACTIVE });
+
+
+      store._handleSwitchBrowserShare(72);
+
+      sinon.assert.notCalled(dispatcher.dispatch);
+      sinon.assert.notCalled(fakeSdkDriver.startScreenShare);
+      sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+    it("should save the windowId when sharing is paused", function () {
+      store.setStoreState({ 
+        screenSharingState: SCREEN_SHARE_STATES.ACTIVE, 
+        sharingPaused: true });
+
+
+      store._handleSwitchBrowserShare(72);
+
+      expect(store._savedWindowId, 72);});
+
+
+    it("should not do anything else when the state is paused", function () {
+      store.setStoreState({ 
+        screenSharingState: SCREEN_SHARE_STATES.ACTIVE, 
+        sharingPaused: true });
+
+
+      store._handleSwitchBrowserShare(72);
+
+      sinon.assert.notCalled(dispatcher.dispatch);
+      sinon.assert.notCalled(fakeSdkDriver.startScreenShare);
+      sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+    it("should invoke the SDK driver with the correct options if the state is pending", function () {
+      store.setStoreState({ 
+        screenSharingState: SCREEN_SHARE_STATES.PENDING });
+
+
+      store._handleSwitchBrowserShare(42);
+
       sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
       sinon.assert.calledWith(fakeSdkDriver.startScreenShare, { 
         videoSource: "browser", 
         constraints: { 
           browserWindow: 42, 
           scrollWithPage: true } });});
 
 
 
 
+    it("should update the SDK driver when the state is active", function () {
+      store._handleSwitchBrowserShare(72);
+
+      sinon.assert.calledOnce(fakeSdkDriver.switchAcquiredWindow);
+      sinon.assert.calledWithExactly(fakeSdkDriver.switchAcquiredWindow, 72);});
+
+
+    it("should log an error if the state is unexpected", function () {
+      store.setStoreState({ 
+        screenSharingState: "invalid" });
+
+
+      store._handleSwitchBrowserShare(72);
+
+      sinon.assert.calledOnce(console.error);});
+
+
     it("should request the new metadata when the browser being shared change", function () {
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store._handleSwitchBrowserShare(42);
+
       clock.tick(500);
 
       sinon.assert.calledOnce(getSelectedTabMetadataStub);
-      sinon.assert.calledTwice(dispatcher.dispatch);
-      sinon.assert.calledWith(dispatcher.dispatch.getCall(1), 
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWithExactly(dispatcher.dispatch, 
       new sharedActions.UpdateRoomContext({ 
         newRoomDescription: "fakeTitle", 
         newRoomThumbnail: "fakeFavicon", 
         newRoomURL: "http://www.fakeurl.com", 
         roomToken: store.getStoreState().roomToken }));});
 
 
 
     it("should process only one request", function () {
       store.startBrowserShare(new sharedActions.StartBrowserShare());
       // Simulates multiple requests.
-      LoopMochaUtils.publish("BrowserSwitch", 72);
-      LoopMochaUtils.publish("BrowserSwitch", 72);
+      store._handleSwitchBrowserShare(42);
+      store._handleSwitchBrowserShare(42);
 
       clock.tick(500);
-      sinon.assert.calledThrice(getSelectedTabMetadataStub);
-      sinon.assert.calledTwice(dispatcher.dispatch);
-      sinon.assert.calledWith(dispatcher.dispatch.getCall(1), 
+
+      sinon.assert.calledTwice(getSelectedTabMetadataStub);
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWithExactly(dispatcher.dispatch, 
       new sharedActions.UpdateRoomContext({ 
         newRoomDescription: "fakeTitle", 
         newRoomThumbnail: "fakeFavicon", 
         newRoomURL: "http://www.fakeurl.com", 
         roomToken: store.getStoreState().roomToken }));});
 
 
 
     it("should not process a request without url", function () {
       getSelectedTabMetadataStub.returns({ 
         title: "fakeTitle", 
         favicon: "fakeFavicon" });
 
 
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store._handleSwitchBrowserShare(42);
+
       clock.tick(500);
 
       sinon.assert.calledOnce(getSelectedTabMetadataStub);
-      sinon.assert.calledOnce(dispatcher.dispatch);});
+      sinon.assert.notCalled(dispatcher.dispatch);});
 
 
     it("should not process a request if sharing is paused", function () {
       store.setStoreState({ 
         sharingPaused: true });
 
 
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store._handleSwitchBrowserShare(42);
       clock.tick(500);
 
       sinon.assert.notCalled(getSelectedTabMetadataStub);
-      sinon.assert.calledOnce(dispatcher.dispatch);});
+      sinon.assert.notCalled(dispatcher.dispatch);});
 
 
     it("should not process a request if no-one is in the room", function () {
       store.setStoreState({ 
         participants: [{ 
           displayName: "Owner", 
           owner: true }] });
 
 
 
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store._handleSwitchBrowserShare(42);
+
       clock.tick(500);
 
       sinon.assert.notCalled(getSelectedTabMetadataStub);
-      sinon.assert.calledOnce(dispatcher.dispatch);});});
-
-
-
-  describe("Screen share Events", function () {
-    beforeEach(function () {
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
-
-      store.setStoreState({ 
-        screenSharingState: SCREEN_SHARE_STATES.ACTIVE });
-
-
-      // Stub to prevent errors surfacing in the console.
-      sandbox.stub(window.console, "error");});
-
-
-    afterEach(function () {
-      store.endScreenShare();});
-
-
-    it("should log an error in the console", function () {
-      var err = new Error("foo");
-      err.isError = true;
-      LoopMochaUtils.publish("BrowserSwitch", err);
-
-      sinon.assert.calledOnce(console.error);});
-
-
-    it("should update the SDK driver when a new window id is received", function () {
-      LoopMochaUtils.publish("BrowserSwitch", 72);
-
-      sinon.assert.calledOnce(fakeSdkDriver.switchAcquiredWindow);
-      sinon.assert.calledWithExactly(fakeSdkDriver.switchAcquiredWindow, 72);});
-
-
-    it("should end the screen sharing session when the listener receives an error", function () {
-      var err = new Error("foo");
-      err.isError = true;
-      LoopMochaUtils.publish("BrowserSwitch", err);
-
-      // The dispatcher was already called once in beforeEach().
-      sinon.assert.calledTwice(dispatcher.dispatch);
-      sinon.assert.calledWith(dispatcher.dispatch, 
-      new sharedActions.ScreenSharingState({ 
-        state: SCREEN_SHARE_STATES.INACTIVE }));
-
-      sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});});
+      sinon.assert.notCalled(dispatcher.dispatch);});});
 
 
 
   describe("#endScreenShare", function () {
     it("should set the state to 'inactive'", function () {
       store.endScreenShare();
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch, 
       new sharedActions.ScreenSharingState({ 
         state: SCREEN_SHARE_STATES.INACTIVE }));});
 
 
 
     it("should remove the sharing listener", function () {
       // Setup the listener.
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
 
       // Now stop the screen share.
       store.endScreenShare();
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);});});
 
 
 
@@ -1918,17 +2044,22 @@ describe("loop.store.ActiveRoomStore", f
       sinon.assert.calledWith(requestStubs["HangupNow"], "fakeToken", 
       "1627384950", "1234");});
 
 
     it("should remove the sharing listener", function () {
       sandbox.stub(loop, "unsubscribe");
 
       // Setup the listener.
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
 
       // Now unload the window.
       store.windowUnload();
 
       sinon.assert.calledOnce(loop.unsubscribe);
       sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");});
 
 
@@ -1987,17 +2118,22 @@ describe("loop.store.ActiveRoomStore", f
 
       sinon.assert.notCalled(requestStubs["HangupNow"]);});
 
 
     it("should remove the sharing listener", function () {
       sandbox.stub(loop, "unsubscribe");
 
       // Setup the listener.
-      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      store.joinedRoom(new sharedActions.JoinedRoom({ 
+        apiKey: "", 
+        sessionToken: "", 
+        sessionId: "", 
+        expires: 0 }));
+
 
       // Now leave the room.
       store.leaveRoom();
 
       sinon.assert.calledOnce(loop.unsubscribe);
       sinon.assert.calledWith(loop.unsubscribe, "BrowserSwitch");});
 
 
--- a/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
+++ b/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
@@ -1,16 +1,16 @@
 /**
- * @license OpenTok.js v2.7.5 ccd6792 HEAD
+ * @license OpenTok.js v2.7.6 cff9122 HEAD
  *
  * Copyright (c) 2010-2015 TokBox, Inc.
  * Released under the MIT license
  * http://opensource.org/licenses/MIT
  *
- * Date: March 18 10:46:23 2016
+ * Date: June 24 07:05:09 2016
  */
 
 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 module.exports = Array;
 
 },{}],2:[function(require,module,exports){
 arguments[4][108][0].apply(exports,arguments)
 },{"dup":108}],3:[function(require,module,exports){
@@ -3811,22 +3811,22 @@ module.exports = reqAnimationFrame;
 'use strict';
 
 var Bluebird = require('bluebird');
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var util = exports;
 
 /**
- * @license  Common JS Helpers on OpenTok v2.7.5 ccd6792 HEAD
+ * @license  Common JS Helpers on OpenTok v2.7.6 cff9122 HEAD
  * http://www.tokbox.com/
  *
  * Copyright (c) 2016 TokBox, Inc.
  *
- * Date: March 18 10:46:49 2016
+ * Date: June 24 07:05:32 2016
  *
  */
 
 
 // OT Helper Methods
 //
 // helpers.js                           <- the root file
 // helpers/lib/{helper topic}.js        <- specialised helpers for specific tasks/topics
@@ -14164,67 +14164,95 @@ var substr = 'ab'.substr(-1) === 'b'
 ;
 
 }).call(this,require('_process'))
 
 },{"_process":79}],79:[function(require,module,exports){
 // shim for using process in browser
 
 var process = module.exports = {};
+
+// cached from whatever global is present so that test runners that stub it
+// don't break things.  But we need to wrap it in a try catch in case it is
+// wrapped in strict mode code which doesn't define any globals.  It's inside a
+// function because try/catches deoptimize in certain engines.
+
+var cachedSetTimeout;
+var cachedClearTimeout;
+
+(function () {
+  try {
+    cachedSetTimeout = setTimeout;
+  } catch (e) {
+    cachedSetTimeout = function () {
+      throw new Error('setTimeout is not defined');
+    }
+  }
+  try {
+    cachedClearTimeout = clearTimeout;
+  } catch (e) {
+    cachedClearTimeout = function () {
+      throw new Error('clearTimeout is not defined');
+    }
+  }
+} ())
 var queue = [];
 var draining = false;
 var currentQueue;
 var queueIndex = -1;
 
 function cleanUpNextTick() {
+    if (!draining || !currentQueue) {
+        return;
+    }
     draining = false;
     if (currentQueue.length) {
         queue = currentQueue.concat(queue);
     } else {
         queueIndex = -1;
     }
     if (queue.length) {
         drainQueue();
     }
 }
 
 function drainQueue() {
     if (draining) {
         return;
     }
-    var timeout = setTimeout(cleanUpNextTick);
+    var timeout = cachedSetTimeout(cleanUpNextTick);
     draining = true;
 
     var len = queue.length;
     while(len) {
         currentQueue = queue;
         queue = [];
         while (++queueIndex < len) {
             if (currentQueue) {
                 currentQueue[queueIndex].run();
             }
         }
         queueIndex = -1;
         len = queue.length;
     }
     currentQueue = null;
     draining = false;
-    clearTimeout(timeout);
+    cachedClearTimeout(timeout);
 }
 
 process.nextTick = function (fun) {
     var args = new Array(arguments.length - 1);
     if (arguments.length > 1) {
         for (var i = 1; i < arguments.length; i++) {
             args[i - 1] = arguments[i];
         }
     }
     queue.push(new Item(fun, args));
     if (queue.length === 1 && !draining) {
-        setTimeout(drainQueue, 0);
+        cachedSetTimeout(drainQueue, 0);
     }
 };
 
 // v8 likes predictible objects
 function Item(fun, array) {
     this.fun = fun;
     this.array = array;
 }
@@ -20249,19 +20277,19 @@ function ws(uri, protocols, opts) {
   }
   return instance;
 }
 
 if (WebSocket) ws.prototype = WebSocket.prototype;
 
 },{}],111:[function(require,module,exports){
 module.exports = {
-  "version": "v2.7.5",
-  "build": "ccd6792",
-  "buildTime": "March 18 10:46:49 2016",
+  "version": "v2.7.6",
+  "build": "cff9122",
+  "buildTime": "June 24 07:05:32 2016",
   "debug": "false",
   "websiteURL": "http://www.tokbox.com",
   "cdnURL": "http://static.opentok.com",
   "loggingURL": "http://hlg.tokbox.com/prod",
   "apiURL": "http://anvil.opentok.com",
   "messagingProtocol": "wss",
   "messagingPort": 443,
   "supportSSL": "true",
@@ -29949,16 +29977,17 @@ var PeerConnection = function(config) {
       // term, I want it to be noisy.
       throw new Error('PeerConnection broke!');
     }
 
     internalCreatePeerConnection(function() {
       subscribeProcessor(
         _peerConnection,
         config.numberOfSimulcastStreams,
+        config.offerConstraints,
 
         // Success: Relay Offer
         function(offer) {
           _offer = offer;
           relaySDP(RaptorConstants.Actions.OFFER, _offer, message.uri);
         },
 
         // Failure
@@ -30418,18 +30447,23 @@ module.exports = function PublisherPeerC
     _peerConnection.processMessage(type, message);
   };
 
   // Init
   this.init = function(iceServers, completion) {
     var pcConfig = {
       iceServers: iceServers,
       channels: channels,
-      numberOfSimulcastStreams: numberOfSimulcastStreams
-    };
+      numberOfSimulcastStreams: numberOfSimulcastStreams,
+      offerConstraints: {}
+    };
+
+    if (session.sessionInfo.reconnection) {
+      pcConfig.offerConstraints.iceRestart = true;
+    }
 
     setCertificates(pcConfig, function(err, pcConfigWithCerts) {
       if (err) {
         completion(err);
         return;
       }
 
       _peerConnection = PeerConnections.add(remoteConnection, streamId, pcConfigWithCerts);
@@ -31219,16 +31253,17 @@ var SDPHelpers = require('./sdp_helpers.
 // * set the new offer as the location description
 //
 // If there are no issues, the +success+ callback will be executed on completion.
 // Errors during any step will result in the +failure+ callback being executed.
 //
 module.exports = function subscribeProcessor(
   peerConnection,
   numberOfSimulcastStreams,
+  offerConstraints,
   success,
   failure
 ) {
   var generateErrorCallback, setLocalDescription;
 
   generateErrorCallback = function(message, prefix) {
     return function(errorReason) {
       logging.error(message);
@@ -31265,19 +31300,17 @@ module.exports = function subscribeProce
   peerConnection.createOffer(
     // Success
     setLocalDescription,
 
     // Failure
     generateErrorCallback('Error while creating Offer', 'CreateOffer'),
 
     // Constraints
-    {
-      iceRestart: true
-    }
+    offerConstraints
   );
 };
 
 },{"../logging.js":160,"./sdp_helpers.js":193}],196:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var parseIceServers = require('../messaging/raptor/parse_ice_servers.js');
@@ -31481,17 +31514,23 @@ module.exports = function SubscriberPeer
   };
 
   this.iceConnectionStateIsConnected = function() {
     return _peerConnection.iceConnectionStateIsConnected();
   };
 
   // Init
   this.init = function(completion) {
-    var pcConfig = {};
+    var pcConfig = {
+      offerConstraints: {}
+    };
+
+    if (session.sessionInfo.reconnection) {
+      pcConfig.offerConstraints.iceRestart = true;
+    }
 
     setCertificates(pcConfig, function(err, pcConfigWithCerts) {
       if (err) {
         completion(err);
         return;
       }
 
       _peerConnection = PeerConnections.add(
--- a/browser/extensions/loop/chrome/locale/en-US/loop.properties
+++ b/browser/extensions/loop/chrome/locale/en-US/loop.properties
@@ -2,17 +2,17 @@
 # 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/.
 
 # Panel Strings
 
 ## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
 ## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
 ## use "..." if \u2026 doesn't suit traditions in your locale.
-loopMenuItem_label=Start a conversation…
+loopMenuItem_label=Start a Conversation…
 loopMenuItem_accesskey=t
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
--- a/browser/extensions/loop/chrome/locale/nn-NO/loop.properties
+++ b/browser/extensions/loop/chrome/locale/nn-NO/loop.properties
@@ -28,17 +28,17 @@ panel_disconnect_button=Kopla frå
 
 ## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Trykk på Hello-knappen for å surfa på nettet saman med ein ven.
 first_time_experience_subheading_button_above=Trykk på knappen ovanfor for å surfa på nettet saman med ein ven.
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
-first_time_experience_content=Bruk han til å planleggja, arbeida, og ha det moro i lag.
+first_time_experience_content=Bruk Hello til å planleggja, arbeida, og ha det moro i lag.
 first_time_experience_content2=Bruk han til å få gjort ting: planlegging, arbeid og for å ha det moro i lag.
 first_time_experience_button_label2=Sjå korleis det fungerer
 
 ## First Time Experience Slides
 fte_slide_1_title=Surf på nettsider med ein ven
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
 fte_slide_1_copy=Om du planlegg ei reise eller leitar du etter ein present, {{clientShortname2}} hjelper deg med å ta raskare avgjerder i sanntid.
--- a/browser/extensions/loop/install.rdf.in
+++ b/browser/extensions/loop/install.rdf.in
@@ -4,17 +4,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>loop@mozilla.org</em:id>
     <em:bootstrap>true</em:bootstrap>
-    <em:version>1.4.1</em:version>
+    <em:version>1.4.2</em:version>
     <em:type>2</em:type>
 
     <!-- Target Application this extension can install into,
          with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>46.0a1</em:minVersion>
--- a/browser/extensions/loop/run-all-loop-tests.sh
+++ b/browser/extensions/loop/run-all-loop-tests.sh
@@ -24,25 +24,25 @@ LOOPDIR=browser/extensions/loop
 # to mess this up with CSP handling, and probably other changes, too.
 
 # Currently disabled due to Bug 1225832 - New Loop architecture is not compatible with test.
 #  browser/components/uitour/test/browser_UITour_loop.js
 
 TESTS="
   ${LOOPDIR}/chrome/test/mochitest
   browser/components/uitour/test/browser_UITour_loop_panel.js
-  browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
+  browser/base/content/test/webrtc/browser_devices_get_user_media_about_urls.js
   browser/base/content/test/general/browser_parsable_css.js
 "
 
 # Due to bug 1209463, we need to split these up and run them individually to
 # ensure we stop and report that there's an error.
 for test in $TESTS
 do
   ./mach mochitest --disable-e10s $test
   # UITour & get user media aren't compatible with e10s currenly.
   if [ "$1" != "--skip-e10s" ] && \
      [ "$test" != "browser/components/uitour/test/browser_UITour_loop.js" ] && \
-     [ "$test" != "browser/base/content/test/general/browser_devices_get_user_media_about_urls.js" ];
+     [ "$test" != "browser/base/content/test/webrtc/browser_devices_get_user_media_about_urls.js" ];
   then
     ./mach mochitest $test
   fi
 done
--- a/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
@@ -42,17 +42,17 @@
 <!ENTITY  font.langGroup.canadian                 "Unified Canadian Syllabary">
 <!ENTITY  font.langGroup.ethiopic                 "Ethiopic">
 <!ENTITY  font.langGroup.georgian                 "Georgian">
 <!ENTITY  font.langGroup.gujarati                 "Gujarati">
 <!ENTITY  font.langGroup.gurmukhi                 "Gurmukhi">
 <!ENTITY  font.langGroup.khmer                    "Khmer">
 <!ENTITY  font.langGroup.malayalam                "Malayalam">
 <!ENTITY  font.langGroup.math                     "Mathematics">
-<!ENTITY  font.langGroup.oriya                    "Oriya">
+<!ENTITY  font.langGroup.odia                     "Odia">
 <!ENTITY  font.langGroup.telugu                   "Telugu">
 <!ENTITY  font.langGroup.kannada                  "Kannada">
 <!ENTITY  font.langGroup.sinhala                  "Sinhala">
 <!ENTITY  font.langGroup.tibetan                  "Tibetan">
 <!-- Minimum font size -->
 <!ENTITY minSize.label                            "Minimum font size:">
 <!ENTITY minSize.accesskey                        "o">
 <!ENTITY minSize.none                             "None">
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -89,17 +89,17 @@
 #customization-container {
   background-color: rgb(247,247,247);
   color: black;
   text-shadow: none;
 }
 
 #customization-palette,
 #customization-empty {
-  padding: 0 25px 25px;
+  padding: 5px 25px 25px;
 }
 
 #customization-header {
   font-size: 1.75em;
   line-height: 1.75em;
   color: #666;
   font-weight: 200;
   margin: 25px 25px 12px;
@@ -224,16 +224,20 @@ toolbarpaletteitem[place="panel"] {
   transition: opacity .3s ease-in-out;
   opacity: 0;
 }
 
 #customization-palette[showing="true"] {
   opacity: 1;
 }
 
+toolbarpaletteitem toolbarbutton[disabled] {
+  color: inherit !important;
+}
+
 toolbarpaletteitem[notransition].panel-customization-placeholder,
 toolbarpaletteitem[notransition][place="toolbar"],
 toolbarpaletteitem[notransition][place="palette"],
 toolbarpaletteitem[notransition][place="panel"] {
   transition: none;
 }
 
 toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
@@ -272,18 +276,21 @@ toolbarpaletteitem[place="toolbar"]:-moz
 }
 
 toolbarpaletteitem[place="palette"]:not([mousedown="true"]):-moz-focusring,
 toolbarpaletteitem[place="panel"]:not([mousedown="true"]):-moz-focusring,
 toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
   /* Delay adding the focusring back until after the transform transition completes. */
   transition: outline-width .01s linear var(--drag-drop-transition-duration);
   outline: 1px dotted rgba(0,0,0,.5);
+  -moz-outline-radius: 2.5px;
+}
+
+toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
   outline-offset: -5px;
-  -moz-outline-radius: 2.5px;
 }
 
 #wrapper-edit-controls[place="palette"] > #edit-controls > toolbarbutton,
 #wrapper-edit-controls[place="palette"] > #edit-controls > separator,
 #wrapper-zoom-controls[place="palette"] > #zoom-controls > toolbarbutton,
 #wrapper-zoom-controls[place="palette"] > #zoom-controls > separator {
   margin-top: 20px;
 }
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -521,17 +521,17 @@ toolbarpaletteitem[place="palette"] > to
   display: flex;
   flex-shrink: 0;
   flex-direction: column;
   background-color: hsla(210,4%,10%,.07);
   padding: 0;
   margin: 0;
 }
 
-#main-window[customizing] #PanelUI-footer-fxa > toolbarseparator {
+#main-window[customizing] #PanelUI-footer-fxa {
   display: none;
 }
 
 #PanelUI-footer-fxa:not([fxastatus="signedin"]) > toolbarseparator,
 #PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-icon,
 #PanelUI-footer-fxa:not([fxaprofileimage]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
   display: none;
 }
@@ -793,17 +793,17 @@ toolbarpaletteitem[place="palette"] > to
 #PanelUI-remotetabs-deck:not([selectedIndex="3"]) > #PanelUI-remotetabs-nodevicespane {
   visibility: collapse;
 }
 
 #PanelUI-remotetabs-main[devices-status="single"] > #PanelUI-remotetabs-buttons {
   display: none;
 }
 
-#PanelUI-fxa-icon[syncstatus="active"] {
+#PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
   list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
 }
 
 #PanelUI-footer-fxa[fxastatus="migrate-signup"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
 #PanelUI-footer-fxa[fxastatus="migrate-verify"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
   list-style-image: url(chrome://browser/skin/warning.svg);
   -moz-image-region: auto;
 }
@@ -837,22 +837,18 @@ toolbarpaletteitem[place="palette"] > to
 #PanelUI-footer-fxa[fxastatus="error"][fxaprofileimage="set"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon {
   display: none;
 }
 
 #PanelUI-footer-fxa[fxastatus="error"]:not([fxaprofileimage="set"]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
   display: none;
 }
 
-#PanelUI-fxa-avatar[disabled],
+#PanelUI-fxa-status[disabled],
 #PanelUI-fxa-icon[disabled] {
-  display: none;
-}
-
-#PanelUI-fxa-status[disabled] {
   pointer-events: none;
 }
 
 #PanelUI-fxa-avatar {
   width: 32px;
   height: 32px;
   border-radius: 50%;
   background-repeat: no-repeat;
@@ -881,17 +877,21 @@ toolbarpaletteitem[place="palette"] > to
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #PanelUI-help[panel-multiview-anchor="true"] {
   -moz-image-region: rect(0, 64px, 16px, 48px);
 }
 
 #PanelUI-help[disabled],
-#PanelUI-quit[disabled] {
+#PanelUI-quit[disabled],
+#PanelUI-fxa-icon[disabled],
+#PanelUI-fxa-avatar[disabled],
+#PanelUI-fxa-label[disabled] > .toolbarbutton-icon,
+#PanelUI-fxa-status::after {
   opacity: 0.4;
 }
 
 #PanelUI-fxa-status:not([disabled]):hover,
 #PanelUI-fxa-icon:not([disabled]):hover,
 #PanelUI-help:not([disabled]):hover,
 #PanelUI-customize:hover,
 #PanelUI-quit:not([disabled]):hover {
@@ -1666,17 +1666,17 @@ menuitem[checked="true"].subviewbutton >
     list-style-image: url(chrome://branding/content/icon32.png);
   }
 
   #PanelUI-fxa-label,
   #PanelUI-fxa-icon {
     list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
   }
 
-  #PanelUI-fxa-icon[syncstatus="active"] {
+  #PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
     list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
   }
 
   #PanelUI-customize {
     list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
   }
 
   #customization-panelHolder #PanelUI-customize {
--- a/browser/themes/windows/devedition.css
+++ b/browser/themes/windows/devedition.css
@@ -256,18 +256,17 @@
 #navigator-toolbox {
   /* The side borders on the toolbox also look out-of-place because we don't paint over
    * the native background color at all, and these are !important for the same reason as above. */
   border-left: none !important;
   border-right: none !important;
 }
 
 /* Disable dragging like in the default theme: */
-#main-window[tabsintitlebar] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):-moz-lwtheme,
-#main-window[tabsintitlebar]:not([customizing]) #personal-bookmarks:-moz-lwtheme {
+#main-window[tabsintitlebar] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):-moz-lwtheme {
   -moz-window-dragging: no-drag;
 }
 
 /* The sidebar header has no background now that the background of the #browser-panel
  * has no image and is transparent. Fix: */
 .sidebar-header:-moz-lwtheme,
 #sidebar-header {
   background-color: var(--chrome-background-color);
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -90,17 +90,19 @@ if test "$OS_TARGET" = "Android"; then
 fi
 ])
 
 AC_DEFUN([MOZ_ANDROID_STLPORT],
 [
 
 if test "$OS_TARGET" = "Android"; then
     cpu_arch_dir="$ANDROID_CPU_ARCH"
-    if test "$MOZ_THUMB2" = 1; then
+    # NDK r12 removed the arm/thumb library split and just made everything
+    # thumb by default.  Attempt to compensate.
+    if test "$MOZ_THUMB2" = 1 -a -d "$cpu_arch_dir/thumb"; then
         cpu_arch_dir="$cpu_arch_dir/thumb"
     fi
 
     if test -z "$STLPORT_CPPFLAGS$STLPORT_LIBS"; then
         case "$android_cxx_stl" in
         libstdc++)
             # android-ndk-r8b and later
             ndk_base="$android_ndk/sources/cxx-stl/gnu-libstdc++/$android_gnu_compiler_version"
@@ -124,16 +126,22 @@ if test "$OS_TARGET" = "Android"; then
             cxxabi_base="$ndk_base/llvm-libc++abi"
             cxxabi_include="$cxxabi_base/libcxxabi/include"
 
             if ! test -e "$cxx_libs/libc++_static.a"; then
                 AC_MSG_ERROR([Couldn't find path to llvm-libc++ in the android ndk])
             fi
 
             STLPORT_LIBS="-L$cxx_libs -lc++_static"
+            # NDK r12 split the libc++ runtime libraries into pieces.
+            for lib in c++abi unwind android_support; do
+                if test -e "$cxx_libs/lib${lib}.a"; then
+                     STLPORT_LIBS="$STLPORT_LIBS -l${lib}"
+                fi
+            done
             # Add android/support/include/ for prototyping long double math
             # functions, locale-specific C library functions, multibyte support,
             # etc.
             STLPORT_CPPFLAGS="-I$android_ndk/sources/android/support/include -I$cxx_include -I$cxxabi_include"
             ;;
         mozstlport)
             # We don't need to set STLPORT_LIBS, because the build system will
             # take care of linking in our home-built stlport where it is needed.
--- a/build/autoconf/ffi.m4
+++ b/build/autoconf/ffi.m4
@@ -32,16 +32,22 @@ if test "$MOZ_BUILD_APP" != js -o -n "$J
       ac_configure_args="$ac_configure_args --enable-debug"
     fi
     if test "$DSO_PIC_CFLAGS"; then
       ac_configure_args="$ac_configure_args --with-pic"
     fi
     for var in AS CC CXX CPP LD AR RANLIB STRIP; do
       ac_configure_args="$ac_configure_args $var='`eval echo \\${${var}}`'"
     done
+    old_cflags="$CFLAGS"
+    # The libffi sources (especially the ARM ones) are written expecting gas
+    # syntax, and clang's integrated assembler doesn't handle all of gas syntax.
+    if test -n "$CLANG_CC" -a "$CPU_ARCH" = arm; then
+      CFLAGS="-no-integrated-as $CFLAGS"
+    fi
     if test "$CROSS_COMPILE"; then
       export CPPFLAGS CFLAGS LDFLAGS
     fi
     ac_configure_args="$ac_configure_args --build=$build --host=$target"
     if test "$_MSC_VER"; then
       # Use a wrapper script for cl and ml that looks more like gcc.
       # autotools can't quite handle an MSVC build environment yet.
       LDFLAGS=
@@ -72,13 +78,14 @@ if test "$MOZ_BUILD_APP" != js -o -n "$J
 
     # Use a separate cache file for libffi, since it does things differently
     # from our configure.
     old_config_files=$CONFIG_FILES
     unset CONFIG_FILES
     AC_OUTPUT_SUBDIRS(js/src/ctypes/libffi)
     ac_configure_args="$_SUBDIR_CONFIG_ARGS"
     CONFIG_FILES=$old_config_files
+    CFLAGS="$old_cflags"
   fi
 
 fi
 ])
 
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -223,17 +223,16 @@ def old_configure_options(*options):
     '--enable-pref-extensions',
     '--enable-printing',
     '--enable-pulseaudio',
     '--enable-raw',
     '--enable-readline',
     '--enable-reflow-perf',
     '--enable-release',
     '--enable-require-all-d3dc-versions',
-    '--enable-rust',
     '--enable-safe-browsing',
     '--enable-sandbox',
     '--enable-signmar',
     '--enable-simulator',
     '--enable-skia',
     '--enable-skia-gpu',
     '--enable-small-chunk-size',
     '--enable-startup-notification',
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/rust.configure
@@ -0,0 +1,146 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+option('--enable-rust', help='Include Rust language sources')
+
+@depends('--enable-rust')
+def rust_compiler_names(value):
+    if value:
+        return ['rustc']
+
+rustc = check_prog('RUSTC', rust_compiler_names, allow_missing=True)
+
+@depends_if(rustc)
+@checking('rustc version')
+@imports('subprocess')
+def rustc_version(rustc):
+    try:
+        # TODO: We should run `rustc --version -v` and parse that output instead.
+        version = Version(subprocess.check_output(
+            [rustc, '--version']
+        ).splitlines()[0].split()[1])
+        return version
+    except subprocess.CalledProcessError as e:
+        die('Failed to get rustc version: %s', e.message)
+
+@depends('--enable-rust', rustc, rustc_version)
+@imports(_from='textwrap', _import='dedent')
+def rust_compiler(value, rustc, rustc_version):
+    if value:
+        if not rustc:
+            die(dedent('''\
+            Rust compiler not found.
+            To compile rust language sources, you must have 'rustc' in your path.
+            See http://www.rust-lang.org/ for more information.
+            '''))
+        if rustc_version < '1.5':
+            die(dedent('''\
+            Rust compiler {} is too old.
+            To compile Rust language sources please install at least
+            version 1.5 of the 'rustc' toolchain and make sure it is
+            first in your path.
+            You can verify this by typing 'rustc --version'.
+            '''.format(rustc_version)))
+        return True
+
+set_config('MOZ_RUST', rust_compiler)
+
+@depends(rust_compiler, rustc, target, cross_compiling)
+@imports('os')
+@imports('subprocess')
+@imports(_from='mozbuild.configure.util', _import='LineIO')
+@imports(_from='mozbuild.shellutil', _import='quote')
+@imports(_from='tempfile', _import='mkstemp')
+def rust_target(rust_compiler, rustc, target, cross_compiling):
+    if rust_compiler:
+        # Rust's --target options are similar to, but not exactly the same
+        # as, the autoconf-derived targets we use.  An example would be that
+        # Rust uses distinct target triples for targetting the GNU C++ ABI
+        # and the MSVC C++ ABI on Win32, whereas autoconf has a single
+        # triple and relies on the user to ensure that everything is
+        # compiled for the appropriate ABI.  We need to perform appropriate
+        # munging to get the correct option to rustc.
+        #
+        # The canonical list of targets supported can be derived from:
+        #
+        # https://github.com/rust-lang/rust/tree/master/mk/cfg
+
+        # Avoid having to write out os+kernel for all the platforms where
+        # they don't differ.
+        os_or_kernel = target.kernel if target.kernel == 'Linux' and target.os != 'Android' else target.os
+        rustc_target = {
+            # DragonFly
+            ('x86_64', 'Dragonfly'): 'x86_64-unknown-dragonfly',
+            # FreeBSD, GNU/kFreeBSD
+            ('x86', 'FreeBSD'): 'i686-unknown-freebsd',
+            ('x86_64', 'FreeBSD'): 'x86_64-unknown-freebsd',
+            # NetBSD
+            ('x86_64', 'NetBSD'): 'x86_64-unknown-netbsd',
+            # OpenBSD
+            ('x86_64', 'OpenBSD'): 'x86_64-unknown-openbsd',
+            # Linux
+            ('x86', 'Linux'): 'i686-unknown-linux-gnu',
+            # Linux
+            ('x86_64', 'Linux'): 'x86_64-unknown-linux-gnu',
+            # OS X and iOS
+            ('x86', 'OSX'): 'i686-apple-darwin',
+            ('x86', 'iOS'): 'i386-apple-ios',
+            ('x86_64', 'OSX'): 'x86_64-apple-darwin',
+            # Android
+            ('x86', 'Android'): 'i686-linux-android',
+            ('arm', 'Android'): 'arm-linux-androideabi',
+            # Windows
+            # XXX better detection of CXX needed here, to figure out whether
+            # we need i686-pc-windows-gnu instead, since mingw32 builds work.
+            ('x86', 'WINNT'): 'i686-pc-windows-msvc',
+            ('x86_64', 'WINNT'): 'x86_64-pc-windows-msvc',
+        }.get((target.cpu, os_or_kernel), None)
+
+        if rustc_target is None:
+            if cross_compiling:
+                die("Don't know how to translate {} for rustc".format(target.alias))
+            # Fall back to implicit (native) target when not cross-compiling
+            return None
+
+        # Check to see whether our rustc has a reasonably functional stdlib
+        # for our chosen target.
+        target_arg = '--target=' + rustc_target
+        in_fd, in_path = mkstemp(prefix='conftest', suffix='.rs')
+        out_fd, out_path = mkstemp(prefix='conftest', suffix='.rlib')
+        os.close(out_fd)
+        try:
+            source = 'pub extern fn hello() { println!("Hello world"); }'
+            log.debug('Creating `%s` with content:', in_path)
+            with LineIO(lambda l: log.debug('| %s', l)) as out:
+                out.write(source)
+
+            os.write(in_fd, source)
+            os.close(in_fd)
+
+            cmd = [
+                rustc,
+                '--crate-type', 'staticlib',
+                target_arg,
+                '-o', out_path,
+                in_path,
+            ]
+            def failed():
+                die('Cannot compile for {} with {}'.format(target.alias, rustc))
+            check_cmd_output(*cmd, onerror=failed)
+            if not os.path.exists(out_path) or os.path.getsize(out_path) == 0:
+                failed()
+        finally:
+            os.remove(in_path)
+            os.remove(out_path)
+        # This target is usable.
+        return target_arg
+
+set_config('RUST_TARGET', rust_target)
+
+# Until we remove all the other Rust checks in old-configure.
+add_old_configure_assignment('MOZ_RUST', rust_compiler)
+add_old_configure_assignment('RUSTC', rustc)
+add_old_configure_assignment('RUST_TARGET', rust_target)
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -635,8 +635,10 @@ def debug_flags(env_debug_flags, enable_
     if len(enable_debug_flags):
         return enable_debug_flags[0]
     if env_debug_flags:
         return env_debug_flags[0]
     return default_debug_flags
 
 set_config('MOZ_DEBUG_FLAGS', debug_flags)
 add_old_configure_assignment('MOZ_DEBUG_FLAGS', debug_flags)
+
+include('rust.configure')
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -65,19 +65,20 @@ def normsep(path):
 # exists.
 # The `paths` parameter may be passed to search the given paths instead of
 # $PATH.
 @imports(_from='which', _import='which')
 @imports(_from='which', _import='WhichError')
 @imports('itertools')
 @imports(_from='os', _import='pathsep')
 def find_program(file, paths=None):
-    if is_absolute_or_relative(file):
-        return os.path.abspath(file) if os.path.isfile(file) else None
     try:
+        if is_absolute_or_relative(file):
+            return normsep(which(os.path.basename(file),
+                                 [os.path.dirname(file)]))
         if paths:
             if not isinstance(paths, (list, tuple)):
                 die("Paths provided to find_program must be a list of strings, "
                     "not %r", paths)
             paths = list(itertools.chain(
                 *(p.split(pathsep) for p in paths if p)))
         return normsep(which(file, path=paths))
     except WhichError:
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -914,21 +914,21 @@ ifdef ASFILES
 	$(AS) $(ASOUTOPTION)$@ $(ASFLAGS) $($(notdir $<)_FLAGS) $(AS_DASH_C_FLAG) $(_VPATH_SRCS)
 endif
 
 ifdef MOZ_RUST
 # Assume any system libraries rustc links against are already
 # in the target's LIBS.
 $(RSOBJS):
 	$(REPORT_BUILD)
-	$(RUSTC) $(RUSTFLAGS) --crate-type rlib --emit dep-info=$(MDDEPDIR)/$(call mk_libname,$<).pp,link=$(call mk_libname,$<) $(_VPATH_SRCS)
+	$(RUSTC) $(RUST_TARGET) $(RUSTFLAGS) --crate-type rlib --emit dep-info=$(MDDEPDIR)/$(call mk_libname,$<).pp,link=$(call mk_libname,$<) $(_VPATH_SRCS)
 
 $(RS_STATICLIB_CRATE_OBJ):
 	$(REPORT_BUILD)
-	$(RUSTC) $(RUSTFLAGS) --crate-type staticlib $(RLIB_EXTERN_CRATE_OPTIONS) --emit dep-info=$(MDDEPDIR)/$(call mk_global_crate_libname,$(RS_STATICLIB_CRATE_SRC)).pp,link=$@ $(RS_STATICLIB_CRATE_SRC)
+	$(RUSTC) $(RUST_TARGET) $(RUSTFLAGS) --crate-type staticlib $(RLIB_EXTERN_CRATE_OPTIONS) --emit dep-info=$(MDDEPDIR)/$(call mk_global_crate_libname,$(RS_STATICLIB_CRATE_SRC)).pp,link=$@ $(RS_STATICLIB_CRATE_SRC)
 endif
 
 $(SOBJS):
 	$(REPORT_BUILD)
 	$(AS) -o $@ $(ASFLAGS) $($(notdir $<)_FLAGS) $(LOCAL_INCLUDES) -c $<
 
 $(CPPOBJS):
 	$(REPORT_BUILD_VERBOSE)
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -1,33 +1,30 @@
 /* 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/. */
 
 /* eslint-env browser */
-/* globals BrowserToolboxProcess */
 
 "use strict";
 
-loader.lazyImporter(this, "BrowserToolboxProcess",
-  "resource://devtools/client/framework/ToolboxProcess.jsm");
-
 const { createClass, DOM: dom } =
   require("devtools/client/shared/vendor/react");
+const { debugAddon } = require("../../modules/addon");
 const Services = require("Services");
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 module.exports = createClass({
   displayName: "AddonTarget",
 
   debug() {
     let { target } = this.props;
-    BrowserToolboxProcess.init({ addonID: target.addonID });
+    debugAddon(target.addonID);
   },
 
   reload() {
     let { client, target } = this.props;
     // This function sometimes returns a partial promise that only
     // implements then().
     client.request({
       to: target.addonActor,
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -0,0 +1,23 @@
+/* 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/. */
+
+"use strict";
+
+loader.lazyImporter(this, "BrowserToolboxProcess",
+  "resource://devtools/client/framework/ToolboxProcess.jsm");
+
+let toolbox = null;
+
+exports.debugAddon = function (addonID) {
+  if (toolbox) {
+    toolbox.close();
+  }
+
+  toolbox = BrowserToolboxProcess.init({
+    addonID,
+    onClose: () => {
+      toolbox = null;
+    }
+  });
+};
--- a/devtools/client/aboutdebugging/modules/moz.build
+++ b/devtools/client/aboutdebugging/modules/moz.build
@@ -1,7 +1,8 @@
 # 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/.
 
 DevToolsModules(
+    'addon.js',
     'worker.js',
 )
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -22,16 +22,17 @@ const actions = Object.assign(
 const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
 const {
   Heritage,
   WidgetMethods,
   setNamedTimeout
 } = require("devtools/client/shared/widgets/view-helpers");
 const { Task } = require("devtools/shared/task");
 const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
 const FUNCTION_SEARCH_POPUP_POSITION = "topcenter bottomleft";
 const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
 const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
 
@@ -900,17 +901,17 @@ SourcesView.prototype = Heritage.extend(
     }
     clipboardHelper.copyString(selected.source.url);
   },
 
   /**
    * Opens selected item source in a new tab.
    */
   _onNewTabCommand: function () {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     let selected = this.selectedItem.attachment;
     win.openUILinkIn(selected.source.url, "tab", { relatedToCurrent: true });
   },
 
   /**
    * Function called each time a breakpoint item is removed.
    *
    * @param object aItem
--- a/devtools/client/eyedropper/eyedropper.js
+++ b/devtools/client/eyedropper/eyedropper.js
@@ -129,18 +129,20 @@ function Eyedropper(chromeWindow, opts =
 
   this._zoomArea = {
     x: 0,          // the left coordinate of the center of the inspected region
     y: 0,          // the top coordinate of the center of the inspected region
     width: CANVAS_WIDTH,      // width of canvas to draw zoomed area onto
     height: CANVAS_WIDTH      // height of canvas
   };
 
-  let mm = this._contentTab.linkedBrowser.messageManager;
-  mm.loadFrameScript("resource://devtools/client/eyedropper/eyedropper-child.js", true);
+  if (this._contentTab) {
+    let mm = this._contentTab.linkedBrowser.messageManager;
+    mm.loadFrameScript("resource://devtools/client/eyedropper/eyedropper-child.js", true);
+  }
 
   // record if this was opened via the picker or standalone
   var telemetry = new Telemetry();
   if (opts.context == "command") {
     telemetry.toolOpened("eyedropper");
   }
   else if (opts.context == "menu") {
     telemetry.toolOpened("menueyedropper");
@@ -187,26 +189,30 @@ Eyedropper.prototype = {
   get centerColor() {
     let x, y;
     x = y = (this.centerCell * this.cellSize) + (this.cellSize / 2);
     let rgb = this._ctx.getImageData(x, y, 1, 1).data;
     return rgb;
   },
 
   get _contentTab() {
-    return this._chromeWindow.gBrowser.selectedTab;
+    return this._chromeWindow.gBrowser && this._chromeWindow.gBrowser.selectedTab;
   },
 
   /**
    * Fetch a screenshot of the content.
    *
    * @return {promise}
    *         Promise that resolves with the screenshot as a dataURL
    */
   getContentScreenshot: function () {
+    if (!this._contentTab) {
+        return promise.resolve(null);
+    }
+
     let deferred = defer();
 
     let mm = this._contentTab.linkedBrowser.messageManager;
     function onScreenshot(message) {
       mm.removeMessageListener("Eyedropper:Screenshot", onScreenshot);
       deferred.resolve(message.data);
     }
     mm.addMessageListener("Eyedropper:Screenshot", onScreenshot);
@@ -219,39 +225,39 @@ Eyedropper.prototype = {
    * Start the eyedropper. Add listeners for a mouse move in the window to
    * show the eyedropper.
    */
   open: function () {
     if (this.isOpen) {
       // the eyedropper is aready open, don't create another panel.
       return promise.resolve();
     }
-    let deferred = defer();
 
     this.isOpen = true;
 
     this._showCrosshairs();
 
     // Get screenshot of content so we can inspect colors
-    this.getContentScreenshot().then((dataURL) => {
-      this._contentImage = new this._chromeWindow.Image();
-      this._contentImage.src = dataURL;
+    return this.getContentScreenshot().then((dataURL) => {
+      // The data url may be null, e.g. if there is no content tab
+      if (dataURL) {
+        this._contentImage = new this._chromeWindow.Image();
+        this._contentImage.src = dataURL;
 
-      // Wait for screenshot to load
-      this._contentImage.onload = () => {
-        // Then start showing the eyedropper UI
-        this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
-        deferred.resolve();
-
-        this.isStarted = true;
-        this.emit("started");
-      };
+        // Wait for screenshot to load
+        let imageLoaded = promise.defer();
+        this._contentImage.onload = imageLoaded.resolve
+        return imageLoaded.promise;
+      }
+    }).then(() => {
+      // Then start showing the eyedropper UI
+      this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
+      this.isStarted = true;
+      this.emit("started");
     });
-
-    return deferred.promise;
   },
 
   /**
    * Called on the first mouse move over the window. Opens the eyedropper
    * panel where the mouse is.
    */
   _onFirstMouseMove: function (event) {
     this._chromeDocument.removeEventListener("mousemove", this._onFirstMouseMove);
@@ -277,18 +283,19 @@ Eyedropper.prototype = {
    * Whether the coordinates are over the content or chrome.
    *
    * @param {number} clientX
    *        x-coordinate of mouse relative to browser window.
    * @param {number} clientY
    *        y-coordinate of mouse relative to browser window.
    */
   _isInContent: function (clientX, clientY) {
-    let box = this._contentTab.linkedBrowser.getBoundingClientRect();
-    if (clientX > box.left &&
+    let box = this._contentTab && this._contentTab.linkedBrowser.getBoundingClientRect();
+    if (box &&
+        clientX > box.left &&
         clientX < box.right &&
         clientY > box.top &&
         clientY < box.bottom) {
       return true;
     }
     return false;
   },
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -47,28 +47,28 @@ this.BrowserToolboxProcess = function Br
     emitter.emit(...args);
     BrowserToolboxProcess.emit(...args);
   };
 
   // If first argument is an object, use those properties instead of
   // all three arguments
   if (typeof aOnClose === "object") {
     if (aOnClose.onClose) {
-      this.on("close", aOnClose.onClose);
+      this.once("close", aOnClose.onClose);
     }
     if (aOnClose.onRun) {
-      this.on("run", aOnClose.onRun);
+      this.once("run", aOnClose.onRun);
     }
     this._options = aOnClose;
   } else {
     if (aOnClose) {
-      this.on("close", aOnClose);
+      this.once("close", aOnClose);
     }
     if (aOnRun) {
-      this.on("run", aOnRun);
+      this.once("run", aOnRun);
     }
     this._options = aOptions || {};
   }
 
   this._telemetry = new Telemetry();
 
   this.close = this.close.bind(this);
   Services.obs.addObserver(this.close, "quit-application", false);
@@ -127,17 +127,17 @@ BrowserToolboxProcess.prototype = {
     // invisible to the debugger (unlike the usual loader settings).
     this.loader = new DevToolsLoader();
     this.loader.invisibleToDebugger = true;
     let { DebuggerServer } = this.loader.require("devtools/server/main");
     this.debuggerServer = DebuggerServer;
     dumpn("Created a separate loader instance for the DebuggerServer.");
 
     // Forward interesting events.
-    this.debuggerServer.on("connectionchange", this.emit.bind(this));
+    this.debuggerServer.on("connectionchange", this.emit);
 
     this.debuggerServer.init();
     this.debuggerServer.addBrowserActors();
     this.debuggerServer.allowChromeProcess = true;
     dumpn("initialized and added the browser actors for the DebuggerServer.");
 
     let chromeDebuggingPort =
       Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
@@ -244,27 +244,31 @@ BrowserToolboxProcess.prototype = {
     Services.obs.removeObserver(this.close, "quit-application");
 
     if (this._dbgProcess.isRunning) {
       this._dbgProcess.kill();
     }
 
     this._telemetry.toolClosed("jsbrowserdebugger");
     if (this.debuggerServer) {
+      this.debuggerServer.off("connectionchange", this.emit);
       this.debuggerServer.destroy();
       this.debuggerServer = null;
     }
 
     dumpn("Chrome toolbox is now closed...");
     this.closed = true;
     this.emit("close", this);
     processes.delete(this);
 
     this._dbgProcess = null;
     this._options = null;
+    if (this.loader) {
+      this.loader.destroy();
+    }
     this.loader = null;
     this._telemetry = null;
   }
 };
 
 /**
  * Helper method for debugging.
  * @param string
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -733,17 +733,17 @@ gDevTools.on("tool-unregistered", functi
 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
 
 Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
 Services.obs.addObserver(gDevToolsBrowser, "browser-delayed-startup-finished", false);
 
 // Fake end of browser window load event for all already opened windows
 // that is already fully loaded.
-let enumerator = Services.wm.getEnumerator("navigator:browser");
+let enumerator = Services.wm.getEnumerator(gDevTools.chromeWindowType);
 while (enumerator.hasMoreElements()) {
   let win = enumerator.getNext();
   if (win.gBrowserInit && win.gBrowserInit.delayedStartupFinished) {
     gDevToolsBrowser._registerBrowserWindow(win);
   }
 }
 
 // Watch for module loader unload. Fires when the tools are reloaded.
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -45,16 +45,20 @@ this.DevTools = function DevTools() {
 
   // This is important step in initialization codepath where we are going to
   // start registering all default tools and themes: create menuitems, keys, emit
   // related events.
   this.registerDefaults();
 };
 
 DevTools.prototype = {
+  // The windowtype of the main window, used in various tools. This may be set
+  // to something different by other gecko apps.
+  chromeWindowType: "navigator:browser",
+
   registerDefaults() {
     // Ensure registering items in the sorted order (getDefault* functions
     // return sorted lists)
     this.getDefaultTools().forEach(definition => this.registerTool(definition));
     this.getDefaultThemes().forEach(definition => this.registerTheme(definition));
   },
 
   unregisterDefaults() {
--- a/devtools/client/framework/toolbox-init.js
+++ b/devtools/client/framework/toolbox-init.js
@@ -24,49 +24,61 @@ if (url.search.length > 1) {
   // `host` is the frame element loading the toolbox.
   let host = window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .containerElement;
 
   // Specify the default tool to open
   let tool = url.searchParams.get("tool");
 
-  if (url.searchParams.has("target")) {
-    // Attach toolbox to a given browser iframe (<xul:browser> or <html:iframe
-    // mozbrowser>) whose reference is set on the host iframe.
-
-    // `iframe` is the targeted document to debug
-    let iframe = host.wrappedJSObject ? host.wrappedJSObject.target
-                                      : host.target;
-    // Need to use a xray and query some interfaces to have
-    // attributes and behavior expected by devtools codebase
-    iframe = XPCNativeWrapper(iframe);
-    iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
+  Task.spawn(function* () {
+    let target;
+    if (url.searchParams.has("target")) {
+      // Attach toolbox to a given browser iframe (<xul:browser> or <html:iframe
+      // mozbrowser>) whose reference is set on the host iframe.
 
-    if (iframe) {
-      // Fake a xul:tab object as we don't have one.
-      // linkedBrowser is the only one attribute being queried by client.getTab
-      let tab = { linkedBrowser: iframe };
+      // `iframe` is the targeted document to debug
+      let iframe = host.wrappedJSObject ? host.wrappedJSObject.target
+                                        : host.target;
+      // Need to use a xray and query some interfaces to have
+      // attributes and behavior expected by devtools codebase
+      iframe = XPCNativeWrapper(iframe);
+      iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
 
-      if (!DebuggerServer.initialized) {
-        DebuggerServer.init();
-        DebuggerServer.addBrowserActors();
-      }
-      let client = new DebuggerClient(DebuggerServer.connectPipe());
-      Task.spawn(function* () {
+      if (iframe) {
+        // Fake a xul:tab object as we don't have one.
+        // linkedBrowser is the only one attribute being queried by client.getTab
+        let tab = { linkedBrowser: iframe };
+
+        if (!DebuggerServer.initialized) {
+          DebuggerServer.init();
+          DebuggerServer.addBrowserActors();
+        }
+        let client = new DebuggerClient(DebuggerServer.connectPipe());
+
         yield client.connect();
         // Creates a target for a given browser iframe.
         let response = yield client.getTab({ tab });
         let form = response.tab;
-        let target = yield TargetFactory.forRemoteTab({client, form, chrome: false});
-        let options = { customIframe: host };
-        yield gDevTools.showToolbox(target, tool, Toolbox.HostType.CUSTOM, options);
-      });
+        target = yield TargetFactory.forRemoteTab({client, form, chrome: false});
+      } else {
+        alert("Unable to find the targetted iframe to debug");
+      }
+    } else {
+      target = yield targetFromURL(url);
     }
-  } else {
-    targetFromURL(url).then(target => {
-      let options = { customIframe: host };
-      return gDevTools.showToolbox(target, tool, Toolbox.HostType.CUSTOM, options);
-    }).then(null, e => {
-      window.alert("Unable to start the toolbox:" + e.message);
+    let options = { customIframe: host };
+    let toolbox = yield gDevTools.showToolbox(target, tool, Toolbox.HostType.CUSTOM, options);
+
+    // Watch for toolbox.xul unload in order to cleanup things when we close
+    // about:devtools-toolbox tabs
+    function onUnload() {
+      window.removeEventListener("unload", onUnload);
+      toolbox.destroy();
+    }
+    window.addEventListener("unload", onUnload, true);
+    toolbox.on("destroy", function () {
+      window.removeEventListener("unload", onUnload);
     });
-  }
+  }).catch(error => {
+    console.error("Exception while loading the toolbox", error);
+  });
 }
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -13,16 +13,24 @@
 
 # LOCALIZATION NOTE (responsive.editDeviceList): option displayed in the device
 # selector
 responsive.editDeviceList=Edit list…
 
 # LOCALIZATION NOTE (responsive.exit): tooltip text of the exit button.
 responsive.exit=Close Responsive Design Mode
 
+# LOCALIZATION NOTE (responsive.deviceListLoading): placeholder text for
+# device selector when it's still fetching devices
+responsive.deviceListLoading=Loading…
+
+# LOCALIZATION NOTE (responsive.deviceListError): placeholder text for
+# device selector when an error occured
+responsive.deviceListError=No list available
+
 # LOCALIZATION NOTE (responsive.done): button text in the device list modal
 responsive.done=Done
 
 # LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
 # device selector
 responsive.noDeviceSelected=no device selected
 
 # LOCALIZATION NOTE  (responsive.title): the title displayed in the global
--- a/devtools/client/locales/en-US/scratchpad.properties
+++ b/devtools/client/locales/en-US/scratchpad.properties
@@ -11,18 +11,17 @@
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
 # LOCALIZATION NOTE  (export.fileOverwriteConfirmation): This is displayed when
 # the user attempts to save to an already existing file.
 export.fileOverwriteConfirmation=File exists. Overwrite?
 
 # LOCALIZATION NOTE  (browserWindow.unavailable): This error message is shown
-# when Scratchpad does not find any recently active window of navigator:browser
-# type.
+# when Scratchpad does not find any recently active main browser window.
 browserWindow.unavailable=Scratchpad cannot find any browser window to execute the code in.
 
 # LOCALIZATION NOTE  (scratchpadContext.invalid): This error message is shown
 # when user tries to run an operation in Scratchpad in an unsupported context.
 scratchpadContext.invalid=Scratchpad cannot run this operation in the current mode.
 
 # LOCALIZATION NOTE  (openFile.title): This is the file picker title, when you
 # open a file from Scratchpad.
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -25,16 +25,17 @@ const {ToolSidebar} = require("devtools/
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {setImageTooltip, getImageDimensions} =
   require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const {LocalizationHelper} = require("devtools/client/shared/l10n");
 const {PrefsHelper} = require("devtools/client/shared/prefs");
 const {ViewHelpers, Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 /**
  * Localization convenience methods.
  */
 const NET_STRINGS_URI = "chrome://devtools/locale/netmonitor.properties";
 const WEBCONSOLE_STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
 var L10N = new LocalizationHelper(NET_STRINGS_URI);
 const WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
@@ -709,17 +710,17 @@ RequestsMenuView.prototype = Heritage.ex
     this._flushRequestsTask.arm();
     return undefined;
   },
 
   /**
    * Opens selected item in a new tab.
    */
   openRequestInTab: function () {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     let selected = this.selectedItem.attachment;
     win.openUILinkIn(selected.url, "tab", { relatedToCurrent: true });
   },
 
   /**
    * Copy the request url from the currently selected item.
    */
   copyUrl: function () {
--- a/devtools/client/performance/test/helpers/tab-utils.js
+++ b/devtools/client/performance/test/helpers/tab-utils.js
@@ -4,16 +4,17 @@
 
 /* globals dump */
 
 const Services = require("Services");
 const tabs = require("sdk/tabs");
 const tabUtils = require("sdk/tabs/utils");
 const { viewFor } = require("sdk/view/core");
 const { waitForDelayedStartupFinished } = require("devtools/client/performance/test/helpers/wait-utils");
+const { gDevTools } = require("devtools/client/framework/devtools");
 
 /**
  * Gets a random integer in between an interval. Used to uniquely identify
  * added tabs by augmenting the URL.
  */
 function getRandomInt(min, max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
 }
@@ -72,13 +73,13 @@ exports.removeTab = function (tab, optio
     }
   });
 };
 
 /**
  * Adds a browser window with the provided options.
  */
 exports.addWindow = function* (options) {
-  let { OpenBrowserWindow } = Services.wm.getMostRecentWindow("navigator:browser");
+  let { OpenBrowserWindow } = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
   let win = OpenBrowserWindow(options);
   yield waitForDelayedStartupFinished(win);
   return win;
 };
--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -2,22 +2,80 @@
  * 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/. */
 
 "use strict";
 
 const {
   ADD_DEVICE,
   ADD_DEVICE_TYPE,
+  LOAD_DEVICE_LIST_START,
+  LOAD_DEVICE_LIST_ERROR,
+  LOAD_DEVICE_LIST_END,
   UPDATE_DEVICE_DISPLAYED,
   UPDATE_DEVICE_MODAL_OPEN,
 } = require("./index");
 
+const { GetDevices } = require("devtools/client/shared/devices");
+
+const Services = require("Services");
+const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
+
+/**
+ * Returns an object containing the user preference of displayed devices.
+ *
+ * @return {Object} containing two Sets:
+ * - added: Names of the devices that were explicitly enabled by the user
+ * - removed: Names of the devices that were explicitly removed by the user
+ */
+function loadPreferredDevices() {
+  let preferredDevices = {
+    "added": new Set(),
+    "removed": new Set(),
+  };
+
+  if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) {
+    try {
+      let savedData = Services.prefs.getCharPref(DISPLAYED_DEVICES_PREF);
+      savedData = JSON.parse(savedData);
+      if (savedData.added && savedData.removed) {
+        preferredDevices.added = new Set(savedData.added);
+        preferredDevices.removed = new Set(savedData.removed);
+      }
+    } catch (e) {
+      console.error(e);
+    }
+  }
+
+  return preferredDevices;
+}
+
+/**
+ * Update the displayed device list preference with the given device list.
+ *
+ * @param {Object} containing two Sets:
+ * - added: Names of the devices that were explicitly enabled by the user
+ * - removed: Names of the devices that were explicitly removed by the user
+ */
+function updatePreferredDevices(devices) {
+  let devicesToSave = {
+    added: Array.from(devices.added),
+    removed: Array.from(devices.removed),
+  };
+  devicesToSave = JSON.stringify(devicesToSave);
+  Services.prefs.setCharPref(DISPLAYED_DEVICES_PREF, devicesToSave);
+}
+
 module.exports = {
 
+  // This function is only exported for testing purposes
+  _loadPreferredDevices: loadPreferredDevices,
+
+  updatePreferredDevices: updatePreferredDevices,
+
   addDevice(device, deviceType) {
     return {
       type: ADD_DEVICE,
       device,
       deviceType,
     };
   },
 
@@ -32,16 +90,49 @@ module.exports = {
     return {
       type: UPDATE_DEVICE_DISPLAYED,
       device,
       deviceType,
       displayed,
     };
   },
 
+  loadDevices() {
+    return function* (dispatch, getState) {
+      yield dispatch({ type: LOAD_DEVICE_LIST_START });
+      let preferredDevices = loadPreferredDevices();
+      let devices;
+
+      try {
+        devices = yield GetDevices();
+      } catch (e) {
+        console.error("Could not load device list: " + e);
+        dispatch({ type: LOAD_DEVICE_LIST_ERROR });
+        return;
+      }
+
+      for (let type of devices.TYPES) {
+        dispatch(module.exports.addDeviceType(type));
+        for (let device of devices[type]) {
+          if (device.os == "fxos") {
+            continue;
+          }
+
+          let newDevice = Object.assign({}, device, {
+            displayed: preferredDevices.added.has(device.name) ||
+              (device.featured && !(preferredDevices.removed.has(device.name))),
+          });
+
+          dispatch(module.exports.addDevice(newDevice, type));
+        }
+      }
+      dispatch({ type: LOAD_DEVICE_LIST_END });
+    };
+  },
+
   updateDeviceModalOpen(isOpen) {
     return {
       type: UPDATE_DEVICE_MODAL_OPEN,
       isOpen,
     };
   },
 
 };
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This file lists all of the actions available in responsive design.  This
 // central list of constants makes it easy to see all possible action names at
 // a glance.  Please add a comment with each new action type.
 
+const { createEnum } = require("../utils/enum");
+
 createEnum([
 
   // Add a new device.
   "ADD_DEVICE",
 
   // Add a new device type.
   "ADD_DEVICE_TYPE",
 
@@ -36,27 +38,24 @@ createEnum([
   "TAKE_SCREENSHOT_START",
 
   // Indicates when the screenshot action ends.
   "TAKE_SCREENSHOT_END",
 
   // Update the device display state in the device selector.
   "UPDATE_DEVICE_DISPLAYED",
 
+  // Indicates that the device list is being loaded
+  "LOAD_DEVICE_LIST_START",
+
+  // Indicates that the device list loading action threw an error
+  "LOAD_DEVICE_LIST_ERROR",
+
+  // Indicates that the device list has been loaded successfully
+  "LOAD_DEVICE_LIST_END",
+
   // Update the device modal open state.
   "UPDATE_DEVICE_MODAL_OPEN",
 
   // Update the touch simulation enabled state.
   "UPDATE_TOUCH_SIMULATION_ENABLED",
 
 ], module.exports);
-
-/**
- * Create a simple enum-like object with keys mirrored to values from an array.
- * This makes comparison to a specfic value simpler without having to repeat and
- * mis-type the value.
- */
-function createEnum(array, target) {
-  for (let key of array) {
-    target[key] = key;
-  }
-  return target;
-}
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -8,28 +8,28 @@
 
 const { createClass, createFactory, PropTypes, DOM: dom } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
   updateDeviceDisplayed,
   updateDeviceModalOpen,
+  updatePreferredDevices,
 } = require("./actions/devices");
 const {
   changeDevice,
   resizeViewport,
   rotateViewport
 } = require("./actions/viewports");
 const { takeScreenshot } = require("./actions/screenshot");
 const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
 const DeviceModal = createFactory(require("./components/device-modal"));
 const GlobalToolbar = createFactory(require("./components/global-toolbar"));
 const Viewports = createFactory(require("./components/viewports"));
-const { updatePreferredDevices } = require("./devices");
 const Types = require("./types");
 
 let App = createClass({
   displayName: "App",
 
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
     location: Types.location.isRequired,
@@ -77,25 +77,23 @@ let App = createClass({
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
   },
 
   onUpdateDeviceModalOpen(isOpen) {
     this.props.dispatch(updateDeviceModalOpen(isOpen));
   },
 
-  onUpdateTouchSimulationEnabled() {
-    let { enabled } = this.props.touchSimulation;
-
+  onUpdateTouchSimulation(isEnabled) {
     window.postMessage({
       type: "update-touch-simulation",
-      enabled,
+      enabled: isEnabled,
     }, "*");
 
-    this.props.dispatch(updateTouchSimulationEnabled(!enabled));
+    this.props.dispatch(updateTouchSimulationEnabled(isEnabled));
   },
 
   render() {
     let {
       devices,
       location,
       screenshot,
       touchSimulation,
@@ -108,29 +106,29 @@ let App = createClass({
       onContentResize,
       onDeviceListUpdate,
       onExit,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModalOpen,
-      onUpdateTouchSimulationEnabled,
+      onUpdateTouchSimulation,
     } = this;
 
     return dom.div(
       {
         id: "app",
       },
       GlobalToolbar({
         screenshot,
         touchSimulation,
         onExit,
         onScreenshot,
-        onUpdateTouchSimulationEnabled,
+        onUpdateTouchSimulation,
       }),
       Viewports({
         devices,
         location,
         screenshot,
         viewports,
         onBrowserMounted,
         onChangeViewportDevice,
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -68,32 +68,51 @@ module.exports = createClass({
       return a.name.localeCompare(b.name);
     });
 
     let selectClass = "viewport-device-selector";
     if (selectedDevice) {
       selectClass += " selected";
     }
 
+    let state = devices.listState;
+    let listContent;
+
+    if (state == Types.deviceListState.LOADED) {
+      listContent = [dom.option({
+        value: "",
+        disabled: true,
+        hidden: true,
+      }, getStr("responsive.noDeviceSelected")),
+        options.map(device => {
+          return dom.option({
+            key: device.name,
+            value: device.name,
+          }, device.name);
+        }),
+        dom.option({
+          value: OPEN_DEVICE_MODAL_VALUE,
+        }, getStr("responsive.editDeviceList"))];
+    } else if (state == Types.deviceListState.LOADING
+      || state == Types.deviceListState.INITIALIZED) {
+      listContent = [dom.option({
+        value: "",
+        disabled: true,
+      }, getStr("responsive.deviceListLoading"))];
+    } else if (state == Types.deviceListState.ERROR) {
+      listContent = [dom.option({
+        value: "",
+        disabled: true,
+      }, getStr("responsive.deviceListError"))];
+    }
+
     return dom.select(
       {
         className: selectClass,
         value: selectedDevice,
         onChange: this.onSelectChange,
+        disabled: (state !== Types.deviceListState.LOADED),
       },
-      dom.option({
-        value: "",
-        disabled: true,
-        hidden: true,
-      }, getStr("responsive.noDeviceSelected")),
-      options.map(device => {
-        return dom.option({
-          key: device.name,
-          value: device.name,
-        }, device.name);
-      }),
-      dom.option({
-        value: OPEN_DEVICE_MODAL_VALUE,
-      }, getStr("responsive.editDeviceList"))
+      ...listContent
     );
   },
 
 });
--- a/devtools/client/responsive.html/components/global-toolbar.js
+++ b/devtools/client/responsive.html/components/global-toolbar.js
@@ -12,28 +12,28 @@ const Types = require("../types");
 module.exports = createClass({
   displayName: "GlobalToolbar",
 
   propTypes: {
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     onExit: PropTypes.func.isRequired,
     onScreenshot: PropTypes.func.isRequired,
-    onUpdateTouchSimulationEnabled: PropTypes.func.isRequired,
+    onUpdateTouchSimulation: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
       screenshot,
       touchSimulation,
       onExit,
       onScreenshot,
-      onUpdateTouchSimulationEnabled
+      onUpdateTouchSimulation
     } = this.props;
 
     let touchButtonClass = "toolbar-button devtools-button";
     if (touchSimulation.enabled) {
       touchButtonClass += " active";
     }
 
     return dom.header(
@@ -44,17 +44,17 @@ module.exports = createClass({
       dom.span(
         {
           className: "title",
         },
         getStr("responsive.title")),
       dom.button({
         id: "global-touch-simulation-button",
         className: touchButtonClass,
-        onClick: onUpdateTouchSimulationEnabled,
+        onClick: () => onUpdateTouchSimulation(!touchSimulation.enabled),
       }),
       dom.button({
         id: "global-screenshot-button",
         className: "toolbar-button devtools-button",
         title: getStr("responsive.screenshot"),
         onClick: onScreenshot,
         disabled: screenshot.isCapturing,
       }),
deleted file mode 100644
--- a/devtools/client/responsive.html/devices.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-const Services = require("Services");
-const { Task } = require("devtools/shared/task");
-const { GetDevices } = require("devtools/client/shared/devices");
-const { addDevice, addDeviceType } = require("./actions/devices");
-
-const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
-
-/**
- * Get the device catalog and load the devices onto the store.
- *
- * @param  {Function} dispatch
- *         Action dispatch function
- */
-let initDevices = Task.async(function* (dispatch) {
-  let preferredDevices = loadPreferredDevices();
-  let devices = yield GetDevices();
-
-  for (let type of devices.TYPES) {
-    dispatch(addDeviceType(type));
-    for (let device of devices[type]) {
-      if (device.os == "fxos") {
-        continue;
-      }
-
-      let newDevice = Object.assign({}, device, {
-        displayed: preferredDevices.added.has(device.name) ||
-          (device.featured && !(preferredDevices.removed.has(device.name))),
-      });
-
-      dispatch(addDevice(newDevice, type));
-    }
-  }
-});
-
-/**
- * Returns an object containing the user preference of displayed devices.
- *
- * @return {Object} containing two Sets:
- * - added: Names of the devices that were explicitly enabled by the user
- * - removed: Names of the devices that were explicitly removed by the user
- */
-function loadPreferredDevices() {
-  let preferredDevices = {
-    "added": new Set(),
-    "removed": new Set(),
-  };
-
-  if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) {
-    try {
-      let savedData = Services.prefs.getCharPref(DISPLAYED_DEVICES_PREF);
-      savedData = JSON.parse(savedData);
-      if (savedData.added && savedData.removed) {
-        preferredDevices.added = new Set(savedData.added);
-        preferredDevices.removed = new Set(savedData.removed);
-      }
-    } catch (e) {
-      console.error(e);
-    }
-  }
-
-  return preferredDevices;
-}
-
-/**
- * Update the displayed device list preference with the given device list.
- *
- * @param {Object} containing two Sets:
- * - added: Names of the devices that were explicitly enabled by the user
- * - removed: Names of the devices that were explicitly removed by the user
- */
-function updatePreferredDevices(devices) {
-  let devicesToSave = {
-    added: Array.from(devices.added),
-    removed: Array.from(devices.removed),
-  };
-  devicesToSave = JSON.stringify(devicesToSave);
-  Services.prefs.setCharPref(DISPLAYED_DEVICES_PREF, devicesToSave);
-}
-
-exports.initDevices = initDevices;
-exports.loadPreferredDevices = loadPreferredDevices;
-exports.updatePreferredDevices = updatePreferredDevices;
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -182,16 +182,17 @@ html, body {
   background-size: 7px;
   border: none;
   color: var(--viewport-color);
   height: 100%;
   padding: 0 16px 0 0;
   text-align: center;
   text-overflow: ellipsis;
   width: 150px;
+  font-size: 11px;
 }
 
 .viewport-device-selector.selected {
   background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
 .viewport-device-selector:hover {
@@ -377,23 +378,27 @@ html, body {
 
 .device-type {
   display: flex;
   flex-direction: column;
   padding: 10px;
 }
 
 .device-header {
+  font-size: 11px;
   font-weight: bold;
   text-transform: capitalize;
   padding: 0 0 3px 23px;
 }
 
 .device-label {
+  font-size: 11px;
   padding-bottom: 3px;
+  display: flex;
+  align-items: center;
 }
 
 .device-input-checkbox {
   margin-right: 5px;
 }
 
 #device-submit-button {
   background-color: var(--theme-tab-toolbar-background);
--- a/devtools/client/responsive.html/index.js
+++ b/devtools/client/responsive.html/index.js
@@ -18,37 +18,37 @@ const Telemetry = require("devtools/clie
 const { loadSheet } = require("sdk/stylesheet/utils");
 
 const { createFactory, createElement } =
   require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const message = require("./utils/message");
-const { initDevices } = require("./devices");
 const App = createFactory(require("./app"));
 const Store = require("./store");
 const { changeLocation } = require("./actions/location");
 const { addViewport, resizeViewport } = require("./actions/viewports");
+const { loadDevices } = require("./actions/devices");
 
 let bootstrap = {
 
   telemetry: new Telemetry(),
 
   store: null,
 
   init: Task.async(function* () {
     // Load a special UA stylesheet to reset certain styles such as dropdown
     // lists.
     loadSheet(window,
               "resource://devtools/client/responsive.html/responsive-ua.css",
               "agent");
     this.telemetry.toolOpened("responsive");
     let store = this.store = Store();
-    yield initDevices(this.dispatch.bind(this));
+    this.dispatch(loadDevices());
     let provider = createElement(Provider, { store }, App());
     ReactDOM.render(provider, document.querySelector("#root"));
     message.post(window, "init:done");
   }),
 
   destroy() {
     this.store = null;
     this.telemetry.toolClosed("responsive");
@@ -116,17 +116,26 @@ window.setViewportSize = (width, height)
   try {
     bootstrap.dispatch(resizeViewport(0, width, height));
   } catch (e) {
     console.error(e);
   }
 };
 
 /**
- * Called by manager.js when tests want to use the viewport's browser to access
- * the content inside.  We mock the format of a <xul:browser> to make this
- * easily usable with ContentTask.spawn(), which expects an object with a
- * `messageManager` property.
+ * Called by manager.js to access the viewport's browser, either for testing
+ * purposes or to reload it when touch simulation is enabled.
+ * A messageManager getter is added on the object to provide an easy access
+ * to the message manager without pulling the frame loader.
  */
 window.getViewportBrowser = () => {
-  let { messageManager } = document.querySelector("iframe.browser").frameLoader;
-  return { messageManager };
+  let browser = document.querySelector("iframe.browser");
+  if (!browser.messageManager) {
+    Object.defineProperty(browser, "messageManager", {
+      get() {
+        return this.frameLoader.messageManager;
+      },
+      configurable: true,
+      enumerable: true,
+    });
+  }
+  return browser;
 };
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -272,18 +272,17 @@ ResponsiveUI.prototype = {
       tab: this.tab,
       containerURL: TOOL_URL,
       getInnerBrowser: Task.async(function* (containerBrowser) {
         let toolWindow = ui.toolWindow = containerBrowser.contentWindow;
         toolWindow.addEventListener("message", ui);
         yield message.request(toolWindow, "init");
         toolWindow.addInitialViewport("about:blank");
         yield message.wait(toolWindow, "browser-mounted");
-        toolViewportContentBrowser =
-          toolWindow.document.querySelector("iframe.browser");
+        toolViewportContentBrowser = ui.getViewportBrowser();
         return toolViewportContentBrowser;
       })
     });
     yield this.swap.start();
 
     // Notify the inner browser to start the frame script
     yield message.request(this.toolWindow, "start-frame-script");
 
@@ -395,17 +394,20 @@ ResponsiveUI.prototype = {
         let { enabled } = event.data;
         this.updateTouchSimulation(enabled);
         break;
     }
   },
 
   updateTouchSimulation: Task.async(function* (enabled) {
     if (enabled) {
-      this.touchEventSimulator.start();
+      let reloadNeeded = yield this.touchEventSimulator.start();
+      if (reloadNeeded) {
+        this.getViewportBrowser().reload();
+      }
     } else {
       this.touchEventSimulator.stop();
     }
   }),
 
   /**
    * Helper for tests. Assumes a single viewport for now.
    */
@@ -417,17 +419,17 @@ ResponsiveUI.prototype = {
    * Helper for tests. Assumes a single viewport for now.
    */
   setViewportSize: Task.async(function* (width, height) {
     yield this.inited;
     this.toolWindow.setViewportSize(width, height);
   }),
 
   /**
-   * Helper for tests. Assumes a single viewport for now.
+   * Helper for tests/reloading the viewport. Assumes a single viewport for now.
    */
   getViewportBrowser() {
     return this.toolWindow.getViewportBrowser();
   },
 
 };
 
 EventEmitter.decorate(ResponsiveUI.prototype);
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/moz.build
@@ -12,17 +12,16 @@ DIRS += [
     'images',
     'reducers',
     'utils',
 ]
 
 DevToolsModules(
     'app.js',
     'constants.js',
-    'devices.js',
     'index.css',
     'manager.js',
     'reducers.js',
     'responsive-ua.css',
     'store.js',
     'types.js',
 )
 
--- a/devtools/client/responsive.html/reducers/devices.js
+++ b/devtools/client/responsive.html/reducers/devices.js
@@ -2,23 +2,29 @@
  * 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/. */
 
 "use strict";
 
 const {
   ADD_DEVICE,
   ADD_DEVICE_TYPE,
+  LOAD_DEVICE_LIST_START,
+  LOAD_DEVICE_LIST_ERROR,
+  LOAD_DEVICE_LIST_END,
   UPDATE_DEVICE_DISPLAYED,
   UPDATE_DEVICE_MODAL_OPEN,
 } = require("../actions/index");
 
+const Types = require("../types");
+
 const INITIAL_DEVICES = {
   types: [],
   isModalOpen: false,
+  listState: Types.deviceListState.INITIALIZED,
 };
 
 let reducers = {
 
   [ADD_DEVICE](devices, { device, deviceType }) {
     return Object.assign({}, devices, {
       [deviceType]: [...devices[deviceType], device],
     });
@@ -40,16 +46,34 @@ let reducers = {
       return d;
     });
 
     return Object.assign({}, devices, {
       [deviceType]: newDevices,
     });
   },
 
+  [LOAD_DEVICE_LIST_START](devices, action) {
+    return Object.assign({}, devices, {
+      listState: Types.deviceListState.LOADING,
+    });
+  },
+
+  [LOAD_DEVICE_LIST_ERROR](devices, action) {
+    return Object.assign({}, devices, {
+      listState: Types.deviceListState.ERROR,
+    });
+  },
+
+  [LOAD_DEVICE_LIST_END](devices, action) {
+    return Object.assign({}, devices, {
+      listState: Types.deviceListState.LOADED,
+    });
+  },
+
   [UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
     return Object.assign({}, devices, {
       isModalOpen: isOpen,
     });
   },
 
 };
 
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -6,16 +6,17 @@ skip-if = !e10s
 support-files =
   devices.json
   doc_page_state.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/framework/test/shared-redux-head.js
 
+[browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
 [browser_exit_button.js]
 [browser_frame_script_active.js]
 [browser_menu_item_01.js]
 [browser_menu_item_02.js]
 [browser_mouse_resize.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_error.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test to check that RDM can handle properly an error in the device list
+
+const TEST_URL = "data:text/html;charset=utf-8,";
+const Types = require("devtools/client/responsive.html/types");
+const { getStr } = require("devtools/client/responsive.html/utils/l10n");
+
+// Set a wrong URL for the device list file
+add_task(function* () {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["devtools.devices.url", TEST_URI_ROOT + "wrong_devices_file.json"]],
+  });
+});
+
+addRDMTask(TEST_URL, function* ({ ui }) {
+  let { store, document } = ui.toolWindow;
+  let select = document.querySelector(".viewport-device-selector");
+
+  // Wait until the viewport has been added and the device list state indicates
+  // an error
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.ERROR);
+
+  // The device selector placeholder should be set accordingly
+  let placeholder = select.options[select.selectedIndex].innerHTML;
+  ok(placeholder == getStr("responsive.deviceListError"),
+    "Device selector indicates an error");
+
+  // The device selector should be disabled
+  ok(select.disabled, "Device selector is disabled");
+});
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
@@ -1,41 +1,43 @@
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test submitting display device changes on the device modal
 
 const TEST_URL = "data:text/html;charset=utf-8,";
+const Types = require("devtools/client/responsive.html/types");
 
 addRDMTask(TEST_URL, function* ({ ui }) {
   let { store, document } = ui.toolWindow;
   let modal = document.querySelector(".device-modal");
   let closeButton = document.querySelector("#device-close-button");
 
-  // Wait until the viewport has been added
-  yield waitUntilState(store, state => state.viewports.length == 1);
+  // Wait until the viewport has been added and the device list has been loaded
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.LOADED);
 
   openDeviceModal(ui);
 
-  let preferredDevicesBefore = loadPreferredDevices();
+  let preferredDevicesBefore = _loadPreferredDevices();
 
   info("Check the first unchecked device and exit the modal.");
   let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => !cb.checked)[0];
   let value = uncheckedCb.value;
   uncheckedCb.click();
   closeButton.click();
 
   ok(modal.classList.contains("hidden"),
     "The device modal is hidden on exit.");
 
   info("Check that the device list remains unchanged after exitting.");
-  let preferredDevicesAfter = loadPreferredDevices();
+  let preferredDevicesAfter = _loadPreferredDevices();
 
   is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size,
     "Got expected number of added devices.");
 
   is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size,
     "Got expected number of removed devices.");
 
   ok(!preferredDevicesAfter.removed.has(value),
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
@@ -14,25 +14,27 @@ const addedDevice = {
   "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
   "touch": true,
   "firefoxOS": false,
   "os": "custom",
   "featured": true,
 };
 
 const TEST_URL = "data:text/html;charset=utf-8,";
+const Types = require("devtools/client/responsive.html/types");
 
 addRDMTask(TEST_URL, function* ({ ui }) {
   let { store, document } = ui.toolWindow;
   let modal = document.querySelector(".device-modal");
   let select = document.querySelector(".viewport-device-selector");
   let submitButton = document.querySelector("#device-submit-button");
 
-  // Wait until the viewport has been added
-  yield waitUntilState(store, state => state.viewports.length == 1);
+  // Wait until the viewport has been added and the device list has been loaded
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.LOADED);
 
   openDeviceModal(ui);
 
   info("Checking displayed device checkboxes are checked in the device modal.");
   let checkedCbs = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => cb.checked);
 
   let remoteList = yield GetDevices();
@@ -58,17 +60,17 @@ addRDMTask(TEST_URL, function* ({ ui }) 
   let value = uncheckedCb.value;
   uncheckedCb.click();
   submitButton.click();
 
   ok(modal.classList.contains("hidden"),
     "The device modal is hidden on submit.");
 
   info("Checking that the new device is added to the user preference list.");
-  let preferredDevices = loadPreferredDevices();
+  let preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.added.has(value), value + " in user added list.");
 
   info("Checking new device is added to the device selector.");
   let options = [...select.options];
   is(options.length - 2, featuredCount + 1,
     "Got expected number of devices in device selector.");
   ok(options.filter(o => o.value === value)[0],
     value + " added to the device selector.");
@@ -83,17 +85,17 @@ addRDMTask(TEST_URL, function* ({ ui }) 
   info("Uncheck the first checked device different than the previous one");
   let checkedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => cb.checked && cb.value != value)[0];
   let checkedVal = checkedCb.value;
   checkedCb.click();
   submitButton.click();
 
   info("Checking that the device is removed from the user preference list.");
-  preferredDevices = loadPreferredDevices();
+  preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.removed.has(checkedVal), checkedVal + " in removed list");
 
   info("Checking that the device is not in the device selector.");
   options = [...select.options];
   is(options.length - 2, featuredCount,
     "Got expected number of devices in device selector.");
   ok(!options.filter(o => o.value === checkedVal)[0],
     checkedVal + " removed from the device selector.");
@@ -118,17 +120,17 @@ addRDMTask(TEST_URL, function* ({ ui }) 
   openDeviceModal(ui);
 
   let remoteList = yield GetDevices();
   let featuredCount = remoteList.TYPES.reduce((total, type) => {
     return total + remoteList[type].reduce((subtotal, device) => {
       return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0);
     }, 0);
   }, 0);
-  let preferredDevices = loadPreferredDevices();
+  let preferredDevices = _loadPreferredDevices();
 
   // Tests to prove that reloading the RDM didn't break our device list
   info("Checking new featured device appears in the device selector.");
   let options = [...select.options];
   is(options.length - 2, featuredCount
     - preferredDevices.removed.size + preferredDevices.added.size,
     "Got expected number of devices in device selector.");
 
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -33,17 +33,17 @@ Services.prefs.setBoolPref("devtools.res
 
 registerCleanupFunction(() => {
   DevToolsUtils.testing = false;
   Services.prefs.clearUserPref("devtools.devices.url");
   Services.prefs.clearUserPref("devtools.responsive.html.enabled");
   Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
 });
 const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
-const { loadPreferredDevices } = require("devtools/client/responsive.html/devices");
+const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices");
 const { getOwnerWindow } = require("sdk/tabs/utils");
 
 const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
 
 /**
  * Open responsive design mode for the given tab.
  */
 var openRDM = Task.async(function* (tab) {
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -1,15 +1,16 @@
 /* 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/. */
 
 "use strict";
 
 const { PropTypes } = require("devtools/client/shared/vendor/react");
+const { createEnum } = require("./utils/enum");
 
 // React PropTypes are used to describe the expected "shape" of various common
 // objects that get passed down as props to components.
 
 /**
  * A single device that can be displayed in the viewport.
  */
 const device = {
@@ -36,16 +37,26 @@ const device = {
   os: PropTypes.String,
 
   // Whether or not the device is displayed in the device selector
   displayed: PropTypes.bool,
 
 };
 
 /**
+ * An enum containing the possible values for the device list state
+ */
+exports.deviceListState = createEnum([
+  "INITIALIZED",
+  "LOADING",
+  "LOADED",
+  "ERROR",
+]);
+
+/**
  * A list of devices and their types that can be displayed in the viewport.
  */
 exports.devices = {
 
   // An array of device types
   types: PropTypes.arrayOf(PropTypes.string),
 
   // An array of phone devices
@@ -64,16 +75,19 @@ exports.devices = {
   consoles: PropTypes.arrayOf(PropTypes.shape(device)),
 
   // An array of watch devices
   watches: PropTypes.arrayOf(PropTypes.shape(device)),
 
   // Whether or not the device modal is open
   isModalOpen: PropTypes.bool,
 
+  // Device list state, possible values are exported above in an enum
+  listState: PropTypes.oneOf(Object.keys(exports.deviceListState)),
+
 };
 
 /**
  * The location of the document displayed in the viewport(s).
  */
 exports.location = PropTypes.string;
 
 /**
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/utils/enum.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+"use strict";
+
+module.exports = {
+
+  /**
+   * Create a simple enum-like object with keys mirrored to values from an array.
+   * This makes comparison to a specfic value simpler without having to repeat and
+   * mis-type the value.
+   */
+  createEnum(array, target = {}) {
+    for (let key of array) {
+      target[key] = key;
+    }
+    return target;
+  }
+
+};
--- a/devtools/client/responsive.html/utils/moz.build
+++ b/devtools/client/responsive.html/utils/moz.build
@@ -1,11 +1,12 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'e10s.js',
+    'enum.js',
     'l10n.js',
     'message.js',
 )
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -429,21 +429,21 @@ var Scratchpad = {
 
     if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER)
       this.setBrowserContext();
     else
       this.setContentContext();
   },
 
   /**
-   * Get the most recent chrome window of type navigator:browser.
+   * Get the most recent main chrome browser window
    */
   get browserWindow()
   {
-    return Services.wm.getMostRecentWindow("navigator:browser");
+    return Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
   },
 
   /**
    * Get the gBrowser object of the most recent browser window.
    */
   get gBrowser()
   {
     let recentWin = this.browserWindow;
@@ -2056,19 +2056,18 @@ var Scratchpad = {
   },
 
   /**
    * Opens the MDN documentation page for Scratchpad.
    */
   openDocumentationPage: function SP_openDocumentationPage()
   {
     let url = this.strings.GetStringFromName("help.openDocumentationPage");
-    let newTab = this.gBrowser.addTab(url);
+    this.browserWindow.openUILinkIn(url,"tab");
     this.browserWindow.focus();
-    this.gBrowser.selectedTab = newTab;
   },
 };
 
 
 /**
  * Represents the DebuggerClient connection to a specific tab as used by the
  * Scratchpad.
  *
--- a/devtools/client/shared/AppCacheUtils.jsm
+++ b/devtools/client/shared/AppCacheUtils.jsm
@@ -26,16 +26,18 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 var { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 var { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 var { LoadContextInfo } = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+var { gDevTools } = require("devtools/client/framework/devtools");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 
 this.EXPORTED_SYMBOLS = ["AppCacheUtils"];
 
 function AppCacheUtils(documentOrUri) {
   this._parseManifest = this._parseManifest.bind(this);
@@ -289,19 +291,19 @@ AppCacheUtils.prototype = {
       throw new Error(l10n.GetStringFromName("noResults"));
     }
     return entries;
   },
 
   viewEntry: function ACU_viewEntry(key) {
     let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
                .getService(Ci.nsIWindowMediator);
-    let win = wm.getMostRecentWindow("navigator:browser");
-    win.gBrowser.selectedTab = win.gBrowser.addTab(
-      "about:cache-entry?storage=appcache&context=&eid=&uri=" + key);
+    let win = wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    let url = "about:cache-entry?storage=appcache&context=&eid=&uri=" + key;
+    win.openUILinkIn(url, "tab");
   },
 
   clearAll: function ACU_clearAll() {
     if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
       throw new Error(l10n.GetStringFromName("cacheDisabled"));
     }
 
     let appCacheStorage = Services.cache2.appCacheStorage(LoadContextInfo.default, null);
--- a/devtools/client/shared/devices.js
+++ b/devtools/client/shared/devices.js
@@ -1,17 +1,16 @@
 /* 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/. */
 
 "use strict";
 
 const { getJSON } = require("devtools/client/shared/getjson");
 const Services = require("Services");
-const defer = require("devtools/shared/defer");
 
 const DEVICES_URL = "devtools.devices.url";
 const Strings = Services.strings.createBundle("chrome://devtools/locale/device.properties");
 
 /* This is a catalog of common web-enabled devices and their properties,
  * intended for (mobile) device emulation.
  *
  * The properties of a device are:
@@ -43,31 +42,27 @@ function AddDevice(device, type = "phone
     list = localDevices[type] = [];
   }
   list.push(device);
 }
 exports.AddDevice = AddDevice;
 
 // Get the complete devices catalog.
 function GetDevices() {
-  let deferred = defer();
-
   // Fetch common devices from Mozilla's CDN.
-  getJSON(DEVICES_URL).then(devices => {
+  return getJSON(DEVICES_URL).then(devices => {
     for (let type in localDevices) {
       if (!devices[type]) {
         devices.TYPES.push(type);
         devices[type] = [];
       }
       devices[type] = localDevices[type].concat(devices[type]);
     }
-    deferred.resolve(devices);
+    return devices;
   });
-
-  return deferred.promise;
 }
 exports.GetDevices = GetDevices;
 
 // Get the localized string for a device type.
 function GetDeviceString(deviceType) {
   return Strings.GetStringFromName("device." + deviceType);
 }
 exports.GetDeviceString = GetDeviceString;
--- a/devtools/client/shared/getjson.js
+++ b/devtools/client/shared/getjson.js
@@ -1,16 +1,17 @@
 /* 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/. */
 
 "use strict";
 
 const {CC} = require("chrome");
 const defer = require("devtools/shared/defer");
+const promise = require("promise");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
 
 const XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
 
 /**
  * Downloads and caches a JSON file from an URL given by a pref.
@@ -36,17 +37,20 @@ exports.getJSON = function (prefName) {
       // Could not move the cache, let's log the error but continue
       console.error(e);
     });
     Services.prefs.clearUserPref(prefName + "_cache");
   }
 
   function readFromStorage(networkError) {
     asyncStorage.getItem(prefName + "_cache").then(function (json) {
-      deferred.resolve(json);
+      if (!json) {
+        return promise.reject("Empty cache for " + prefName);
+      }
+      return deferred.resolve(json);
     }).catch(function (e) {
       deferred.reject("JSON not available, CDN error: " + networkError +
                       ", storage error: " + e);
     });
   }
 
   xhr.onload = () => {
     try {
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_storage.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
-const TOOL_DELAY = 200;
+const TOOL_DELAY = 1000;
 
 add_task(function* () {
   info("Activating the storage inspector");
   Services.prefs.setBoolPref("devtools.storage.enabled", true);
 
   yield addTab(TEST_URI);
   let Telemetry = loadTelemetryAndRecordLogs();
 
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -2,17 +2,17 @@
  * 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/. */
 
 "use strict";
 
 var { Task } = require("devtools/shared/task");
 
 var Services = require("Services");
-var {gDevTools} = require("devtools/client/framework/devtools");
+var { gDevTools } = require("devtools/client/framework/devtools");
 var { getSourceText } = require("devtools/client/debugger/content/queries");
 
 /**
  * Tries to open a Stylesheet file in the Style Editor. If the file is not
  * found, it is opened in source view instead.
  * Returns a promise resolving to a boolean indicating whether or not
  * the source was able to be displayed in the StyleEditor, as the built-in
  * Firefox View Source is the fallback.
@@ -152,18 +152,18 @@ exports.viewSourceInScratchpad = Task.as
  * @param {string} sourceURL
  * @param {number} sourceLine
  *
  * @return {Promise}
  */
 exports.viewSource = Task.async(function* (toolbox, sourceURL, sourceLine) {
   // Attempt to access view source via a browser first, which may display it in
   // a tab, if enabled.
-  let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-  if (browserWin) {
+  let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+  if (browserWin && browserWin.BrowserViewSourceOfDocument) {
     return browserWin.BrowserViewSourceOfDocument({
       URL: sourceURL,
       lineNumber: sourceLine
     });
   }
   let utils = toolbox.gViewSourceUtils;
   utils.viewSource(sourceURL, null, toolbox.doc, sourceLine || 0);
   return null;
--- a/devtools/client/shared/widgets/MdnDocsWidget.js
+++ b/devtools/client/shared/widgets/MdnDocsWidget.js
@@ -22,33 +22,32 @@
  * the content in a tooltip, use this in conjunction with Tooltip.js.
  */
 
 "use strict";
 
 const Services = require("Services");
 const defer = require("devtools/shared/defer");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
 // Parameters for the XHR request
 // see https://developer.mozilla.org/en-US/docs/MDN/Kuma/API#Document_parameters
 const XHR_PARAMS = "?raw&macros";
 // URL for the XHR request
 var XHR_CSS_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/";
 
 // Parameters for the link to MDN in the tooltip, so
 // so we know which MDN visits come from this feature
 const PAGE_LINK_PARAMS =
   "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
 // URL for the page link omits locale, so a locale-specific page will be loaded
 var PAGE_LINK_URL = "https://developer.mozilla.org/docs/Web/CSS/";
 exports.PAGE_LINK_URL = PAGE_LINK_URL;
 
-const BROWSER_WINDOW = "navigator:browser";
-
 const PROPERTY_NAME_COLOR = "theme-fg-color5";
 const PROPERTY_VALUE_COLOR = "theme-fg-color1";
 const COMMENT_COLOR = "theme-comment";
 
 /**
  * Turns a string containing a series of CSS declarations into
  * a series of DOM nodes, with classes applied to provide syntax
  * highlighting.
@@ -251,22 +250,21 @@ function MdnDocsWidget(tooltipDocument) 
 
   this.doc = tooltipDocument;
 
   // get the localized string for the link text
   this.elements.linkToMdn.textContent =
     l10n.strings.GetStringFromName("docsTooltip.visitMDN");
 
   // listen for clicks and open in the browser window instead
-  let browserWindow = Services.wm.getMostRecentWindow(BROWSER_WINDOW);
+  let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
   this.elements.linkToMdn.addEventListener("click", function (e) {
     e.stopPropagation();
     e.preventDefault();
-    let link = e.target.href;
-    browserWindow.gBrowser.addTab(link);
+    mainWindow.openUILinkIn(e.target.href, "tab", { inBackground: true });
   });
 }
 
 exports.MdnDocsWidget = MdnDocsWidget;
 
 MdnDocsWidget.prototype = {
   /**
    * This is called just before the tooltip is displayed, and is
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -11,16 +11,17 @@ const {CubicBezierWidget} =
       require("devtools/client/shared/widgets/CubicBezierWidget");
 const {MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
 const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
 const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {colorUtils} = require("devtools/client/shared/css-color");
 const Heritage = require("sdk/core/heritage");
 const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
+const {gDevTools} = require("devtools/client/framework/devtools");
 const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
 loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
 loader.lazyRequireGetter(this, "clearNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
 loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
 
@@ -915,21 +916,21 @@ Heritage.extend(SwatchBasedEditorTooltip
     }
   },
 
   _openEyeDropper: function () {
     let chromeWindow = this.tooltip.doc.defaultView.top;
     let windowType = chromeWindow.document.documentElement
                      .getAttribute("windowtype");
     let toolboxWindow;
-    if (windowType != "navigator:browser") {
+    if (windowType != gDevTools.chromeWindowType) {
       // this means the toolbox is in a seperate window. We need to make
       // sure we'll be inspecting the browser window instead
       toolboxWindow = chromeWindow;
-      chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
+      chromeWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
       chromeWindow.focus();
     }
     let dropper = new Eyedropper(chromeWindow, { copyOnSelect: false,
                                                  context: "picker" });
 
     dropper.once("select", (event, color) => {
       if (toolboxWindow) {
         toolboxWindow.focus();
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -1,10 +1,8 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft= javascript ts=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/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
@@ -60,22 +58,22 @@ HUD_SERVICE.prototype =
    *
    * @type object
    *       Includes a property named |callback|. Assign the function to the
    *       |callback| property of this object.
    */
   lastFinishedRequest: null,
 
   /**
-   * Firefox-specific current tab getter
+   * Get the current context, which is the main application window.
    *
    * @returns nsIDOMWindow
    */
   currentContext: function HS_currentContext() {
-    return Services.wm.getMostRecentWindow("navigator:browser");
+    return Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
   },
 
   /**
    * Open a Web Console for the given target.
    *
    * @see devtools/framework/target.js for details about targets.
    *
    * @param object aTarget
@@ -290,17 +288,17 @@ function WebConsole(aTarget, aIframeWind
   this.iframeWindow = aIframeWindow;
   this.chromeWindow = aChromeWindow;
   this.hudId = "hud_" + ++gHudId;
   this.target = aTarget;
 
   this.browserWindow = this.chromeWindow.top;
 
   let element = this.browserWindow.document.documentElement;
-  if (element.getAttribute("windowtype") != "navigator:browser") {
+  if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
     this.browserWindow = HUDService.currentContext();
   }
 
   this.ui = new WebConsoleFrame(this);
 }
 
 WebConsole.prototype = {
   iframeWindow: null,
@@ -433,17 +431,17 @@ WebConsole.prototype = {
    * @param string aSourceURL
    *        The URL of the file.
    * @param integer aSourceLine
    *        The line number which should be highlighted.
    */
   viewSource: function WC_viewSource(aSourceURL, aSourceLine) {
     // Attempt to access view source via a browser first, which may display it in
     // a tab, if enabled.
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
+    let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     if (browserWin && browserWin.BrowserViewSourceOfDocument) {
       return browserWin.BrowserViewSourceOfDocument({
         URL: aSourceURL,
         lineNumber: aSourceLine
       });
     }
     this.gViewSourceUtils.viewSource(aSourceURL, null, this.iframeWindow.document, aSourceLine || 0);
   },
--- a/devtools/client/webide/content/addons.js
+++ b/devtools/client/webide/content/addons.js
@@ -1,23 +1,26 @@
 /* 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/. */
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Services = require("Services");
+const {gDevTools} = require("devtools/client/framework/devtools");
 const {GetAvailableAddons, ForgetAddonsList} = require("devtools/client/webide/modules/addons");
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.querySelector("#aboutaddons").onclick = function () {
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-    browserWin.BrowserOpenAddonsMgr("addons://list/extension");
+    let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    if (browserWin && browserWin.BrowserOpenAddonsMgr) {
+      browserWin.BrowserOpenAddonsMgr("addons://list/extension");
+    }
   };
   document.querySelector("#close").onclick = CloseUI;
   GetAvailableAddons().then(BuildUI, (e) => {
     console.error(e);
     window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
   });
 }, true);
 
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -233,21 +233,20 @@ var UI = {
   },
 
   configureSimulator: function (event, simulator) {
     UI.selectDeckPanel("simulator");
   },
 
   openInBrowser: function (url) {
     // Open a URL in a Firefox window
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-    if (browserWin) {
-      let gBrowser = browserWin.gBrowser;
-      gBrowser.selectedTab = gBrowser.addTab(url);
-      browserWin.focus();
+    let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    if (mainWindow) {
+      mainWindow.openUILinkIn(url, "tab");
+      mainWindow.focus()
     } else {
       window.open(url);
     }
   },
 
   updateTitle: function () {
     let project = AppManager.selectedProject;
     if (project) {
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -77,16 +77,25 @@ var gNextLoaderID = 0;
  */
 this.DevToolsLoader = function DevToolsLoader() {
   this.require = this.require.bind(this);
 
   Services.obs.addObserver(this, "devtools-unload", false);
 };
 
 DevToolsLoader.prototype = {
+  destroy: function (reason = "shutdown") {
+    Services.obs.removeObserver(this, "devtools-unload");
+
+    if (this._provider) {
+      this._provider.unload(reason);
+      delete this._provider;
+    }
+  },
+
   get provider() {
     if (!this._provider) {
       this._loadProvider();
     }
     return this._provider;
   },
 
   _provider: null,
@@ -172,22 +181,17 @@ DevToolsLoader.prototype = {
    *
    * @param String data
    *    reason passed to modules when unloaded
    */
   observe: function (subject, topic, data) {
     if (topic != "devtools-unload") {
       return;
     }
-    Services.obs.removeObserver(this, "devtools-unload");
-
-    if (this._provider) {
-      this._provider.unload(data);
-      delete this._provider;
-    }
+    this.destroy(data);
   },
 
   /**
    * Sets whether the compartments loaded by this instance should be invisible
    * to the debugger.  Invisibility is needed for loaders that support debugging
    * of chrome code.  This is true of remote target environments, like Fennec or
    * B2G.  It is not the default case for desktop Firefox because we offer the
    * Browser Toolbox for chrome debugging there, which uses its own, separate
--- a/devtools/shared/gcli/commands/mdn.js
+++ b/devtools/shared/gcli/commands/mdn.js
@@ -64,18 +64,18 @@ exports.items = [{
       root.appendChild(title);
 
       let link = document.createElement("p");
       link.classList.add("gcli-mdn-url");
       link.textContent = l10n.lookup("mdnCssVisitPage");
       root.appendChild(link);
 
       link.addEventListener("click", () => {
-        let gBrowser = context.environment.chromeWindow.gBrowser;
-        gBrowser.selectedTab = gBrowser.addTab(result.url);
+        let mainWindow = context.environment.chromeWindow;
+        mainWindow.openUILinkIn(result.url, "tab");
       });
 
       let summary = document.createElement("p");
       summary.textContent = result.data.summary;
       root.appendChild(summary);
     }
 
     return root;
--- a/devtools/shared/gcli/commands/screenshot.js
+++ b/devtools/shared/gcli/commands/screenshot.js
@@ -148,18 +148,18 @@ exports.items = [
         root.appendChild(image);
       }
 
       // Click handler
       if (imageSummary.href || imageSummary.filename) {
         root.style.cursor = "pointer";
         root.addEventListener("click", () => {
           if (imageSummary.href) {
-            const gBrowser = context.environment.chromeWindow.gBrowser;
-            gBrowser.selectedTab = gBrowser.addTab(imageSummary.href);
+            let mainWindow = context.environment.chromeWindow;
+            mainWindow.openUILinkIn(imageSummary.href, "tab");
           } else if (imageSummary.filename) {
             const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
             file.initWithPath(imageSummary.filename);
             file.reveal();
           }
         });
       }
 
@@ -271,22 +271,25 @@ function createScreenshotData(document, 
   const window = document.defaultView;
   let left = 0;
   let top = 0;
   let width;
   let height;
   const currentX = window.scrollX;
   const currentY = window.scrollY;
 
+  let filename = getFilename(args.filename);
+
   if (args.fullpage) {
     // Bug 961832: GCLI screenshot shows fixed position element in wrong
     // position if we don't scroll to top
     window.scrollTo(0,0);
     width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
     height = window.innerHeight + window.scrollMaxY - window.scrollMinY;
+    filename = filename.replace(".png", "-fullpage.png");
   }
   else if (args.selector) {
     ({ top, left, width, height } = getRect(window, args.selector, window));
   }
   else {
     left = window.scrollX;
     top = window.scrollY;
     width = window.innerWidth;
@@ -318,17 +321,17 @@ function createScreenshotData(document, 
     window.scrollTo(currentX, currentY);
   }
 
   return Promise.resolve({
     destinations: [],
     data: data,
     height: height,
     width: width,
-    filename: getFilename(args.filename),
+    filename: filename,
   });
 }
 
 /**
  * We may have a filename specified in args, or we might have to generate
  * one.
  */
 function getFilename(defaultName) {
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -195,18 +195,16 @@ EXPORTS.mozilla.dom += [
     'ScriptSettings.h',
     'ShadowRoot.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
     'Text.h',
     'TreeWalker.h',
-    'URL.h',
-    'URLSearchParams.h',
     'WebKitCSSMatrix.h',
     'WebSocket.h',
     'WindowOrientationObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
@@ -331,18 +329,16 @@ UNIFIED_SOURCES += [
     'ShadowRoot.cpp',
     'StructuredCloneHolder.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
     'TreeWalker.cpp',
-    'URL.cpp',
-    'URLSearchParams.cpp',
     'WebKitCSSMatrix.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
     'WindowOrientationObserver.cpp',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     UNIFIED_SOURCES += [
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1909,16 +1909,19 @@ nsDOMWindowUtils::SendQueryContentEvent(
       message = eQueryTextRect;
       break;
     case QUERY_EDITOR_RECT:
       message = eQueryEditorRect;
       break;
     case QUERY_CHARACTER_AT_POINT:
       message = eQueryCharacterAtPoint;
       break;
+    case QUERY_TEXT_RECT_ARRAY:
+      message = eQueryTextRectArray;
+      break;
     default:
       return NS_ERROR_INVALID_ARG;
   }
 
   SelectionType selectionType = SelectionType::eNormal;
   static const uint32_t kSelectionFlags =
     QUERY_CONTENT_FLAG_SELECTION_SPELLCHECK |
     QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT |
@@ -2026,16 +2029,19 @@ nsDOMWindowUtils::SendQueryContentEvent(
       queryEvent.InitForQueryCaretRect(aOffset, options);
       break;
     case eQueryTextRect:
       queryEvent.InitForQueryTextRect(aOffset, aLength, options);
       break;
     case eQuerySelectedText:
       queryEvent.InitForQuerySelectedText(selectionType, options);
       break;
+    case eQueryTextRectArray:
+      queryEvent.InitForQueryTextRectArray(aOffset, aLength, options);
+      break;
     default:
       queryEvent.Init(options);
       break;
   }
 
   nsEventStatus status;
   nsresult rv = targetWidget->DispatchEvent(&queryEvent, status);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -279,17 +279,16 @@ bool nsGlobalWindow::sIdleObserversAPIFu
 static int32_t              gRefCnt                    = 0;
 static int32_t              gOpenPopupSpamCount        = 0;
 static PopupControlState    gPopupControlState         = openAbused;
 static int32_t              gRunningTimeoutDepth       = 0;
 static bool                 gMouseDown                 = false;
 static bool                 gDragServiceDisabled       = false;
 static FILE                *gDumpFile                  = nullptr;
 static uint32_t             gSerialCounter             = 0;
-static uint32_t             gTimeoutsRecentlySet       = 0;
 static TimeStamp            gLastRecordedRecentTimeouts;
 #define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC)
 
 #ifdef DEBUG_jst
 int32_t gTimeoutCnt                                    = 0;
 #endif
 
 #if defined(DEBUG_bryner) || defined(DEBUG_chb)
@@ -9231,18 +9230,16 @@ nsGlobalWindow::ShowModalDialogOuter(con
     mDoc->WarnOnceAbout(nsIDocument::eShowModalDialog);
   }
 
   if (!IsShowModalDialogEnabled()) {
     aError.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  Telemetry::Accumulate(Telemetry::DOM_WINDOW_SHOWMODALDIALOG_USED, true);
-
   RefPtr<DialogValueHolder> argHolder =
     new DialogValueHolder(nsContentUtils::SubjectPrincipal(), aArgument);
 
   // Before bringing up the window/dialog, unsuppress painting and flush
   // pending reflows.
   EnsureReflowFlushAndPaint();
 
   if (!AreDialogsEnabled()) {
@@ -12071,17 +12068,16 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
   nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
   nsCOMPtr<nsIPrincipal> ourPrincipal = GetPrincipal();
   if (ourPrincipal->Subsumes(subjectPrincipal)) {
     timeout->mPrincipal = subjectPrincipal;
   } else {
     timeout->mPrincipal = ourPrincipal;
   }
 
-  ++gTimeoutsRecentlySet;
   TimeDuration delta = TimeDuration::FromMilliseconds(realInterval);
 
   if (!IsFrozen() && !mTimeoutsSuspendDepth) {
     // If we're not currently frozen, then we set timeout->mWhen to be the
     // actual firing time of the timer (i.e., now + delta). We also actually
     // create a timer and fire it off.
 
     timeout->mWhen = TimeStamp::Now() + delta;
@@ -12454,19 +12450,16 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
   if (!last_expired_timeout) {
     return;
   }
 
   // Record telemetry information about timers set recently.
   TimeDuration recordingInterval = TimeDuration::FromMilliseconds(STATISTICS_INTERVAL);
   if (gLastRecordedRecentTimeouts.IsNull() ||
       now - gLastRecordedRecentTimeouts > recordingInterval) {
-    uint32_t count = gTimeoutsRecentlySet;
-    gTimeoutsRecentlySet = 0;
-    Telemetry::Accumulate(Telemetry::DOM_TIMERS_RECENTLY_SET, count);
     gLastRecordedRecentTimeouts = now;
   }
 
   // Insert a dummy timeout into the list of timeouts between the
   // portion of the list that we are about to process now and those
   // timeouts that will be processed in a future call to
   // win_run_timeout(). This dummy timeout serves as the head of the
   // list for any timeouts inserted as a result of running a timeout.
@@ -12476,18 +12469,16 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
   last_expired_timeout->setNext(dummy_timeout);
   RefPtr<nsTimeout> timeoutExtraRef(dummy_timeout);
 
   last_insertion_point = mTimeoutInsertionPoint;
   // If we ever start setting mTimeoutInsertionPoint to a non-dummy timeout,
   // the logic in ResetTimersForNonBackgroundWindow will need to change.
   mTimeoutInsertionPoint = dummy_timeout;
 
-  Telemetry::AutoCounter<Telemetry::DOM_TIMERS_FIRED_PER_NATIVE_TIMEOUT> timeoutsRan;
-
   for (nsTimeout *timeout = mTimeouts.getFirst();
        timeout != dummy_timeout && !IsFrozen();
        timeout = nextTimeout) {
     nextTimeout = timeout->getNext();
 
     if (timeout->mFiringDepth != firingDepth) {
       // We skip the timeout since it's on the list to run at another
       // depth.
@@ -12511,17 +12502,16 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
 
     if (!scx) {
       // No context means this window was closed or never properly
       // initialized for this language.
       continue;
     }
 
     // This timeout is good to run
-    ++timeoutsRan;
     bool timeout_was_cleared = RunTimeoutHandler(timeout, scx);
 
     if (timeout_was_cleared) {
       // The running timeout's window was cleared, this means that
       // ClearAllTimeouts() was called from a *nested* call, possibly
       // through a timeout that fired while a modal (to this window)
       // dialog was open or through other non-obvious paths.
       MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
--- a/dom/base/nsQueryContentEventResult.cpp
+++ b/dom/base/nsQueryContentEventResult.cpp
@@ -1,17 +1,18 @@
 /* -*- 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 "nsQueryContentEventResult.h"
 #include "nsIWidget.h"
 #include "nsPoint.h"
+#include "nsQueryContentEventResult.h"
+#include "mozilla/Move.h"
 #include "mozilla/TextEvents.h"
 
 using namespace mozilla;
 
 /******************************************************************************
  * Is*PropertyAvailable() methods which check if the property is available
  * (valid) with the event message.
  ******************************************************************************/
@@ -194,35 +195,62 @@ nsQueryContentEventResult::GetTentativeC
   }
   if (NS_WARN_IF(mEventMessage != eQueryCharacterAtPoint)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   *aNotFound = (mTentativeCaretOffset == WidgetQueryContentEvent::NOT_FOUND);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsQueryContentEventResult::GetCharacterRect(int32_t aOffset,
+                                            int32_t* aLeft, int32_t* aTop,
+                                            int32_t* aWidth, int32_t* aHeight)
+{
+  NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mEventMessage == eQueryTextRectArray,
+                 NS_ERROR_NOT_AVAILABLE);
+
+  if (NS_WARN_IF(mRectArray.Length() <= static_cast<uint32_t>(aOffset))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aLeft = mRectArray[aOffset].x;
+  *aTop = mRectArray[aOffset].y;
+  *aWidth = mRectArray[aOffset].width;
+  *aHeight = mRectArray[aOffset].height;
+
+  return NS_OK;
+}
+
 void
 nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget,
-                                          const WidgetQueryContentEvent &aEvent)
+                                          WidgetQueryContentEvent &aEvent)
 {
   mEventMessage = aEvent.mMessage;
   mSucceeded = aEvent.mSucceeded;
   mReversed = aEvent.mReply.mReversed;
   mRect = aEvent.mReply.mRect;
   mOffset = aEvent.mReply.mOffset;
   mTentativeCaretOffset = aEvent.mReply.mTentativeCaretOffset;
   mString = aEvent.mReply.mString;
+  mRectArray = mozilla::Move(aEvent.mReply.mRectArray);
+  // Mark as result that is longer used.
+  aEvent.mSucceeded = false;
 
   if (!IsRectRelatedPropertyAvailable(mEventMessage) ||
       !aWidget || !mSucceeded) {
     return;
   }
 
   nsIWidget* topWidget = aWidget->GetTopLevelWidget();
   if (!topWidget || topWidget == aWidget) {
     return;
   }
 
   // Convert the top widget related coordinates to the given widget's.
   LayoutDeviceIntPoint offset =
     aWidget->WidgetToScreenOffset() - topWidget->WidgetToScreenOffset();
   mRect.MoveBy(-offset);
+  for (size_t i = 0; i < mRectArray.Length(); i++) {
+    mRectArray[i].MoveBy(-offset);
+  }
 }
--- a/dom/base/nsQueryContentEventResult.h
+++ b/dom/base/nsQueryContentEventResult.h
@@ -19,25 +19,26 @@ class nsIWidget;
 class nsQueryContentEventResult final : public nsIQueryContentEventResult
 {
 public:
   nsQueryContentEventResult();
   NS_DECL_ISUPPORTS
   NS_DECL_NSIQUERYCONTENTEVENTRESULT
 
   void SetEventResult(nsIWidget* aWidget,
-                      const mozilla::WidgetQueryContentEvent &aEvent);
+                      mozilla::WidgetQueryContentEvent& aEvent);
 
 protected:
   ~nsQueryContentEventResult();
 
   mozilla::EventMessage mEventMessage;
 
   uint32_t mOffset;
   uint32_t mTentativeCaretOffset;
   nsString mString;
   mozilla::LayoutDeviceIntRect mRect;
+  nsTArray<mozilla::LayoutDeviceIntRect> mRectArray;
 
   bool mSucceeded;
   bool mReversed;
 };
 
 #endif // mozilla_dom_nsQueryContentEventResult_h
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -235,29 +235,25 @@ nsRange::IsNodeSelected(nsINode* aNode, 
 /******************************************************
  * constructor/destructor
  ******************************************************/
 
 nsRange::~nsRange() 
 {
   NS_ASSERTION(!IsInSelection(), "deleting nsRange that is in use");
 
-  // Maybe we can remove Detach() -- bug 702948.
-  Telemetry::Accumulate(Telemetry::DOM_RANGE_DETACHED, mIsDetached);
-
   // we want the side effects (releases and list removals)
   DoSetRange(nullptr, 0, nullptr, 0, nullptr);
 }
 
 nsRange::nsRange(nsINode* aNode)
   : mRoot(nullptr)
   , mStartOffset(0)
   , mEndOffset(0)
   , mIsPositioned(false)
-  , mIsDetached(false)
   , mMaySpanAnonymousSubtrees(false)
   , mIsGenerated(false)
   , mStartOffsetWasIncremented(false)
   , mEndOffsetWasIncremented(false)
   , mEnableGravitationOnElementRemoval(true)
 #ifdef DEBUG
   , mAssertNextInsertOrAppendIndex(-1)
   , mAssertNextInsertOrAppendNode(nullptr)
@@ -2819,18 +2815,16 @@ nsRange::ToString(nsAString& aReturn)
   return NS_OK;
 }
 
 
 
 NS_IMETHODIMP
 nsRange::Detach()
 {
-  // No-op, but still set mIsDetached for telemetry (bug 702948)
-  mIsDetached = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsRange::CreateContextualFragment(const nsAString& aFragment,
                                   nsIDOMDocumentFragment** aReturn)
 {
   if (mIsPositioned) {
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -340,17 +340,16 @@ protected:
   nsCOMPtr<nsINode> mRoot;
   nsCOMPtr<nsINode> mStartParent;
   nsCOMPtr<nsINode> mEndParent;
   RefPtr<mozilla::dom::Selection> mSelection;
   int32_t mStartOffset;
   int32_t mEndOffset;
 
   bool mIsPositioned : 1;
-  bool mIsDetached : 1;
   bool mMaySpanAnonymousSubtrees : 1;
   bool mIsGenerated : 1;
   bool mStartOffsetWasIncremented : 1;
   bool mEndOffsetWasIncremented : 1;
   bool mEnableGravitationOnElementRemoval : 1;
 #ifdef DEBUG
   int32_t  mAssertNextInsertOrAppendIndex;
   nsINode* mAssertNextInsertOrAppendNode;
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -1,22 +1,20 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
-  file_url.jsm
   file_empty.html
   file_bug945152.jar
   file_bug945152_worker.js
   file_bug1008126_worker.js
 
 [test_anonymousContent_xul_window.xul]
 [test_bug715041.xul]
 [test_bug715041_removal.xul]
 [test_domrequesthelper.xul]
-[test_url.xul]
 [test_navigator_resolve_identity_xrays.xul]
 support-files = file_navigator_resolve_identity_xrays.xul
 [test_sendQueryContentAndSelectionSetEvent.html]
 [test_bug1016960.html]
 [test_copypaste.xul]
 subsuite = clipboard
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -789,25 +789,16 @@ skip-if = debug == false
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_simplecontentpolicy.html]
 skip-if = e10s || buildapp == 'b2g' # Bug 1156489.
 [test_text_wholeText.html]
 [test_textnode_normalize_in_selection.html]
 [test_textnode_split_in_selection.html]
 [test_title.html]
 [test_treewalker_nextsibling.xml]
-[test_unknown_url_origin.html]
-[test_url.html]
-[test_url_data.html]
-[test_url_empty_port.html]
-[test_url_malformedHost.html]
-[test_urlExceptions.html]
-[test_urlSearchParams.html]
-[test_urlSearchParams_utf8.html]
-[test_urlutils_stringify.html]
 [test_user_select.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_viewport_scroll.html]
 [test_viewsource_forbidden_in_object.html]
 [test_w3element_traversal.html]
 [test_w3element_traversal.xhtml]
 [test_w3element_traversal_svg.html]
 [test_warning_for_blocked_cross_site_request.html]
--- a/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
+++ b/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
@@ -112,16 +112,42 @@ function runTests()
      "sendQueryContentEvent(QUERY_TEXT_RECT) should return same top as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
   is(result.left, textRectNative.left,
      "sendQueryContentEvent(QUERY_TEXT_RECT) should return same left as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
   is(result.height, textRectNative.height,
      "sendQueryContentEvent(QUERY_TEXT_RECT) should return same height as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
   is(result.width, textRectNative.width,
      "sendQueryContentEvent(QUERY_TEXT_RECT) should return same width as calling with QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK");
 
+  // QueryTextRectArray
+  var textRectArray = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT_ARRAY, 1, 2, 0, 0);
+  ok(textRectArray.succeeded,
+     "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should succeed");
+  var textRect = gUtils.sendQueryContentEvent(gUtils.QUERY_TEXT_RECT, 1, 2, 0, 0);
+  ok(textRect.succeeded,
+     "sendQueryContentEvent(QUERY_TEXT_RECT) should succeed");
+  var left = {};
+  var top = {};
+  var width = {};
+  var height = {};
+  var left2 = {};
+  var top2 = {};
+  var width2 = {};
+  var height2 = {};
+  textRectArray.getCharacterRect(0, left, top, width, height);
+  ok(textRect.top, top.value,
+     "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same top that returns QUERY_TEXT_RECT");
+  ok(textRect.left, left.value,
+     "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same left that returns QUERY_TEXT_RECT");
+  textRectArray.getCharacterRect(1, left2, top2, width2, height2);
+  ok(textRect.width, width.value + width2.value,
+     "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same width that QUERY_TEXT_RECT is returned for offset 1 and 2");
+  ok(textRect.height, height.value,
+     "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same height that returns QUERY_TEXT_RECT");
+
   // QueryCharacterAtOffset
   result = gUtils.sendQueryContentEvent(gUtils.QUERY_CHARACTER_AT_POINT, 0, 0, textRectNative.left + 1, textRectNative.top + 1,
                                         gUtils.QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
   ok(result.succeeded,
      "sendQueryContentEvent(QUERY_CHARACTER_AT_POINT, QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK) should succeed");
   is(result.top, textRectNative.top,
      "The character top is wrong");
   is(result.left, textRectNative.left,
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -180,20 +180,16 @@ IsDOMObject(JSObject* obj)
 {
   return IsDOMClass(js::GetObjectClass(obj));
 }
 
 #define UNWRAP_OBJECT(Interface, obj, value)                                 \
   mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
     mozilla::dom::Interface##Binding::NativeType>(obj, value)
 
-#define UNWRAP_WORKER_OBJECT(Interface, obj, value)                           \
-  UnwrapObject<prototypes::id::Interface##_workers,                           \
-    mozilla::dom::Interface##Binding_workers::NativeType>(obj, value)
-
 // Some callers don't want to set an exception when unwrapping fails
 // (for example, overload resolution uses unwrapping to tell what sort
 // of thing it's looking at).
 // U must be something that a T* can be assigned to (e.g. T* or an RefPtr<T>).
 template <class T, typename U>
 MOZ_ALWAYS_INLINE nsresult
 UnwrapObject(JSObject* obj, U& value, prototypes::ID protoID,
              uint32_t protoDepth)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -359,17 +359,16 @@ DOMInterfaces = {
 },
 
 'DataChannel': {
     'nativeType': 'nsDOMDataChannel',
 },
 
 'DedicatedWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'workers': True,
 },
 
 'DeviceAcceleration': {
     'headerFile': 'mozilla/dom/DeviceMotionEvent.h',
 },
 
 'DeviceRotationRate': {
     'headerFile': 'mozilla/dom/DeviceMotionEvent.h',
@@ -429,22 +428,16 @@ DOMInterfaces = {
     'nativeType': 'nsDOMTokenList',
 },
 
 'DummyInterface': {
     'skipGen': True,
     'register': False,
 },
 
-'DummyInterfaceWorkers': {
-    'skipGen': True,
-    'register': False,
-    'workers': True
-},
-
 'DynamicsCompressorNode': {
     'binaryNames': {
         'release': 'getRelease'
     },
 },
 
 'Event': {
     'implicitJSContext': [ 'defaultPrevented', 'preventDefault' ],
@@ -960,32 +953,30 @@ DOMInterfaces = {
 
 'ServiceWorker': {
     'nativeType': 'mozilla::dom::workers::ServiceWorker',
     'headerFile': 'mozilla/dom/workers/bindings/ServiceWorker.h',
 },
 
 'ServiceWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'workers': True,
 },
 
 'ServiceWorkerRegistration': {
     'implicitJSContext': [ 'pushManager' ],
 },
 
 'SharedWorker': {
     'nativeType': 'mozilla::dom::workers::SharedWorker',
     'headerFile': 'mozilla/dom/workers/bindings/SharedWorker.h',
     'implicitJSContext': [ 'constructor' ],
 },
 
 'SharedWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'workers': True,
 },
 
 'Storage': {
     'nativeType': 'mozilla::dom::DOMStorage',
 },
 
 'StyleSheet': {
     'nativeType': 'mozilla::CSSStyleSheet',
@@ -1276,21 +1267,16 @@ DOMInterfaces = {
 'TreeWalker': {
     'wrapperCache': False,
 },
 
 'UndoManager': {
     'implicitJSContext' : [ 'undo', 'redo', 'transact' ],
 },
 
-'URL' : [{},
-{
-    'workers': True,
-}],
-
 'VRDevice': {
     'concrete': False
 },
 
 'VTTCue': {
     'nativeType': 'mozilla::dom::TextTrackCue'
 },
 
@@ -1552,25 +1538,23 @@ DOMInterfaces = {
 
 'Worker': {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
     'nativeType': 'mozilla::dom::workers::WorkerPrivate',
 },
 
 'WorkerDebuggerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'nativeType': 'mozilla::dom::workers::WorkerDebuggerGlobalScope',
     'implicitJSContext': [
         'dump', 'global', 'reportError', 'setConsoleEventHandler',
     ],
 },
 
 'WorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'workers': True,
     'concrete': False,
     'implicitJSContext': [
         'close',
     ],
     # Rename a few things so we don't have both classes and methods
     # with the same name
     'binaryNames': {
         'performance': 'getPerformance',
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -111,18 +111,17 @@ class Configuration:
         # are.
         mainTypes = set()
         for descriptor in ([self.getDescriptor("DummyInterface", workers=False)] +
                            self.getDescriptors(workers=False, isExternal=False, skipGen=False)):
             mainTypes |= set(getFlatTypes(getTypesFromDescriptor(descriptor)))
         (mainCallbacks, mainDictionaries) = findCallbacksAndDictionaries(mainTypes)
 
         workerTypes = set()
-        for descriptor in ([self.getDescriptor("DummyInterfaceWorkers", workers=True)] +
-                           self.getDescriptors(workers=True, isExternal=False, skipGen=False)):
+        for descriptor in (self.getDescriptors(workers=True, isExternal=False, skipGen=False)):
             workerTypes |= set(getFlatTypes(getTypesFromDescriptor(descriptor)))
         (workerCallbacks, workerDictionaries) = findCallbacksAndDictionaries(workerTypes)
 
         self.dictionaries = [d for d in parseData if d.isDictionary()]
         self.callbacks = [c for c in parseData if
                           c.isCallback() and not c.isInterface()]
 
         # Dictionary mapping from a union type name to a set of filenames where
--- a/dom/bindings/mozwebidlcodegen/test/DummyBinding.webidl
+++ b/dom/bindings/mozwebidlcodegen/test/DummyBinding.webidl
@@ -1,2 +1,1 @@
 interface DummyInterface {};
-interface DummyInterfaceWorkers {};
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1194,16 +1194,18 @@ ContentEventHandler::HandleQueryContentE
     case eQuerySelectedText:
       return OnQuerySelectedText(aEvent);
     case eQueryTextContent:
       return OnQueryTextContent(aEvent);
     case eQueryCaretRect:
       return OnQueryCaretRect(aEvent);
     case eQueryTextRect:
       return OnQueryTextRect(aEvent);
+    case eQueryTextRectArray:
+      return OnQueryTextRectArray(aEvent);
     case eQueryEditorRect:
       return OnQueryEditorRect(aEvent);
     case eQueryContentState:
       return OnQueryContentState(aEvent);
     case eQuerySelectionAsTransferable:
       return OnQuerySelectionAsTransferable(aEvent);
     case eQueryCharacterAtPoint:
       return OnQueryCharacterAtPoint(aEvent);
@@ -1388,16 +1390,95 @@ static nsINode* AdjustTextRectNode(nsINo
       node = aNode->GetChildAt(childCount - 1);
       aNodeOffset = node->IsNodeOfType(nsINode::eTEXT) ?
         static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength()) : 1;
     }
   }
   return node;
 }
 
+static
+nsIFrame*
+GetFirstFrameInRange(nsRange* aRange)
+{
+  // used to iterate over all contents and their frames
+  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
+  iter->Init(aRange);
+
+  // get the starting frame
+  int32_t nodeOffset = aRange->StartOffset();
+  nsINode* node = iter->GetCurrentNode();
+  if (!node) {
+    node = AdjustTextRectNode(aRange->GetStartParent(), nodeOffset);
+  }
+  nsIFrame* firstFrame = nullptr;
+  GetFrameForTextRect(node, nodeOffset, true, &firstFrame);
+  return firstFrame;
+}
+
+nsresult
+ContentEventHandler::OnQueryTextRectArray(WidgetQueryContentEvent* aEvent)
+{
+  nsresult rv = Init(aEvent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  LineBreakType lineBreakType = GetLineBreakType(aEvent);
+  RefPtr<nsRange> range = new nsRange(mRootContent);
+  uint32_t offset = aEvent->mInput.mOffset;
+
+  LayoutDeviceIntRect rect;
+  WritingMode writingMode;
+  while (aEvent->mInput.mLength > aEvent->mReply.mRectArray.Length()) {
+    rv = SetRangeFromFlatTextOffset(range, offset, 1, lineBreakType, true,
+                                    nullptr);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // get the starting frame
+    nsIFrame* firstFrame = GetFirstFrameInRange(range);
+    if (NS_WARN_IF(!firstFrame)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // get the starting frame rect
+    nsRect frameRect(nsPoint(0, 0), firstFrame->GetRect().Size());
+    rv = ConvertToRootRelativeOffset(firstFrame, frameRect);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    int32_t nodeOffset = range->StartOffset();
+    AutoTArray<nsRect, 16> charRects;
+    rv = firstFrame->GetCharacterRectsInRange(
+           nodeOffset,
+           aEvent->mInput.mLength - aEvent->mReply.mRectArray.Length(),
+           charRects);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    for (size_t i = 0; i < charRects.Length(); i++) {
+      nsRect charRect = charRects[i];
+      charRect.x += frameRect.x;
+      charRect.y += frameRect.y;
+
+      rect = LayoutDeviceIntRect::FromUnknownRect(
+               charRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
+
+      aEvent->mReply.mRectArray.AppendElement(rect);
+    }
+    offset += charRects.Length();
+  }
+  aEvent->mSucceeded = true;
+  return NS_OK;
+}
+
 nsresult
 ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -48,16 +48,18 @@ public:
   // eQuerySelectedText event handler
   nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent);
   // eQueryTextContent event handler
   nsresult OnQueryTextContent(WidgetQueryContentEvent* aEvent);
   // eQueryCaretRect event handler
   nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent);
   // eQueryTextRect event handler
   nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent);
+  // eQueryTextRectArray event handler
+  nsresult OnQueryTextRectArray(WidgetQueryContentEvent* aEvent);
   // eQueryEditorRect event handler
   nsresult OnQueryEditorRect(WidgetQueryContentEvent* aEvent);
   // eQueryContentState event handler
   nsresult OnQueryContentState(WidgetQueryContentEvent* aEvent);
   // eQuerySelectionAsTransferable event handler
   nsresult OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent);
   // eQueryCharacterAtPoint event handler
   nsresult OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent);
@@ -294,13 +296,16 @@ protected:
                                int32_t aBaseOffset,
                                int32_t aXPStartOffset,
                                int32_t aXPEndOffset,
                                LineBreakType aLineBreakType);
   nsresult GenerateFlatFontRanges(nsRange* aRange,
                                   FontRangeArray& aFontRanges,
                                   uint32_t& aLength,
                                   LineBreakType aLineBreakType);
+  nsresult QueryTextRectByRange(nsRange* aRange,
+                                LayoutDeviceIntRect& aRect,
+                                WritingMode& aWritingMode);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ContentEventHandler_h_
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -855,16 +855,17 @@ EventStateManager::HandleQueryContentEve
       // Will not be handled locally, remote the event
       GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
       return;
     // Following events have not been supported in e10s mode yet.
     case eQueryContentState:
     case eQuerySelectionAsTransferable:
     case eQueryCharacterAtPoint:
     case eQueryDOMWidgetHittest:
+    case eQueryTextRectArray:
       break;
     default:
       return;
   }
 
   // If there is an IMEContentObserver, we need to handle QueryContentEvent
   // with it.
   if (mIMEContentObserver) {
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -11,17 +11,16 @@
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/URL.h"
 #include "mozilla/dom/WorkerPrivate.h"
-#include "mozilla/dom/workers/bindings/URL.h"
 #include "mozilla/unused.h"
 
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
@@ -169,38 +168,37 @@ GetRequestURLFromChrome(const nsAString&
   aRv = uriClone->GetSpec(spec);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   CopyUTF8toUTF16(spec, aRequestURL);
 }
 
-already_AddRefed<workers::URL>
+already_AddRefed<URL>
 ParseURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
                    ErrorResult& aRv)
 {
   workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
-  RefPtr<workers::URL> url =
-    workers::URL::Constructor(aGlobal, aInput, baseURL, aRv);
+  RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aInput, baseURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
   }
   return url.forget();
 }
 
 void
 GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
                         nsAString& aRequestURL, ErrorResult& aRv)
 {
-  RefPtr<workers::URL> url = ParseURLFromWorker(aGlobal, aInput, aRv);
+  RefPtr<URL> url = ParseURLFromWorker(aGlobal, aInput, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   nsString username;
   url->GetUsername(username, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
@@ -374,17 +372,17 @@ Request::Constructor(const GlobalObject&
               principal->GetOrigin(globalOrigin);
               aRv.ThrowTypeError<MSG_CROSS_ORIGIN_REFERRER_URL>(referrer,
                                                                 NS_ConvertUTF8toUTF16(globalOrigin));
               return nullptr;
             }
           }
         }
       } else {
-        RefPtr<workers::URL> url = ParseURLFromWorker(aGlobal, referrer, aRv);
+        RefPtr<URL> url = ParseURLFromWorker(aGlobal, referrer, aRv);
         if (NS_WARN_IF(aRv.Failed())) {
           aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
           return nullptr;
         }
         url->Stringify(referrerURL, aRv);
         if (NS_WARN_IF(aRv.Failed())) {
           aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
           return nullptr;
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -10,17 +10,16 @@
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/FetchBinding.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/URL.h"
-#include "mozilla/dom/workers/bindings/URL.h"
 
 #include "nsDOMString.h"
 
 #include "InternalResponse.h"
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
@@ -83,18 +82,17 @@ Response::Redirect(const GlobalObject& a
 
     CopyUTF8toUTF16(spec, parsedURL);
   } else {
     workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->AssertIsOnWorkerThread();
 
     NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
-    RefPtr<workers::URL> url =
-      workers::URL::Constructor(aGlobal, aUrl, baseURL, aRv);
+    RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aUrl, baseURL, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
 
     url->Stringify(parsedURL, aRv);
   }
 
   if (aRv.Failed()) {
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1775,16 +1775,20 @@ HTMLInputElement::BeforeSetAttr(int32_t 
       if (container &&
           ((aValue && !HasAttr(aNameSpaceID, aName)) ||
            (!aValue && HasAttr(aNameSpaceID, aName)))) {
         nsAutoString name;
         GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
         container->RadioRequiredWillChange(name, !!aValue);
       }
     }
+
+    if (aName == nsGkAtoms::webkitdirectory) {
+      Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
+    }
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
                                                           aValue, aNotify);
 }
 
 nsresult
 HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
@@ -8374,15 +8378,16 @@ HTMLInputElement::UpdateEntries(const ns
   fs->CreateRoot(entries);
 
   mEntries.SwapElements(entries);
 }
 
 void
 HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<Entry>>& aSequence)
 {
+  Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
   aSequence.AppendElements(mEntries);
 }
 
 } // namespace dom
 } // namespace mozilla
 
 #undef NS_ORIGINAL_CHECKED_VALUE
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -225,16 +225,17 @@ TextTrackManager::UpdateCueDisplay()
 
   nsIFrame* frame = mMediaElement->GetPrimaryFrame();
   nsVideoFrame* videoFrame = do_QueryFrame(frame);
   if (!videoFrame) {
     return;
   }
 
   nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
+  nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls();
   if (!overlay) {
     return;
   }
 
   nsTArray<RefPtr<TextTrackCue> > activeCues;
   mTextTracks->UpdateAndGetShowingCues(activeCues);
 
   if (activeCues.Length() > 0) {
@@ -242,17 +243,17 @@ TextTrackManager::UpdateCueDisplay()
 
     jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE,
                        &NS_GET_IID(nsIDOMEventTarget),
                        activeCues.Length(),
                        static_cast<void*>(activeCues.Elements()));
 
     nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow();
     if (window) {
-      sParserWrapper->ProcessCues(window, jsCues, overlay);
+      sParserWrapper->ProcessCues(window, jsCues, overlay, controls);
     }
   } else if (overlay->Length() > 0) {
     nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
   }
 }
 
 void
 TextTrackManager::NotifyCueAdded(TextTrackCue& aCue)
@@ -291,16 +292,20 @@ TextTrackManager::PopulatePendingList()
 }
 
 void
 TextTrackManager::AddListeners()
 {
   if (mMediaElement) {
     mMediaElement->AddEventListener(NS_LITERAL_STRING("resizevideocontrols"),
                                     this, false, false);
+    mMediaElement->AddEventListener(NS_LITERAL_STRING("seeked"),
+                                    this, false, false);
+    mMediaElement->AddEventListener(NS_LITERAL_STRING("controlbarchange"),
+                                    this, false, true);
   }
 }
 
 void
 TextTrackManager::HonorUserPreferencesForTrackSelection()
 {
   if (performedTrackSelection || !mTextTracks) {
     return;
@@ -400,21 +405,27 @@ NS_IMETHODIMP
 TextTrackManager::HandleEvent(nsIDOMEvent* aEvent)
 {
   if (!mTextTracks) {
     return NS_OK;
   }
 
   nsAutoString type;
   aEvent->GetType(type);
-  if (type.EqualsLiteral("resizevideocontrols")) {
+  if (type.EqualsLiteral("resizevideocontrols") ||
+      type.EqualsLiteral("seeked")) {
     for (uint32_t i = 0; i< mTextTracks->Length(); i++) {
       ((*mTextTracks)[i])->SetCuesDirty();
     }
   }
+
+  if (type.EqualsLiteral("controlbarchange")) {
+    UpdateCueDisplay();
+  }
+
   return NS_OK;
 }
 
 
 class SimpleTextTrackEvent : public Runnable
 {
 public:
   friend class CompareSimpleTextTrackEvents;
--- a/dom/indexedDB/IDBKeyRange.cpp
+++ b/dom/indexedDB/IDBKeyRange.cpp
@@ -17,26 +17,25 @@ namespace dom {
 
 using namespace mozilla::dom::indexedDB;
 
 namespace {
 
 nsresult
 GetKeyFromJSVal(JSContext* aCx,
                 JS::Handle<JS::Value> aVal,
-                Key& aKey,
-                bool aAllowUnset = false)
+                Key& aKey)
 {
   nsresult rv = aKey.SetFromJSVal(aCx, aVal);
   if (NS_FAILED(rv)) {
     MOZ_ASSERT(NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_DOM_INDEXEDDB);
     return rv;
   }
 
-  if (aKey.IsUnset() && !aAllowUnset) {
+  if (aKey.IsUnset()) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   return NS_OK;
 }
 
 } // namespace
 
@@ -342,49 +341,48 @@ IDBKeyRange::Includes(JSContext* aCx,
                       ErrorResult& aRv) const
 {
   Key key;
   aRv = GetKeyFromJSVal(aCx, aValue, key);
   if (aRv.Failed()) {
     return false;
   }
 
-  switch (Key::CompareKeys(Lower(), key)) {
-  case 1:
-    return false;
-  case 0:
-    // Identical keys.
-    if (LowerOpen()) {
+  MOZ_ASSERT(!(Lower().IsUnset() && Upper().IsUnset()));
+  MOZ_ASSERT_IF(IsOnly(),
+    !Lower().IsUnset() && !LowerOpen() &&
+    Lower() == Upper() && LowerOpen() == UpperOpen());
+
+  if (!Lower().IsUnset()) {
+    switch (Key::CompareKeys(Lower(), key)) {
+    case 1:
       return false;
+    case 0:
+      // Identical keys.
+      return !LowerOpen();
+    case -1:
+      if (IsOnly()) {
+        return false;
+      }
+      break;
+    default:
+      MOZ_CRASH();
     }
-    break;
-  case -1:
-    if (IsOnly()) {
-      return false;
-    }
-    break;
-  default:
-    MOZ_CRASH();
   }
 
-  if (!IsOnly()) {
+  if (!Upper().IsUnset()) {
     switch (Key::CompareKeys(key, Upper())) {
     case 1:
       return false;
     case 0:
       // Identical keys.
-      if (UpperOpen()) {
-        return false;
-      }
-      break;
+      return !UpperOpen();
     case -1:
       break;
     }
-  } else {
-    MOZ_ASSERT(key == Lower());
   }
 
   return true;
 }
 
 // static
 already_AddRefed<IDBKeyRange>
 IDBKeyRange::Only(const GlobalObject& aGlobal,
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1209,16 +1209,27 @@ interface nsIDOMWindowUtils : nsISupport
    * @param aY        Y offset in the widget.
    *
    * @return offset, notFound, left, top, width and height properties of the
    *         result are available.
    */
   const unsigned long QUERY_CHARACTER_AT_POINT                  = 3208;
 
   /**
+   * QUERY_TEXT_RECT_ARRAY queries the rects per character
+   *
+   * @param aOffset   The first character's offset.  0 is the first character.
+   * @param aLength   The length of getting text.  If the aLength is too long,
+   *                  the extra length is ignored.
+   * @param aX        Not used.
+   * @param aY        Not used.
+   */
+  const unsigned long QUERY_TEXT_RECT_ARRAY                     = 3209;
+
+  /**
    * Called when the remote child frame has changed its fullscreen state,
    * when entering fullscreen, and when the origin which is fullscreen changes.
    * aFrameElement is the iframe element which contains the child-process
    * fullscreen document.
    */
   void remoteFrameFullscreenChanged(in nsIDOMElement aFrameElement);
 
   /**
--- a/dom/interfaces/base/nsIQueryContentEventResult.idl
+++ b/dom/interfaces/base/nsIQueryContentEventResult.idl
@@ -19,12 +19,16 @@ interface nsIQueryContentEventResult : n
   readonly attribute boolean reversed;
 
   readonly attribute long left;
   readonly attribute long top;
   readonly attribute long width;
   readonly attribute long height;
   readonly attribute AString text;
 
+  void getCharacterRect(in long offset,
+                        out long left, out long top,
+                        out long width, out long height);
+
   readonly attribute boolean succeeded;
   readonly attribute boolean notFound;
   readonly attribute boolean tentativeCaretOffsetNotFound;
 };
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -335,17 +335,17 @@ MediaFormatReader::OnDemuxerInitDone(nsr
 
   mIsEncrypted = crypto && crypto->IsEncrypted();
 
   if (mDecoder && crypto && crypto->IsEncrypted()) {
 #ifdef MOZ_EME
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
     for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
       NS_DispatchToMainThread(
-        new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData, NS_LITERAL_STRING("cenc")));
+        new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData, crypto->mInitDatas[i].mType));
     }
 #endif // MOZ_EME
     mInfo.mCrypto = *crypto;
   }
 
   int64_t videoDuration = HasVideo() ? mInfo.mVideo.mDuration : 0;
   int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
 
--- a/dom/media/QueueObject.cpp
+++ b/dom/media/QueueObject.cpp
@@ -13,18 +13,23 @@ namespace mozilla {
 
 QueueObject::QueueObject(RefPtr<AbstractThread> aThread) : mThread(aThread) {}
 
 QueueObject::~QueueObject() {}
 
 void
 QueueObject::Dispatch(nsIRunnable* aRunnable)
 {
-  nsCOMPtr<nsIRunnable> runnable = aRunnable;
-  mThread->Dispatch(runnable.forget());
+  Dispatch(do_AddRef(aRunnable));
+}
+
+void
+QueueObject::Dispatch(already_AddRefed<nsIRunnable> aRunnable)
+{
+  mThread->Dispatch(Move(aRunnable));
 }
 
 bool
 QueueObject::OnThread()
 {
   return mThread->IsCurrentThreadIn();
 }
 
--- a/dom/media/QueueObject.h
+++ b/dom/media/QueueObject.h
@@ -16,16 +16,17 @@ namespace mozilla {
 class AbstractThread;
 
 class QueueObject
 {
 public:
   explicit QueueObject(RefPtr<AbstractThread> aThread);
   ~QueueObject();
   void Dispatch(nsIRunnable* aRunnable);
+  void Dispatch(already_AddRefed<nsIRunnable> aRunnable);
   bool OnThread();
   AbstractThread* Thread();
 
 private:
   RefPtr<AbstractThread> mThread;
 };
 }
 
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -485,9 +485,46 @@ IsAACContentType(const nsAString& aConte
     },
     [](const nsAString& codec) {
       return codec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
              codec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
              codec.EqualsLiteral("mp4a.67");     // MPEG2 AAC-LC
     });
 }
 
+bool
+IsVorbisContentType(const nsAString& aContentType)
+{
+  return CheckContentType(aContentType,
+    [](const nsAString& type) {
+      return type.EqualsLiteral("audio/webm") ||
+             type.EqualsLiteral("audio/ogg");
+    },
+    [](const nsAString& codec) {
+      return codec.EqualsLiteral("vorbis");
+    });
+}
+
+bool
+IsVP8ContentType(const nsAString& aContentType)
+{
+  return CheckContentType(aContentType,
+    [](const nsAString& type) {
+      return type.EqualsLiteral("video/webm");
+    },
+    [](const nsAString& codec) {
+      return codec.EqualsLiteral("vp8");
+    });
+}
+
+bool
+IsVP9ContentType(const nsAString& aContentType)
+{
+  return CheckContentType(aContentType,
+    [](const nsAString& type) {
+      return type.EqualsLiteral("video/webm");
+    },
+    [](const nsAString& codec) {
+      return codec.EqualsLiteral("vp9");
+    });
+}
+
 } // end namespace mozilla
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -442,11 +442,20 @@ StringListContains(const ListString& aLi
   for (const auto& listItem : MakeStringListRange(aList)) {
     if (listItem.Equals(aItem)) {
       return true;
     }
   }
   return false;
 }
 
+bool
+IsVorbisContentType(const nsAString& aContentType);
+
+bool
+IsVP8ContentType(const nsAString& aContentType);
+
+bool
+IsVP9ContentType(const nsAString& aContentType);
+
 } // end namespace mozilla
 
 #endif
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -30,16 +30,17 @@
 #include "GMPUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
 #include "DecoderDoctorDiagnostics.h"
+#include "WebMDecoder.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -407,35 +408,100 @@ GMPDecryptsAndGeckoDecodesAAC(mozIGeckoM
     }
     return false;
   }
 #endif
   return MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
 }
 
 static bool
+GMPDecryptsAndGeckoDecodesVorbis(mozIGeckoMediaPluginService* aGMPService,
+                                const nsAString& aKeySystem,
+                                const nsAString& aContentType,
+                                DecoderDoctorDiagnostics* aDiagnostics)
+{
+  MOZ_ASSERT(HaveGMPFor(aGMPService,
+             NS_ConvertUTF16toUTF8(aKeySystem),
+             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
+  MOZ_ASSERT(IsVorbisContentType(aContentType));
+  return !HaveGMPFor(aGMPService,
+                     NS_ConvertUTF16toUTF8(aKeySystem),
+                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+                     NS_LITERAL_CSTRING("vorbis")) &&
+         WebMDecoder::CanHandleMediaType(aContentType);
+}
+
+static bool
+GMPDecryptsAndGeckoDecodesVP8(mozIGeckoMediaPluginService* aGMPService,
+                             const nsAString& aKeySystem,
+                             const nsAString& aContentType,
+                             DecoderDoctorDiagnostics* aDiagnostics)
+{
+  MOZ_ASSERT(HaveGMPFor(aGMPService,
+             NS_ConvertUTF16toUTF8(aKeySystem),
+             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
+  MOZ_ASSERT(IsVP8ContentType(aContentType));
+  return !HaveGMPFor(aGMPService,
+                     NS_ConvertUTF16toUTF8(aKeySystem),
+                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+                     NS_LITERAL_CSTRING("vp8")) &&
+         WebMDecoder::CanHandleMediaType(aContentType);
+}
+
+static bool
+GMPDecryptsAndGeckoDecodesVP9(mozIGeckoMediaPluginService* aGMPService,
+                             const nsAString& aKeySystem,
+                             const nsAString& aContentType,
+                             DecoderDoctorDiagnostics* aDiagnostics)
+{
+  MOZ_ASSERT(HaveGMPFor(aGMPService,
+             NS_ConvertUTF16toUTF8(aKeySystem),
+             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
+  MOZ_ASSERT(IsVP9ContentType(aContentType));
+  return !HaveGMPFor(aGMPService,
+                     NS_ConvertUTF16toUTF8(aKeySystem),
+                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+                     NS_LITERAL_CSTRING("vp9")) &&
+         WebMDecoder::CanHandleMediaType(aContentType);
+}
+
+static bool
 IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
                  const nsAString& aAudioType,
                  DecoderDoctorDiagnostics* aDiagnostics)
 {
-  return IsAACContentType(aAudioType) &&
-         (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
-          GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics));
+  if (IsAACContentType(aAudioType)) {
+    return GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
+           GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  }
+  if (IsVorbisContentType(aAudioType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+    // GMP does not decode Vorbis, so don't bother checking
+    return GMPDecryptsAndGeckoDecodesVorbis(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  }
+  return false;
 }
 
 static bool
 IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
                  const nsAString& aVideoType,
                  DecoderDoctorDiagnostics* aDiagnostics)
 {
-  return IsH264ContentType(aVideoType) &&
-         (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
-          GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics));
+  if (IsH264ContentType(aVideoType)) {
+    return GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
+           GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  }
+  if (IsVP8ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+    return GMPDecryptsAndGeckoDecodesVP8(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  }
+  if (IsVP9ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+    return GMPDecryptsAndGeckoDecodesVP9(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  }
+  return false;
 }
 
 static bool
 IsSupported(mozIGeckoMediaPluginService* aGMPService,
             const nsAString& aKeySystem,
             const MediaKeySystemConfiguration& aConfig,
             DecoderDoctorDiagnostics* aDiagnostics)
 {
@@ -469,17 +535,17 @@ IsSupportedInitDataType(const nsString& 
   // All supported keySystems can handle "cenc" initDataType.
   // ClearKey also supports "keyids" and "webm" initDataTypes.
   return aCandidate.EqualsLiteral("cenc") ||
     ((aKeySystem.EqualsLiteral("org.w3.clearkey")
 #ifdef MOZ_WIDEVINE_EME
     || aKeySystem.EqualsLiteral("com.widevine.alpha")
 #endif
     ) &&
-    (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm)")));
+    (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm")));
 }
 
 static bool
 GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
                    const nsAString& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
                    MediaKeySystemConfiguration& aOutConfig,
                    DecoderDoctorDiagnostics* aDiagnostics)
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -1045,17 +1045,18 @@ TrackBuffersManager::OnDemuxerInitDone(n
   }
 
   UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
   if (crypto && crypto->IsEncrypted()) {
 #ifdef MOZ_EME
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
     for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
       NS_DispatchToMainThread(
-        new DispatchKeyNeededEvent(mParentDecoder, crypto->mInitDatas[i].mInitData, NS_LITERAL_STRING("cenc")));
+        new DispatchKeyNeededEvent(mParentDecoder, crypto->mInitDatas[i].mInitData,
+                                   crypto->mInitDatas[i].mType));
     }
 #endif // MOZ_EME
     info.mCrypto = *crypto;
     // We clear our crypto init data array, so the MediaFormatReader will
     // not emit an encrypted event for the same init data again.
     info.mCrypto.mInitDatas.Clear();
     mEncrypted = true;
   }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4be8f340c3f0476a5bd2245d1f98156934f6e969
GIT binary patch
literal 7553
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
bTlSfUssDo}JxsVu1n$Rd62Y|y_T~Qq8_<@3
new file mode 100644
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..56cf4c483c54f51c3ccfad632b321637623b4dfe
GIT binary patch
literal 44671
zc$}1%Lz5_q&aT_GZQHhO+qP}nwr#st+qP}ncc1!x!A;dWc#=UdNrEN1SDY^t76>Tx
z_MaXDL4=+HA%ucL+)a&ag~9`YghB&>Fq8w#0RjGJb@GU^*)CnO-O<X`A*hm-X38~z
zcK=6cDs|TXQH5-G%zuP0+~%rW69fqSe~6>p{a-Msj_0}-;Qz<|m!g$wtuYv0NI)bq
zL}VBMKsF%W#?t72<<T<GG0`#7F{leB{SWnxoje^}2pH&?|L6Q)IjeQcYk`2yBjF+~
z*`*}XZoz?oVWDdJY6={-0S!h1wF-=Z018ZjfC|k2i8W9BAP`jiFd<s^O|&)yMOzJh
zh!O2b%Ut{{MEpDua6h6U2q>T{2+-nxz5U-fYH7~`0g4Gs+5!Op0sw(K8LoNWM1V5^
zt_7-EsuJH<%pt22Xfc1LpVRJh*3gW7VqY)q|0)8k<fFEM(O_If?I!RQSL(mYcN-&6
z5q%3Scv_v-cH<}BMDOUF_zhZz03o1iS=9gu+Ld4$ei07-<jo8R2_iKFPK6l{>xCLn
z6lTKlx1fjvs0WRt81gd%;T3+cL0?+Ki-~3s{a(?YS1xi3Y&eX_PB<**W~m?$yY5nq
z!YBI*(4iK!qa|V-FKe*Fp)C?b7KHut20W1*VIm?o9r>*Fxwr-6_|z@ySy>DPmJRB(
zF#s6-CEE=EDpAfnP|ueO2hW>5L`2y0-uqUb=!k2ta0`u+Ulc0(L!g|Os*sSKo@sG#
z&ckL7&?RkPkHhSN0|-4Pc4JSDc_U!k#ftFu?*(gJJ6S$m)OSca+TJEKVHm=ERLXbM
zLW(0z^YNhgJVZ$R^2v=j15A&mM`4Rh`B8?fNI-d~X%x?)<KnP%Nkpb!DE#Cf=N;8R
zGaQnK>H=~(=?}j7Ie!$y_xt7UD%|~k6U3o)eo5aqx0s;^s2Q}LFVi?BForrCe051*
zzvur|8R=Ba_g^AsnF5G%C*=Q`Wlv^Ry!x0!W|R;a#uXveWiST=5GzB=g5r{O9DL^T
z`F*mnv=%n)Mi9U!H=`7p3XXccmtUPj+oZ&$PWLm_f#zSF(1oISoc>0}8w#$K=n4Zm
z93r=EysGIbz#~v%COi;@#a^>A-ypM<+6FZx-Tkd;_<ssW{luXJI!}@Pp%;t!2)dlu
zBQrdb3kMETOljXqPAdAoFAyG;o^uP5C5fuT+xz+K(?{0Z<Y})Mv54|(c?++ZX%;s(
zy}Qiq#Vxs<JW1Bag(E49nQlV%nfI}JPAE%-#T|*r&Y*lDxpqa<JQu|A(RMMzI)$<X
z#Gq|(M+FIna1)P_?)ABFQura4{*G**6V?QTN_GZyE6VkCMUO2yGDNM@Ht>A>6i8zf
z|M>7*ruMiLqw~q!hg85Wd#xPamH-iSS<_`gR566jf7CO85F~<3R4LcrxbLSzH<r<!
z@a6|T-lnZ_m=SA-uh~e$zU&>^Me4YL#&w=O9@)k!Wmov{<@x1fCf^)0E#3Q6>LdSq
z<mthEVHo=hfMfpShd0|2Fvn;N5U_Iv=I=}^g%&sg4<<j>=hkCx%PkYsQFB>hi40l~
z<3TI*e6*Sf;MX=QdZ@<iG|wGE{O2uzf`)Ea82Y(-62QQ2*k{uK|4jneG4w4UX&|4R
zNC^^$^va_QlB++>+H$Vku!`$}JN&PucY<!XqAUk)Pv@m#$s12#;SH6~e--Ydc?K+d
z8SNN~FHyHMne{%;+0xtug=*2+62X|5TjhCmIFo&TvE3%4lA4vb2BBntd&<CNa7wys
zzrizSkX=m*7UGK!oDT<ZCSo-MAvrJe3k2*w``c>SM|18*V7C2v68?MG^zv9Y<dDV?
zON$#jqobIx!ZZ@F@U1M_Y9ZcwdR?lNBiw@pNR+@>3$h)+h)O?iYnIG8>Wq+4UZLy;
zO~uikrT2|1tKU@hFBu(G0|-z9Wm{KUme<%*AZV5df9Tj|rBHk0H?fp$MpTDY-<{Vo
z_{A6`sA7sJg}*Rg=?cNk1lS|I<vx0JM^30B(MP0C|8a#>qCY1=LLb*lR0Y{uF(UAi
z!BdxFvoC(ATdog#6ZyVSZ78gHF|kVNOqwlzVH(4INV}N2h^c54NX5#rtuOd97L&?&
zB+s&0t{{o-tuCu8QWouFE?T9b^7N7Rod-kCqTSbhJG48pkIsUxuXSoAtdoG99$oSu
z&D8hY0Z1YDLw%dJ<dS{Z+@eM~2(4HnWj9|xyF!lDZNYAi7R`{?S$laZ%W`-74z}Y;
zh1m4>)|9o_Q|MlA0>t@y6?8kI=a62iYF=3}YAuh~$UH=^dbYq{S*@~IIoek~lN+=x
zmGu@RVd%(|qk^avxoVdmjO?reJj0}NL3|3Cx^1V87L&JU27NoIQ&L?=@$Czw1SoHX
zHQ4%9Oa!mJ%{h;%_Hrf6E|+ab!sb_XmwOcRc4C<Fw1f3_p`ra8)t2}xJXM!tHy?j+
z+6bs=Q`F(Zpj<d81jvqoob!BEBZw}=;k;P_aBvDb7cbjF!|RdtKYDbJVnIwdLLAD+
zfde(ji;GX9^`!*w6I+4fY8^V1p6n$A9!2?d!fQ!7`S4#gGfZ;-NFGk%+upJ;2(@8x
zYV-<o5aHhwa9@>*vB&qlFw)Q=-r}16iJGDgdGaWI|6VpkpDBBuUi*G@;pRPg*$|6k
zhdq7JOuIJi3fBkzKwNJVrqlaivJph0A^C%Y1*TSYr(4LBHZU4e|CW9?$Z$f-(K00;
zfE9piISU9Zck81MNjwQqwEA+1PS{T$8VXak+OJ&n?o5bk+aK~P{d1wMf$8Q&&;7&i
z5mZqp7xxeQC7=+(C50DZlWamFbnPGVNYwUiP_2XmJ59fANY~e>z=$Z29HHS;ezrIz
zH5z#6M%x>ioo!gxLI(JiQ31zoTWcoX+5>jx@omKEaBDhZ{E8|I18_bWtlpMnOFvw^
z?!ZgP{z7IQ{b0X8wmeMKOP8C&9U@pRtPg<ZyT%lWM;?+EJ{iX%rkD&oXz&PYR=zEb
zEcw0|fg3}WF7ZqC%8dw`vL}B$oqhFoO#%NvqI_AB0utay2(fOej(#}#r+>QXk8w>S
zjZ{)Ss)NWZW%zV4FS2ngi8CY&Epbir-5M)upJon~-&qfH!S3xf(#xgbs}RXGbZoy9
z*v3ej@O9im=Usa4P@}kZX$6b9GGPwgav-EOdp3@%Wk=~|upiu(C9q>^AlKQ{S?yHs
zB?9Lnp}9P46UlsN2DH4#Z!p-p#jm#;X`9^c7KLB=mq4foQnSP2-sR4F;J{of%6o+@
zUf$KjfDv1Hlwi05d2|Y_R0dt<sVs%C1UIU!pJ11~7Pm;@kezFnKvA2Eeq`~y@h8JR
zX4t2s6Fzdp$v9fWr}|x4DK6e<5jCJ!C}vmLB;jfR<F*GN)SxVy?=;6Wwz4w9(cgYt
z1)F<6$q>(VGp`5#EJgQtv+d5+MvfxQIz*Pu+iq813q3rW{c{uoedBg5Q-fTdJ@!RE
zIVaZN*`44j+q^K#Ayr=%Qjg-e2S0q-ct9DY+XI%EY+y85y0{R$a@MP(P844JAWgjL
z;xxhPOZlr-zDU{-_B#TdmT+V=)Sa_EcQCg60rm$@`_3T?_jX15erCP~YJ(e}8*6m8
z<HAgvO=E?<S)ED<4o~CtzkElu32mm$$Nps{^qHWl`V8(Yi~*~Z<Dx6lxDx!WvyQTp
zgM9oAhdEFnaT1X`sg$mL2j?Eh-fxReTBMTxn@iZoW*MN}U+Girr>6)Brdgd-U{vM9
z$8<@a?!<3Kfu^AfzoYMBTNJila^~}*p-$1ER`wtNA+UxWDzcJxky-u-g~O0X*Vo4x
zzC$;mNvE*?O9Gn~zn`~oQ`qBZ=u}Ko?{8_DSH6J&RFUXWtVD-L{s2RjPnHk(1G*Pk
zSaFESM;dQI=4e*>D>b9cwY6BG5a&h9eHOwo_f+^w=L;{=7S>^YljchmR6VzfOJ>+t
z9|y3|`6DCr5n9_USjS(4oc<^wdf>e2=dkJSkOJi>ZH?gFt(3SwBED*U?Ow9dg`RDz
zHv)lBcA;fhwA#z!?x;~IZydxk4k<GcTj0P-csFlJ8qJ3^R9)s09mzZyFb!C#-HWdG
z)5$;YsV(8S?A8EY&J`GRyz5?t2EcKOsmymZmxfJ{683)i>yKdmp1A^FdB1~JG9OB!
zZRYb%5p%9145?e3$5pz7M3a50Bf9l>i>(nlWAK$3@;iK|f<u*e;ac4uyGPpXI+{W3
z(MBd;Tp#&~o<+8v0n_a~AxVq^ZFA)xgDLojhKjhiHmnYuNI}l*no~3csP91{6?FUA
zIL`&H7Z*MYw5WtjSpi;kdpsHF^m2RWL3@YERFPi8L?C3ot)q^4Kp#g(AX;Pyw5sEU
zEKGl_sx~~!i*f&upU|qm;sp1%qoZ|BY1FY>zsnW%%#fA4u-}S;(cR8~)eJF`1*z)S
zx=pAIZKmsEujN!H0i0Sg^^K7cyi5nNkl;Kppc>yx53?i9Qgi*_lnNSZ_|vky!=6YG
zfZB-Dw=${WLW+fq5wS#MJ9bM+wRz#W^mKZ7dN{;mBN>xG$ZO0-|2C#(RK$w<F9n$f
zwnNO3W8bN%Lw#{d+(RFdxxKWEm0rS-+vBfUUK7`qZv%Y)v`oFv%YZB=)`w5y?+YMt
z>zlm<(Ygj<F7Y4-&1t%Rb0IL5?&r3K-_6Un2fJ<dB!z^_5y<Re2^Y3zrqG-Y513U7
ze)gl_A5SGYD?yd>(??#}TEJd2GRk5a7@uueMvD^t{+mTg+l+1xMb_wwa9K<v0?;}t
zRqRqrja3@k@3wi0_+ZH<Ah(g9<jg>K;!)i&(Z;8*SIfGtN4sNj=~KETRV2p;NCn_j
z>@U~+U)@3Ka?-^pb%Na3KQlZR`J_(6nqgPKbTBtg*yG%cghG39v?9Lb<CwMtm`9W!
zSkE3`Q!$j;ddzAc!!Y-r#<kyVs!Q2;%omYxN;m*0Er;sgTeeApke>F@Rw(;WIwl(a
zCHRtRcyMS5ein|HAZ5(Vwmh+1jWyh6*wOl`U4~ZGekkfTkoVkAVG~cLNJ=xA?G|g6
zX66eb0lTrH$*gDg!jFnFCh#7PRJh|kHklBc2$jIJfH(`L*A*R|<NTM?v-<OzRUxdH
zw`fSZzuXSyHm!z%zUN3_g_S(N3Bmxe^axR>bzgC#)Ba00tBphUJV*O!O`atqKb+p_
zT>8w;42%nq;bSm~sj-6Akc~WwX{%nF3j{)VA+MVc0KHFa2B-O?N?!kcltt!dB$emx
z<Ic}iz0FI)3B?w~l1woPC}*C@#AuK2jtDLp3Ics$avQtnGrSAk%N+(^-<~$){Ik<N
z5hix7a4lgwU4VOdGiT$y@*szw$(!<KZX6(xVbEP<C$fJAc#0d<hb<f0&6LTc`<Dr$
zcn~=iQ{{J)yNDDN<B85o*vMU;9$dYB96i2jVI<(rWoOFy=H7Ro(SR6wb2HQP7(agm
zt)e#C00;R@Og-lwj;1NoHV^kn48~K$Rzo3@D@hBJpwK0#!e_gpMZ>TAbl|FAa@en5
zp|}^=VrMzzH6zB=iF-z5@af$pyYVgnp*NP227B8^zF>jZk2YhTY;L_>^GC_OZ5^O_
z-Yy6rNAB?Zu`~vVfknXRktqxvVRKYmy6c18N?y`+rB1y;B<UJPE;#TY1-492ZFjt>
z;vDiZ=>NT1q1GQJc#|%9sz{L<x7?XmdC+2DT}R;zw=fZ)(r`LuE4`YoeuwHWtjRT8
zn~$dJ9_9BKYSWL$NpB5ANAs(|3#RvQ5UfQgb~b1Papdx%;AT#9h#&5U`(qsp@+hpE
z{ksI0W)qW3g$r@prsUS^NPUyd=vPRrqs~VlSoS9E8%DRQt%jPmJ-EZf*;~%}0VNz|
zD(G$9Wr++sHX1iG4~jDDfe>tzh_9$~IZ<3Nbs)3cpNa=FY&pl|rB%Fd2NTu2ramhE
z8tr-OUEky;Q;LAk00{`-wB!;$81!b=6j2@Wzhp8l0Twbxt|?AWriP9FbV<5G2_mP7
z@_rfDbt2=?TRT(&ua2#hZ2&bk)CCOkEe>4#o-%CD7}ZAjx+=uCRAGmpVqZHVcckl(
zR?qh+<aE8%+di6uI+S}zgRr69mT#w-#Kl&0lGsY2nKj}#r=R9pwMWQ5;T3aV5&gh1
zw{oDAVlh|PsIPG<bUk0BhgVl@Nbe(!NbWOwHSt>PN2Z0vuWsoD8)P__O*hn(8b>@u
z-n;GM6O?(ru~a+`n6O}yZbFi|#i(>uuWF*#ZoSI4gbXyFoU=b|i2d@{RFWdiOqCh&
z;V1S?8^FQQ_@`+JWHo4%fC1D4wre@V7cQ_ZbIMm&RLdEp&|~`TlBRC{smI}sZenHr
zqEqHvamd65qe@u?wA+!i{6rsLKACvm#G5^bbCNc&d`!!O&q1JD+Mfc`&Ykf=BXcNd
z@*(*F^kLjI=oftoVOAo~^TJ8bl=x^OY4T}9m(o|eL~2Fto5=)geo?_LqW))}W+daB
zq)OAN)ya+?y3lk~;K~Cn2p;H+WtFb3{f&xPpcAuE_RxeaZBJ}DoSyTpRB@RvXzg*2
z>7ZSli(b=y)eG7$2;tkcp#3guj>=h^f$1e!Ts?|Hr3}XD0YyUlTfCa72w;>0mFX1J
z7x#uqoHvMQS0rgW8hk(6etnduAfubi4v8^oF^PMBu9fKf;Xnw@@`EA+8u3*AkSC@h
z1u!XEi4TEMO{J3+D@OljyDU-E=)5n0A;WDQ%)$sHq5p|>-SVN{Ww_0zhfrFnnMxsn
zR8bVepEC9n!wOfG0{@$!GcHb)OKQP5=ttN!b^p;3oJ%{*5&Pph_p1P?lpb9-_4Q~y
zMhA6%-{E%qbtB3#igWL+a)@@nIqP7iVm#<-DGfrZ%k9&Mwz-R#V?4QA&hMEJCLJ64
z?t$Q7k-fu6lOuUdD34)yp2AW2W=hC8-qD5nYU<vhg}6U67cI12Y~bHslR7ed|9n~_
z)hs?xO4m_2@w&=%1mKfJ%qp}l((b#FFq)K&(46Fl7k<c~^U=;b^~g!!UQSS8-m|?Q
zwJDeFv0ZdkhsTx13(!TL54EPcmg-HDtAQryQC6%x&Bq-s^wYvw5y4E2(L_q*^9Of-
zk7^Pg$|4ixHbk3LfV^C*{}YtKgG#o~(`lzw`<5|MV~fq4tGvEyNN2og2@9|ISg9pe
z6^%(M6-?<7BlvWepF*)H!>aQGvS&}Ng%5Hme_hr}Ccr=HOLYNjl+=xam%7r6^ps$v
zxb*ct%85Q?OH(Go=nCN;O__!Xh&+*Q@yM~~UTmVblU%qd=wN$xH2-@qc&5sFhOU=v
z9oPfA5;!!<b2ziCF)-%%#q*XS-jnWS6r>u5EwaGJT?|#b8-K9|*Zu==;JO}`irH(h
z>2eIKgXJ;M0upw+XQMD|Wax*y04|+w8^o7*Q(%xxt%T|zgjz=MGcrm^?er&s@LC<{
zUF7-#_|`?Em+mntwsDFBk#B${<Zu>md!s7|_)3;<3~6!@u5qJm<CvxO_c2GBcB)`;
zD^=<MRW-ws$0MEHZy)g?R4``A4EGo66OVM>Ad-DG<gls!_xP8048OtDSD$}eA8$sU
zH1LbT+rlKH*~BO5JIg&<X}dzvHF$nDgD2USKq+=$xj*PU-M#ay%(V{+En`8L^>fj4
zDRxYE#HL(A$SIR~YMjcb`w@pF6!ZY1<r0aMyd}4g$wtb!K``pyZnh<Dr9?QRf|T*u
z33kKG;zqmV%fs@eiE14K<i)^KLg5gnM6Bt8wK2J-+YEL#Hbyk%z`r`kU?@k5wO+lT
zn1@8&I?*E@=yJNzz65np%E;H`UNKj$BFPWNb`9)ai`E@FuFn*Tc8XylMEWy><HNmM
zO*pO;UqY!7cyUHkn{ep;l4KPs_IheSD&Mj~*bCb=RFT{fXi_G@f_;xOrnzp_>7=^}
zV!Jk)CH5Pefhk8*{}{D_3?r*OHfyvJM6)E1@*3rU!SNjhI5gdsUr`<{D#f_E@Pw1^
zQ#+_otn(obF`&<Fb}?QjBE9bm%lQ*qsrHrkP1(^8hKL?(C=Uj#Kn%NXhZIB-7sq$}
zN()@GUEZ>f!r!WvM>c7HR>jBF>>`*AON%;rOY9`QK6Fu>K7VJ5b@lJ!$g^aN{*G>}
zo-U)d=2ostN%J~M)evczcjWg|r(6mXY2C|UDgcb+-fl}h(D5K_qhPm~0n@%<A#FK)
zxObk@hGi(3_|jr`+h6>7V0J?+Kd7HzO#R(^q}tS~*>4y(8b9N}_+NYZ5U`ckhyrxM
z#V)bju+rLfY0e=%cJs<C^MnF~A(=gcGD;Ubth>7-XPl!d)&b-iYYwA3OIF%sT&}rx
zbpq%~au3oyG9;*H>`qr1^~Y$i>E170g)2pF2A;wG{zmc;i=vdR$kLd4#&DH_(YFUz
z2s9+$$@~76J-WVNEVLctFI0u`8(>5}8YzP0s9J_$N&o2XCRb~&9VY`70l14!cO#<Y
ztu^RL#nj^gslcf|lSigL>G5M;F#h6{-fTW$Ry6>IMuHuVa2IYUqc(~xv7_)x(3E-E
z3#*XK8aJmEEeE#oF1(P8yIE@B9fxwiZqBVOWH_UkaY~xall=og+*G#_jiZ>ohV>I(
zH8<XqK+R(<P-5nmUL=o%T2%KMMORHOK6hTPXEiJ1xl_=xKA~bZoE{M{oocJ+d?T>m
z^&f=v^E~9lCThaKPF5~lGYRoS?V3za0WW+koS3N=cKjyHBzBP_luK9#)%27C?O-2y
zWFPGOm72ryHgVCgk&Rqj3i>U$3ucBQ+C1l1D{Mu5)sDJT5JpwfR*Y<_o43yJ$0Jf;
zvP;v@kj&Iqm~BH~XgRrO?eT+W$`KZ8WBD46DzF<slaw+#S8j=;#civsn@cBpStxk6
zj&?oTH+Ix~K$)Ef4m;#(lI9oII0z-qf16o#dSgOyvdLsY*@?D?V(B>zpsHQ{-!!3I
zTYdu5jl+BS3k4f83gz}!X-3Y%D}$8RC2;F!_`Y3;0>6~+74%^g@ixQC%MkDjZ>srr
z6J*^2VOe^Sn|5%HtM_ZRJQhnzg}T}fT3dK@tCV$|l_=7`;ElLRq>l{vx8_X4m5mg5
z7Fbe_&0{DBs1c`;Pnc{5tD@dK0UW|**%ObT>F;e-Mcgm)WpD2U3dkaftG-gwVNv(M
zZw+u(;7Rk#(jO#Uboxr%=oZ1Y0oMt=V6{a9G@~afi1c;ECo0I^{@C2aBTX`kyfhBh
z=6LA!biaA7h3;54A`p0{M>+Ni63vJ@tfXKIYA0F^iAi~UO^aY_C!u7lms=+o-63Es
zzB8*ywbXoi#}jd}U9vm@H_#bv^hc$g%&u0kApdYOw;3sU%HWr>n{L~lW^{4`kwJLR
z$3;}X6oh3*pVCVL3*Ue6)6a`SirEEQ1vBY)CF=5-_H{H_eIJm!tOn16qZJ5|`>TJF
zMmF|nber3SunUUhxBawhBzkYXG*h}EsMsKViwA(;?6KI17+(IJ<sAyMr7H^Fz`m(w
zh_x6BeaPQF-yc+%H7}fo(YvlgaF7i|yXYavmVi&0Y~~JH(_Xv<!RAMVXB^4k4x0CF
ze}QzPmG@zi!-RYEEa>f5^rDL-(e@<-Qa19a3Gt+#oUOgsi`eHKoUeKE&7l+XH`97Q
zFh3Yc;5%nsfMwQys1M3tg=(pa!W!9|dB{QUn%k77QV|TAQVX9BRuAqQ@#7^9b4^-`
zLW2z{9s^H1$7h+67KzX}nzh?WVcm)fnF!gk@ieKudP61^W;d{iT={z2i<&Eu&3BX&
zs%V34Q>jQxc;k~24RNbSuWV>y-eIG3EWAI<;vxo=be!R1Vf%~djkBeJ9$W?$o}xT9
z1G|+~9J>zaPzN@Fj5A}or=w;2)C$ky<1MB7%X~qDP<=G#*mL+=c}Ape_0Ng2<ZvF#
z5NQk(b*4hQ*U3&s#a-mT6#m&&K-TJ`f|w~KwvSq!;;1Rtr;gH=1|bFPsPK)zeQ3%P
zLLYR99h*b}q2{(8_IWHIpIrL_GsQ`uBjvK2lNuF$81&_@$g)HX>2{!T;A2wfR8g!g
zUs<Z#?tD@Zj-LoWmO<kyq><vjV{N9-^#L>50bvCyB_7PW@{0PfP{0Tw*jYImJZ2dQ
zN#puPGkUs%DfUMrx$H-%9upQ`d7DtbhQPujLO<|a@0C=a7_d9cu2n@+GkTiHON=dP
z+Q&$;7U?}>Z;)@<y(+yBv_4wsBlMk0+adl-k0=`hmee54GXA3vvODL5`r9}0Ul%X-
zEYrmm#8jqhv#1;(%!WcVDV3KwB3Qy42ihX(JZ(2FY}?Yl)nx!%4pAjDX;fIFlPvLs
z>&($ZGSiy90CqV`Am3zB7{D3TTrGEUx;%h{G~)&t7gJUI9jso5wmum@!E2$XA>;~`
zQ2eI5^+_?q+NZ3<y_PpgO)tW13>_iO;B>7Oo15KE-lLBR7|uSKCcsp?Quexq%~jA$
zz*$_Dl5DV+nUwf#=K3piRrmp;xi+v?;RQ^#hJM7X1o{w|T&zsC-Zl8-8Q69AqLWte
z6`b-)so$$@@QphL?iJn-6PmdD3dx~1Q+x!+X2oB_CM_gJr&y3Z)IlhZhOi2B2y1rE
z!LZzwFh0vaD08_~0=?;Ce$>*HA9Kfa^sPC9K3#61>92Ki(o^`L_oo@)J|6?7C6<&N
z70>_?SP{NxGk=<!!vXEh$3O5xPt5MwUl%EILTLU(!YvIu_WCTQ6SD!kVQH?PO4uw~
zB>yl>?mrikqSRjKc{$m0p2|}R890B{K?roX37;vHp0}MKrk80}{}`xES$+BxR;`Ax
zGTPzg&0O50TCr({vTge*ZSp@zs^BR>G6{zEq2nk<m5CCz$B4nvLkMNj4$J(P(HPEH
z+Kw~T<At_A=Rt}JkEy>x)=U~4W*Ys`uPmCEwQ4f}HiiX2)6VFrW+!Rt%nvdmYxhyP
zfPtI!$HU<{#h8_h=}|u?L{(dtlAEH$%h7`M6>KyQ1{oHjRDHRsC-dZ3=7Z;q4PA6b
zEQN%~hm%cPSIXH>d2mbh)8Ehl8VK8w<2H)qm-2wgS7qoCFWCM12fLz+<QUbT1y+Yt
zx1tB?VcPgSGR5cL-#@g%7fvr+hoHW{N7w6nR7LI{NrBSh6amg8aVqe^10x~>>acc5
z`;H;pGMjc;krHIK*0y8a(;F!N5EeEtPzmWwXF-^U`v3c>vpYg1%EV`#-aBZ9J_-T5
z=DE4z?auHCx-G#nRh;W*RXGNu3klMLR9^%^4{w9JHpPsFvb=0%%YB$R&Hg-1880RI
z1d!`MhK;YGj&@$$ABAV-CAV=!K65CyIfcR5B8w$fPki(vYNzsTlTG}S%nDlkJ~U7{
zc>4#|?bZvmL(u;$Eb3yK4wN@_6nXkPt)r^EmN9Ndy0t@HGs`-|yK6mflyc67vGVJ*
zZAY!VLEDKOxRj;hJjU%)qtT;n;}G_wJzmPqiZ8HTI%P9dIf#TTb?}-&^P~i|O@O*^
z&JC+#=2vGU8Wj*8!OWC#o4%JXkl1>WO%#q%9>7qZ%<D5T7P@rV-;Zy0=SG-|EzIbP
zDXlGz0x>3>{a+8+9J4UQXa(KRMq9^vNlqRUj7xAAA3=a_J6wO*#CAf5W!(#Lk@_^)
z@z|AcfkQnNBKM=Tjz+|*NYlzU!1D?78g!R08NaB~PKBAvt@QRJB$_1mF9?T%+VwwG
zX@Bz8i{Dk_O2Wz1wU+6hWb{MDfxC0}6nZhmtlslE0;)^dcQkVqZnBhLcY~14<;1f5
z0NLe1s(f6G^WkGUgG!4vGQZd$w`{4R8p=Zv$U(VQe&aj9j#&>aR`V&cQ+!EiH=YMH
zy%MECNq7JZ2NK6VV@!;2wMF~Veks9R$4XQZSz)(2`7{_-lt|Q&(jNS|vY(_gS6^%o
zUzz~`j>B>YI;(GvqBX8VxomfYCOW9ua|SWsEVm{(#>2Dby7_zBs&mwTH^`bg&@Eg7
zwzKihDFrp4B<4&}9|<9Vb-v{L8`_zq4J_X{djst?n((4c!jZfjq0Xx^4z)kqXBA`O
zS{u4pqtd3MxbJ`F|7h_#S%!9diiP_<9^CQnn#Nvv<VBc@n&A*i)Tr>Pe@&vc_S-6o
z=Eks&5>t%p=MV$>noPRGz3;0=$yQDx)A`<cwf{IX7M%>YE?OHvyYax)G^IM~z7IWF
z<Dh(PWXkMm++`%E5K#l^x2YUQUmQz9Hr(4^o#BC8;1J$MxVs$ZVUYT{j!o%h6B)UE
zIMR-%r2bua<aP*~CHnq^ynZxo5nc}BIk~K}eu>G5c1VfMkJ@8KUM3T-y{$<&U4{V`
zm{z89_<gXGhmF4dxrwgL6R3K2y(~-_<L$6(zqz2R!=Kotf8emsKl&z)cB=+@blX5C
zqeZx7dsDq7asYj;F}gs{`I=L&f<G2}j0Ja*fX;n(GfVUV*_b-KhMcnrO_Y$@LWM@E
zy9~tp1?^?M<Fg0k8H(XxeFa)8sH?pLYDvN&6v(us=ejOi>7~F2zzifTyLMNuZ}BeC
zyKIJn0!hlEJ5qe^TUeHuJUfoR65;P{h*^m&W%@;TPkr18UT7{$s?e{b<Qk<r(3d!*
z_eh;enC)4W%h?w=Yl7UtXvYm#IbzpNgTOo?JA9fA??8s!+sr5BryAi`B{1XX03-XN
zqB3D;oK6-je2$IJ+0e0DG~!fUDMr+s9ZmIVzhtVc*GtY0r}zV&3s7-MHilCi))_eA
zhu16o4OgVli_9k^^f9a}cZtIB8HvB3t>s~a7v>eG(=nb#g7_;VzG((>s+a;yN0p0K
zSsWPWWFPB7dP8SL<VSat@E|m$@bc{K!x!GF?LmIG<V;kJI^_dUidkIt4<wU&WH=D0
z1Yobechjb<>D1|;g{~nXDg<OEmFsIGz<&T<D!#3tb{e|TunfxA5w+VyVl|Y=hQA2u
zzeDF5s1UMZA3kr6Eb9<EROZCOcn7s5CD8&1vTD;>;ioXuHiON4UuQ<m4R{9oJ?YWZ
zc$ubTwKDn^lnZVA!Wo_7R;o2OCAGbNMR)OahUGFU_@TPuqiP$-^!~_msUpqMxpkDC
zU$}+I)$V~3yDyWJi!9uxqrNo>rs}A1wPo@EgqDd%D!FMiXP5)0zQ~pK)6jXUA1qQo
z%^jC2Dw9%5fc+HT6`ZSyA|EB2-+wno6lh}p416((eG)#QwNg55q*HTwd%#CmH?v0F
zH9@^HE#Smz!*-&%FV3lbnK!drmq)8-k#J~~h@zV1Oi_qfK=CRL%h|<+Y%3#JN?3cZ
z8h9G#n|&{VOH%@P59H<AHTLnYij(2>o>VB{e0za@qk(py)s;IipOq;&OfUGF+Tp;*
zNLB%ul^6UqUkxR8=#)QegLl_Cuzl5Z*oLnufejwD3|BsaIFw~kznrqC!3oq%W3&j>
zWBz=QfMBwMt#(_a`#pe*yhw_o9I(FiY*p_SV1nE_<EPy(+)LONRZ>XW>a#t4*0SlR
z`}Ff*GvXDr@PpJXgv&MVg1XI@k}>^0TVPrvzd!CjCH`R2ASsi#3v=L9#jz_B3AtKe
z5<nNNCEsvdKfPt!8b`o$8-sLD>A~cr7%T9E#Gf|Q^k@qsb!wg^Hd8`pbbmmpmqGIx
zs4CB9HKa&J=1P!KU@W$*)xRt*t=OsKf%`4-g@3EPw2EkHIHIElBe;>li@e6lLE1>M
zbVXv*$V9yOQO69eD7yb4j75Qd3$x*AOe4c_JaMb^93W}#E%>8a10h;q1dA`c12gWM
zPrX^P@kVot9V3_!1(t5DxLHbNM0d5?enz3FcKP2e2-A`Va+Mv9!k1wfv~+76d2trJ
z6w2{kroYP`+<H3eY^?!vD6|_d#o8Gueb(uzC&~GJVJqNN9ex?ii4L4Jnsv=*kKV}2
z0S7Ywd4T5D{;C*l9f#HOPLFfBe5W$hRDPcf{OdJz+yu<+-6ICDqL?`^0&fnoOKq|{
zN<V!qDugkMEvx~mrf0Eg1G`}yxgWBt3g-h(Jv8f!838i53cHsAIYdP)uP%HPKen@C
zzI>$c82w}jS2#o(bf>dkn=jqsL%t^%)0;Tp83#Fhy~Z+IY*Kb5=m8By+EN(wYLF=*
z^LYZ&l08A}9b5=U5}sHDY#%jjABT|V*#nygz(`Ck4<s{v-ldm0!EwUfKa^)%6UIlh
zO}yIBWHJ*p`4$U_m4TB?1prP_1#BT*?L^n)ASO$a5Srx!;eA=W+^&}eb_E(=u_xht
z;;H_muZ##OlmXQDMbBxr4#c4$$q0svY6jr{lMCa!yRmi^DZcLLFl@q6Qa|}D%gXU%
zVa~me(9)S8Sg=d1$FJyAtyTrIM}>^c9bXtYVnvao8%7hXOge^I%m}4+GWYR3#Sfc>
zyW&!0$BWP*{9BQ6u>J=)=pQayJ(c|rsgo7^Rwd-+I^O8h?WA6vA~YTYF^kk4J7a`J
zdc=!NuQB_I6`8D5qGr%*(~g1r;TmaIQ%CE&9SodH&S~8>ALovyKs4A{)>eFw+>;|s
z^<IbZL6rNoyxUxvS%Frx3(Gm5MN=rZG)TEW7seyyQ%S+JN3n5*bf^F^v~4RZZLbj+
zCnugGS`mo5dEF?d`}dC&jkTus-bO?WBnJA3IOwwuZ=&^hL2Rg~ER;KxC}i|Q67EZ1
zlOXMBgEp<!cfR*8GLmhW+SKZqNVr*7csigK`i@ok+`S1&w_kgtP@3>M_+O<_zbWt%
zJ%=WgO`a&4{wYbGYR61-G_N&Vp0N<yn$7v1d<S78IG~P;6@C{*<=3ck8X4I`id1a7
z)l>rI`cM^l7bklqVAdsSn+x(K3(hsnuf>R-uK*5R+NhkY-1$t*xKlV0WKoMZHDk_2
z;Q1<uBn$MQ2Q}u&jRdwtbO$_Ak5I`af|c%!m5`e)Cvi@vea%V>$wV_9Xs^%4j31B?
z3c&LKt-i`sv|Re9#c`(J@fx?%m21vEamK!_z6s}_l0#5fQ)XAYCWl}SLRjwAx+^W%
zJFm!;8yO7nw62wJAG+}y0<pQ7aa9IS!rNR6#mc_Bwmz<<v|i%s%+;BRtbs!8d{C0B
zfuP1*v4H{JA#r^d4_ST9Wd|*~$WinP<K8KXK@15PWLNd#z*5>pN2wxxHoNwH+`Bbd
z7uHz(hc{(*nDvdVG5blD$Z-_Ogj28Hdg^HMB8b_1WUw;LwYnYcGUn>TmUSxEi`C#j
zs9<q6bUy?aMT@UAIwRl|LtF2x?5SiTw_Ld5@}5M~?=7kkzC60$*FZ4#@d`Gucyz8P
zERUO^04?BW9oUZ}*)7Rhmqx6-aE8ONUwB~X$)t1R*RzGYwmTg>?%lrVoJOMs4y39g
z(>)Oj$tTy`RX>vRPcUVS{X!OC`KD_9+K4dG;1L45BVQ?59$JO+4QWVrBw{&m0L)mu
zo!}faiTA6MHD<F64RnlMzF>$1rSD*CHvcvd{Qy2+p#nI89999Qsb&7lmOu#P-AM<X
zEBj-fX;i0l`=7rS8RcKWpPbOm`GVzXlk67>zmsmDXbIo#9T_K223r;IQ);eyr%5r!
zyJ+@usCown23|P2{W`G|Awnz>b`gvG0YMv|wsLa!EZx8UN7vc8<oy*gP?c{In1?=Z
zfSl?;{^QTqiLsac;$Je!h{aff!u9;=ZYL)-#nd7sb-28RvkedlDPpsZtVAp~fDPSE
zHTW6X(=sVgHc450(9~!Z8gS+p{ED~ce+5$2)B?e?jm$11eTDF?waXB@CpG+eEPp*#
zo>{8Y$*}g%0g6U{KdRLV{o^%dM#3m-<pE_F_mswHLG0|4im2~<_3J@Lk)Zr@N^jQW
zNn!y-9_Uq^2)}-)hb1UnvsUp=kk{_CAk{q8Wc0M$QjSvV-nusZrGjkH+*e;5h)i{;
z?mWnFrLo(-F@kL~d2M6XpBHbN66aZn1htN!u=eDNrIIynCQwOhp4b@*(;EXNOgCCc
zqSN#`%+r$?j}%}Bj_5u*I3#qq*fBIGm#XfYn$B?$oXV(x@%0u9d49torSg{}9eTe+
zmoN)iTH=n-gB7$&8h$vZZhWi|f|q-5z!DOl%!^h9?B8d4GyLM(mwGHRrn~{9!PoTu
zg<?4vET{O|nP9O6?FgIntM-39#n4gg6dEHLV;kqj#8jyaDdUU0;UrgmWT?CLu0VM$
zL}4xvrvteBNYyYl+9z>IDmp9zvzKKr7?)+6i<!IMo_25gFlh+;59*pZVsZgiX|Nd&
zLr&CQ;Rv*EBt2ze($FAdSk~7VmEp}ilDN8o7kL;SJ05VCd$I&>EgU&6!J8$1W(2`1
z?;iz)O%{{F*|{+bp+<B7BD_AP&pq#8?&u>&8zv{)Ijtatp+qL-?<^P^gyKfvzS>^j
z1B-shvt<wq_z!H%P%Szs;{IJ!;7>*vXX3F?JJ?n4$@BFaA#mK66p`8@^ljDCH;{1x
z^eQ~W5*6xRSIz`~@OzjP3~a|>ybR1CR2#B(dnWE8dHoW{ffb9~MV<#xd6;BFm4q2k
zH2}YCWEyblo!rxkc4a24qwAD53KVHZg$7FeX!?%7YpIU4UNQmH@1DzsoCa{L{aI&z
z&SqC1o4)cs<st50ez=C`w6%&EU`Z^~Aj-{W1UbkWU?P>*bKlI(#v;<a$plFK^zP<2
zLFwt3?mWMMfI!U_tdFBewwl=JP{|TEKlrFHvCOCR(4eICL2&xa=AwiQ^8w##r`0)U
zQf-b0rn|QLJx4i4NG*SF_E`Av%XqK@*pP)7Pznq1;L0C+LsqkA<B5@w<-G&ZJE@}L
zu_Bxgoma(<hRJ1wLQw|gibOmBnCdpmdZ$6w*tBC1CWqK0rV&}$)h%(!586(I8!Oae
zyyUhbg1>I1*A{#JB|Hb~x#)nAsfuZ6O0vKO?}k`7g6gWl8UBhF(`5Gzz<S*if<$(l
zS-3`^O2ip0nd;_Mq}CV_4^u8w5PP0A*wG}uox)BhAfovzajI3Y9xuab>``FQsJ~)1
zl|PrX42i6^`GPff@my?eMUqiEPDhKEaBohIZ{gP>%n5fF$AtQ~{S5h+K{SdK807gd
z-PFA!Oo$$5SES--C^cYBy2FBvh6*6h`geNj7u~lJST0*2TA_8+>zzl%Ar_@Tvwqo^
zGU~pJ`I5vC!+uwmL&eq++t07&irwrb%7C*$DnWSKf^Y$h?iH%4*rq}z<9z1t)z|#e
zdrgsTFMijU6xlReN{c(E5So}qgMxh!9e#_#z%M>j|JbjU-|3mhh85GddUpq=??!&D
z2oMzf+CFY*ambzaJ_GBslZaATb0=Qzd;w<%zDMeU-m6WvI=ov>8@-E?q2?I*ju{`a
zlHL3qFwXiCe-=d-vOO-l)X2d{DnUfza0f1z5CuU5U~;7TL2=`Ir7Z{@A2=-!qtTcE
zfh2PDwwY=c8Y+tMNN$epandxlEK`3s2hRks4NKu<&AUu54w8U2p7Yw5VwBkgf))5*
z8iGU{)>meTfcL|F47qsAZ33pFZ257D>4T-`&%%dti!ov<gOHPIL+AI_daky8SVEu*
z`_8atruz15X&N*q(^!KFpBIpZk;WyVZM$JB)83Epgzm&@SlJ|U)pca5r?@;_54AC8
z=Jp^^!krDWt>~4IP;CqCSAAp@P*A&eBmq0oOQm%_WF1DmBsO2*C&0~I`$AX}t|AaJ
zLe^XgI5ejt1M!~{;f{pG@-idso(hmjXZ!myhWS1!Z&45k4%3Vl{!~}R>z<7+ozr5P
zL+-P*M76Ll3u}!4k=g?ya;W$L-ygoVb1CB&O=I4$&jqG$eV+>eYWmtScv&Xj*9F`*
zW%{A3=h`+W@)F-0@{)*FVEk_h6hm#`meAE5OQi8(v9m$`#W<N40-ZeDn6*U(x#Nj-
z#xes3YhCAw4!kCf6q0gMyEJgP+{RSX1Ve`!_G{*wOWZ|t6@^t7xc>qmfL6#MTK3T9
zOx153-{i)|OMBi#ys7JN>MsSLF*<N2wBH(1cfcxb#$4^`H_jo=<8SvMsAhZd^XbP5
zP?#NABz>=a+y~;+%8Pxi?^nkp?D2I>s(LN6R<<jJ=1X?8p6tY}Y*ANZ1@Shl<RZto
zYo2Uu@=I0wK<)a-a48DyXS<T8CUt`<a!5HvhA(@!;EW*edz_W46N((rtgo)A4nt|J
zHPLy3dYPHyhFuHCBA)>aq5Pe;E#4)}VP%?R4(2My%NTCiTH~1lvQ+DVGEzQ$Jxh))
z6wy)tA;j4GPV}>j`<l{#x;qCOqrGE*dNT-Z>|3V}_ySN{-5zvbcATDndtKxdxP(A?
zJ0Mc^#7zK!MRFe>d0S@`p8Hd_esDllGjC_!F@fnxdC!1igS2_nYzRNiM#VxE`XFGy
zJc86%xaB_B&R_`|_*1<d$wPY^q`~^c&0P@D8I`&d+RX8N)3WGs;6NRQx_v{2&&9dG
zHp_y<VO(Yq#<7j;0K9Tnt6jzaL-SYTp|%U_KbwAcC&b*EaDT=sAq6jXq<E$N_iIzC
zK6zRJ;evCQw`_nJMecnLf_Bf^ehmkP!S2>&jT#EJ-ZMN<(Wbrf3!z{m>rp3T`rT3x
zYJIe<&2mkMB2uTvZgoy8NZs{|0Kr#j6e3>8)ps0s3f(8_KEfvrzCt^?r<L$;R?aBp
zh}ZPTO5IU2^^pgBSW|dPi)2nN!q&pAX#zNp$G&rX;ow`2-@rqxWwr+X!4MvCjN}<#
z{5$inm<@CZ(+uJ2jE1le4H*y7c?cwe9*ecl1*L52Fs<My3<Pm*dB{O#AZ&1E=&Efh
zOIbQ}x0h=or8mew@1&`oBs^<MGnwzogFsURji8c6BZzMQ#-fbeP2cNLba*IZ^+=8X
zi^5Sv6X8qc2er2uc0a4mcw^LJ)_dJC1SloT?}co*d;KwV=PM0y_*Qp(zX<jxmAygu
z7*LwLZgzyZr30R)%(o(=<N~rlhF6}rYbDgYUGH=bcr?bLMXhA|P+(_7E!nufiyKI%
zfyh3q@M98dZ-OFB*<x-bZIyCID%*3+Cv3|u6D~>n<dr|?8wTX03&nS}vx~QefN}w^
z%YlvUH;Q;E=QEL8!1HiurGV2{@ElS(W-3D0HEbPD?u9e*S+GRpn3{g!mWAw1&{9*x
z!C7eK#}z}6W)2e1f^~_9K<KRbH}Q&Zd9Dt$-@rb(f@T5O5mB0aL#7&TT&p!^@lsWo
z;aOr`1NR4wA1DEvSI(*YS$q_O&#!YEf_4xvH?FT-)4P*gh4D?q^msI@NdNo?xK!qL
z{|bQU2Vo1!iY9C&2_nNuswIQ<(H=bBQZO;lV_^Aaq;7dKR*u_ixN{!u-0xcspa%xl
z)}7U{3uv-qR!qyVTz(}=3Ei)_t2ddce!5!Mr3`@v{?W+I))yK%{|$=vJ?yNjrW46l
z8RNlo305@0*OKVgMCKj0uJ7`-8ZcrBil$58B0Jpex3K?vDSUUg(&D_?A}SeUJ_G}A
zvHSBUB|uh&H<E|LpJ(5sLBoL9K|S?THG37rsN*^V<B#_h@bj548YwSK)O8~_b>_$E
zRX7W$tcDOtB*GN7-w0#qrs*M{Qjj(FRn`X6a8HN03|6?1s_BT-&Ix}*!}8sE@s5?&
zQOV8Ru#uRz2SqdcFjH6RMi%l3b}#rfAys}KZGNRsN%6&;NqbO<yEIXsp7#&`9TqQ*
z{yrHa<)v-|dkT%yZ($l-brStovRi1gb8S+P#_xJG>=)nysSmYvb1K5xi&jdk`=srs
z&zEu-0#PQLEt6OLt|EhE+Kml5|C-)a`j9Tm-iARYBVdAvO9C+fPx>9Rly;m6e?+rJ
zlq@IkE$lIo^;WkIcWzw|h+flkQvr9ulg4mT^9?0sH%SAPOP}UUm^%l|_Z5=!k)$5<
zjC5DB!;7Ss-?2s3^1u713Bg0C1gpxL|MLk!=L0K_bwXuycZgq}`&f`?@aV|<=Pry4
z7!cEXl7lu)V>y)QtgkI$S)CS9A}J%ryENLia|sWgTk{0opOZ=HCznY>{pchGsA@*!
z3jn0|(vrtJ7jxU6($+7`{_uv$@g?D^tav0hX~1$E9!`@GGBYGQVYVh+1VQl$;Jgyr
zPwh}ZiFUT+yN4osjwdhL*cNTQb{-Ect2GUzZ{lR%5fpA$$q$ZzzYtW@W|ETIaqW`7
zfdB^ZJt-F7bmB;*8c0#t%kaEI*J3gIn_b7_RCm)pc9sb{=_1No=-O|oMIZ|#BAf%J
z`bJwJbi=WxLQUi2asTVB6L$LSAL8HUSYOdRiXzr6Oz)dqwpuJ7h6A`u!S&V%%nAV!
zB5!D=8~6G&99*1BdrlG{j)NC|-7ImNv|z=&8_N@USXyRFhvDv!UM|Wos95t5BJ;&=
zt*{>~)#}BrR_ngslpiQ--D}$?j^XDZa-R{UD(z(HfV|?>@|Qz=TD$Q;Z$T;`5^fZB
zh_uJo%KOBUN1=;NRUHn9J1x3k3c9CxgOq<Ilq+Uo(PbdIVC%oEpgDoDsvV$6m>LA@
z?qIM1MQ|yWtyf&xf=<Vi6TGX&Dh*%PJ0q7)kG@0px_m=+VlB1&gn@k3H!2j_B@?tT
zMc}PAVU<Iv##io2R3iJ}4eT`f32tC}A#7dbR_MEIcT|wdW0nq8>1W@>nGk)!_Iqc`
z<_#iC;}3mBX`Jp5&f$Li7OUDUMnZn1o+S|K{5hLp9LcV%#LMTUiH;czH2eH7j&7?|
zJ((7biyPaaQ7I4(*sE;ldDTfNp{3h&%qa+z`!H`)Nc_d|Dg01y1H}g9r1kYDBA7;P
zsv?%Qk9;#N(TP4YM*WCndB93z4|iGJg&k%Ge#f%-fF?k+u#R*T{@)`|{VAmRos*@0
zO_5-zk?pNmA>!J?`AhNEx(E#~$R(5tiL4<J))*bdDuZf2d)&WG&$xzyIMVLv5C6@G
zF-$1OlnWNBbG(g;H5Z2<d!rg;uQe?GEW0k4(%nfGd-I2xSKcIIGBlHe0dKj>UGPuY
z;mcF0jyDk<ciZREjSrA_K5xGB($@z821+N@ktniZ^!u35uA_@Tm<lA?>pF=wg#lbF
zADhNJn9&sFmX9sqF&d<aaT%!67@1R~lLjG_x|Xq1X2Z69ko8%*e~(krbDEN_T!60m
z_Xtg-t@@ox$92CB32bx6O!S5GHri$&z@XYv<?U9b^;T_fql~K$x0XBok%X310(CtE
zzjzkUcAXl$x7l|79SZ+kctwu3TeY{;!E*J^sfLX#A@?!?_J&@Uju6~(Uzr4df<kgT
z@e@;q5@n>1IXOHK<~jk`#I#|c<;rfS)N1@gAW4NHS3c{d!M_pNb74W06*Tq_%2j`=
z%!Xyr$TRoHDITkYvv8SZLt<S?AB8V$|40O+`kHNT7+0Fbr>32LR$Q{eyK=Q6?#zwr
zkcM`pnDf|$yo(EDe{#=X{#+XnO{e5bR>*$wFuAbtUS~;>iEBaV&^)xQyK}e~;SB1I
zrw>J_l^4Yo6F*S^1&-g|IPb-v<f7O*o6$B(`z>PTa>@Ejct`WY7uo-&G1u%|lVhlv
zV|sg&FHDR3<)=s|<>hv$#y4=r0{y^?`<mLJIa$}TapX5mjz;T{B)}_D5Dr^9-LxXF
z8z*cY8772l@L~mE9x~GI6~dOd^rT-z(K*2TIAi$+98&xLE+V1&htFpZyJ_htv8FH6
z^!67gTw&YwV7Cq?$s-`ebvT9?MnDLc{vS>E*jx$Mg$Xz(wr$(CZQHhO+h)fd+jcq~
z+ji2ib&`2!>Y0DAue<Krwf2{_SzAwD&R?saeWA`3plCwU8ER)}e)QKx;KI=xqQE}|
zBYX(~GiLrcmTtdp7jKr#;=`JIjz%v21h{V6lDk)-mGh|}`<0m@x4m^+v`v@_bdtjb
z-RR1TxG3b>X-?HjZ^_<+Q$>jXS#kxM)7V*-OYa@&vezBhC23-t{;NtTx?wQYNbnW-
zeh&6uPF`s)=Z`aQRM7-Uuef(f2konNK2S90Q)140KPdVaK4lKQujIM%AQk_VC<K4&
z5@mF~Q}guu6?D+_##uav&1k`e#ha8p5+mAQb_vpeEN0*x7{nVvPW_QR1Z3bryFBQq
zy<61;f`mH5;=4!wDX<x(?Q3TQi%Hb}mh+8e^>grELE+GUE2=Hcb3zmp`}*^$Q(_<@
z!7+#m+x1r{OeY9`i327<Z?T%{BqBvu7<*X9_8@t8Ug}f}d0`H3I6Byo{zP~E*;z{6
za<QKQ$}Aws;O-e$?s{^*9i&L{fWd}W?57kIYAA|-gC$VRwSz8IUamItM%eQr-`WqX
z3?CJ}<8ariq~FvJxNmr|#|-t>*wy&YyeKDZa9({{AorLm^ED9SwWm)Xi>>Rf->b@S
z1rlo8HrGK|tyyxsEJb7oI?a9nDSZ|3H%Z35JTW?*Y_|-xHi2e9;J5`JKGSHFzgjO6
zG<G^}y&vDq+O)8<yqDH1NEoBIpwc;fF%vv6F+Dd@cR9+LjlQVsvoV^n4+h)mn{sH%
zbe;vzPy3H58@N8_kgd<(;X{d_QEVg-Xvl5MUzMKZ8WEOr&(-g^JW+pE&qccisY~5@
zbYGMv_#NigeWskvF-d`dysYe;1f@%~x2Zf1pEYKL+$-P>C(T{@NB1xH@b%ewT#VzP
z?__k}_h?sd*CP&QYkA3;v80&o;k4VZkO`rPa1Je438KcDljii<fIiKImw+&@57B!_
z41L#weDi(_>)#=wb5g)jl97wFZ|}hPeJikYA|IT8F$GW;va_*j=59C<G%JG@Vy)4#
z%}HHc_)PMuS2h>vLSi%u%fp7cIM8Zp<FUtKaOfKG>Nrxrn1OVo%Wwo^&gCMP&%(w#
zu}i7PvnY+R3<MfmAsL8|<9{hfHf!jGstT}z$#4iz+H;IesC?&+|2l%L6yS*^Rjjf>
zjvc|<YV!Qie7Z%J;8FH|8p(BD+n{LV`tAVHpehqdPpkYCcqjf8*b6m~&xxJ6oRP8$
z;hlskxfArqU|VFz3V0%nn<-d5$5a)dw=|?DXJH}F6?WGc5kD~#=q_tKVAQ6Ft!z4?
z_;WjUwp{94a6yC+*t0SB8PIgqFflN?4v%>JTNjA5R$KM?E}#S566D~@g1o5hJuX6;
zkvFk?SPmblltCBC?Xop4*~=Z#{svnh!l0(0LbPZ{c$bVpx8TM~_W~xrlBrLaY9n-c
zJVmCG*<7`ZIU;lcOXq|HcV_}o+hYcd2V42UYs;S%T;$Xuaviz&Te?l^#uXrqNB*h3
zTVPydaEG)(H=z_%Rx~m?ws@7zDfWB&>9lfmaW|u19PA<93+b2h?wsZ_69V*#IlsL{
z(bWEr`Rmzk$c!r8`VC#Or)sYio;sH#j+c{@dqn(Mip}A%v>TQ48I*uIOf8O+DQ9e<
zje6xg$`SoUW)3#Yt1w(Z^UdbT!#XyY*Hu0%m*$-!ItD|n{$~zKw^42qO~H2{JLm94
zLB(Z~kaP9J+e3ZrK7Z+BPi<lG&P%ufLX=zfdLpXvv8~}Nhjxr6S7fzHQs^vmUIwFB
z)BMi6i1{Aej;|NG{)O6TXe5sSe{B3RDHTl+#?Cmx11=p~+6NS5+7eZaDM`_R%c`#O
z+Wd+^8FpJY(Y=ZNjlY8w$?@C~(Vs~@Oe%ZnOU2uU+8+gj;QcYL*aF|ma&f_%!b+9`
ziY}IsusljLRQrRz<dY6UITtvaSaH0wM;wWG>n<^lkR9`#*vM5)VrF9Ao9lrzEXK!#
z%=#HVuDCKRjU&s8UIA7I2WPt|S{IeBouhlue2eunZob6$J0y$YkFLi#!^XI-^l^d$
z;(GTYjX8G(1CVy0*F^WuFYCago9kItY=K?Py~Kc~w8)#5{1!rWuev3M7P>bTFe|ta
zIg#oK8Jc*){OL3GOHsD84hCCr*rz<7Tw7kQpg{39%VtKm-DMm1z&UV(Pa7mtDQDqf
z?O(r;VB_A<@m^m&I#j>G6Uv1ddQk5A{(k$D)R*^^_m20Zj|8z`E4l!2vS5}oQeffK
zkuX#t`@eCp2dn}%Lh3R7K8U3}GHqeG+9M~H>Moli1ROs32vMmWsM?OqMS~_(XM)Kw
zZqZ}ctBwq<lydjNNl6^1X4ati#S~T#Z91QPc!C(P`lm0=6zG4QhZvAcI<qdX<?_mj
zpo1M`0nt2kz+yKS$fU`ax*em`sE&~bohqazZbWx@RC{CRq={zDj;$1B<>cn(%QoK|
zS5sj}fu(gzqN!WY!?HJ1mZQbf2XLq<9EA7FcY`k)<snzjNGg0T>YJGf)hY$}i+RI`
zc(+yV@w?xY8yvTfAw~T3kqxUy)W$J6T+|$p@#f9>Gc%Q;G`Tt~oMouatKzJVA4H32
zC0tGc5JI~iZ8Nzha;Ih7p+8~=GBwABDoVmrxQyRX=XxU<!|%V16((2>-i@)L<&X+(
z7quj__a(@>*1I-?^xbam#NG)YJT<(Pm17ox-k9r#O3L(fC0TVhZ~I?=@@y`MD}cJ@
zy+R3zZv!n;Xnog)@gS!9YKD|hyv+H|6IEl!>W%jxu%3&mz6jV<ne+m{cmB|r1GT$`
z>y2MPK!|y=gMt5}88g*RzV>__llSqx{hnvgsqhG9Gx5ARq^AXf+&Q%xqOckq|GG(*
z=`j%p%3Cq=Im2TvQ-5Npy8BTL%{;YU=y;>0N#-xViN<x~jJ)&J(;<J5Fyv)Sl#B!X
zb8s%fTHy|1BzPF6pSKQrR-^RoR2tSgG>WXvg0rYwDdR?{gUw1~-J)-dXiDn)zx1wG
zYCS`ov}Lr(6!90cLVk=5JrD%&W?p86V#BI;=~<<7Tg?ln;W3GW$4(xDY0>{k0yb8|
zkU=-6Cs(_H95mF{bbl2BN@#3+J?faKBPy%|;J6a0sX*H29bI!)MXd+Z(2uu8#_~GS
zvN{s$tmTy;dxL&67HczPNF$tnir6KKyR{&60jAfQyc>xC;<(|u-b4%rPC{j_X6pZt
ztwz4kjG0OvR7`(*!CB&lMT>w(<(XTtvcU+rR(o>7zO|7JWQp|h#XNS;yl<C&y<A*F
zb)yWXuxVP?UDNwQIxACBwlw+8i^SJbfd@!bqcEIq$8(0lGnYbq9coJ@>Z*h;WE^fE
z6*=hJX?{0;FMrPP_dB{Fr?U$2_6X0HbRm?zd@F*8zF;vM>Q8<JT(W1bDLL%IUd^wV
zhWf=C8Ez~lM!m`>cNRs+*_w;dywvGcU&5iqImTx(nG2+0p40h+*+rw*q1@Ae{+uct
zHU+Ie6rpa=lMScy`0%T^Erd$2c3;hgbml>|nC8AeBF%5S(41H{=f)=aPG$O?es}I?
zLF$YGi({PV$=J>mpGX72-r?s&0-Tah2QiuEueRl~NqKWg&hbLQHxKlLnV(NAKLMxZ
z8usD^iYfv{t->cE@InE~SUbV93!+M6URsP^4VifCrCpxHCQC9n$lOp}K<FKf3c^WU
zZX6i6hK$<5CObWJz>h;nUESDgF@A47+(`}R^z;3})?NePpVr9rkO%RZR^k~0;YeGL
zU4S_;2jaJ8@3-<bbQlSFT-PIhA%g0BdXu?o2{@z+c0lViW`H2ZZe&F?uE0!_iB91*
zm<dO>Kqw<vVLTSJ``L-FC!>DGgTo-Bo9t$9hVqmYEs76hr%328AS|_h8*c#R7DEoA
zT&C|b_P#?>Oh^Zp_1vsFA}bjBzA%y;MHpw7jby=8T0MSn{8MvvpX~dW8EV^J3-Cx)
z>C}JRFBVL%O`E#V%vZrM$IpVMd#v`i?PWXNLTb7^UX$FX3_mL<_3X6BDh^QvA`~TE
zZiwL~{<SvHQZ9{YiY@h~?a%H_OQR1{F%D)n)xjwBJs%Er@rw<t=k*8s8gh`dDjeh6
z!zxP7qSPPlEzDT|9N3#Oq9Vwqgghh^fj?k^DwrxD@mjqSX7Qc9WEyY!y99LJYl*o2
zrF8VM$5GI@!s`&RLEnxIAX2itM(~%Px4#=lN_9Du#)BFmx-4VS?T9&Q8_XKr6;t)|
zR}qYmqwST;hZF;ac#5fKQ--iB(qPQVOhCwvq<^Iv0)O=yk<DIDo3?9Q)km#-*%PCP
zy*gWHMoLZKqAIB-UnWZpy=Yk$0F4deGXVv+I2vnFmxvyVY!1hC2#}Y>sfs7_@#qzG
zbS><YMw#q$#dn5B`8&OvraeoX<pP?XpJPB2q;+HmD5(uN>u|X_Zuay<7Q<WC+CgE(
zy+%~q9Z!b<eAhDuroBiK%{+Q0ojxVWq~F$TgwFqK=k-tJLGN9D;Gl4-D!MOOh6=(l
zHkv00FgUq68s$dDPCI6{-Vd=3ap9CovN}#}#fijQJ+kWUS0P8&aJLA1+Okq>(RBLe
z_mll17caGZ!J@3SvwQhTbUvXSdg)^v%&cGnT+)<21BkJH^pYaE<9@VSj<QnhdvtO)
z(ZdYgoKUCDy59l5f*CEd=o&rJ_{B9zMSIf3G*U`F<d#K^01^oYQuz2B>cQ|hqB$`Q
z-ljv4SGDH8JIrHqdQ5L+#C3G-4Dn0?`_JI%dP1lVW-c_f*1v*s=4mUwvD4O3KBzy2
zDam~P-&0oPDSz@D|B=}+iTp7HonO{}*|maImi!~zK6@Ff;b!{x-Y9{rmxAx$F-xvq
zBuhUO43Ci{Zwp0n68@F`>Ru&yUF5>SJ|O&3o<i>(*=eUe&IV}wh2|{2<by>c{knHz
zHTQ3i^P&+%OKt!pgR101b6~HGcN1r<_)#na{{c}dX%Aa>W{%b)LZ$&{ZqLxdjRo9g
zCCV=nztRspgs=epD&UM3`=P#DtwOjK6%fc4>(YXHUP3N$TFQ%`i>YF1c>IuJF>?fO
z9WU^v+@cc!rgg4axy!Stre2r?Dn>$j;(a4&YMR+{u|xz(^2o__%=NiP{H0?%pA7Tq
z$nWImO>|`@3oz(q4?tl}cS3VG&HF_Y!K`ZrE#|MKBZN3Yc@d?=eQ0tXGnl~Jo^@Mp
zv6*yEc`Pw9OXA$vrXDi0HQbC?)bY?{aMa;-@h!F~O)kY+*rO;?-bH(V;s+Oad<b>W
zXN4;=Zn3l8jYRhB!oG;j2!`X<QZnH!iY?F$sS+m?Me1O*l*O+$SK9LMie8Xg(+w7@
z6yLZCR!G2FV)<y+_7`jn>oaBGbmv4FT`%Pk$$&kr`#uKPrZ<9}E%2c?dDG9mFfJ>H
zOkXjX#)a@Yf0&ssfIK335Mh&<r}CZ$-*=>Gr8%9sR2QuBrQ5^uw~3ib*gICVB;Jx&
z3`7THJJC%ya&T)IoeK;mnHa|rTdJL<lUl<H!4}!nyoTEe%<ivD+#&H17(N8pvYATX
z$C)E8?ADXq{k0>;nlh9`Kl!eO7BO-E(^3WCwZP1ej<FyBOf>=ikk1#qN#XSz6jNk9
zZ5Sc$_X8+r<hi}bu8iWlc}h*YltHBbEI$Uc>L_)U3!1*B&st7?4eAijj2DblonM9`
zjuzEVW8ORk!ZFFRuq(sbJtnUK-DQHZNt%Kyse=FtGf25melytE4i%fWXVp~XPqebX
zLJtwVRnyz73>hj(qJ`qRqXez|b=I%?{1|g)G+Sam?|%!%W~>>4muRW&jBs3JEaNeL
zmC=*N=~c3Hn<K2NGQwORWJyxya>)b&j9$M^4x0O`HU$Ha5wW#fdCX<N9HW4Px`P*b
z9}e)Aq}4s#J~y}V=++TK8gkQSxeRA#P05dtSgQ_IX>2QT+x;45Kqd3|Y=WIsNZ+Yb
zGaTE;_mcs)wcj$zCq^Z<O(x0alsP9x!jCT!Tq%kllN?lCk0-%r4j+GqELa!csR)O?
zrko2*swXhIOqKyaN?(uLwGZ)VV8kr+Chx0U!ct96aZ0Q>Fs@t*7S*Xsm_lF*r#EGt
zHo3gmdL2wBid$(ZA2KhMxY#cDSY&oY4NECazE0(oDw=IXv8r-7lEFO9C!UPeYSg`O
z5Yw;5t7=+PQ*l9`jC`|v8IkY2Q8H_qhOZ}-FqYJ7$AUC^!QdNj{x|lwK>o7`w1EYE
zvZ&XFILVCS8fEcmDFU-%!p;w(44!s(v(pIt-|_Jqa|oy*tzs}|hd{m@#`NXrC)>u*
z#Tgc<z2GXO9Hqo3@*8N}1~%2a3Ny-g!w4Dj(D*Y@>!mc&A(V&NxL7@pDcbpBM)jEZ
zk}rCv?|jh8*P)m94o!g<oYFc+S*scf3IFqW73<z#DAK>o+JN=Kz@d#)oI=(<&B>|Z
zIcV)>R)Ab>XQ&`JvZhzUh|D}**r_u)gjxF3pnQ*l%gmwQ*6pwuh&ML&VeQ9V7Q0M;
zW(MU@bGUoh)E=9g|Iw{0;$;Ud)^N!1Czoxq$4TGN4y4OjaKE`UBJpB=L{H9aAMNax
zsW$E)NJQJ%<{0FF8o>ngWm1NaK2NZapvaf(;G=8!B;n~<1<DE)Dw<o^*Q#(&#Ft&Z
zKMaR<uDDqNXN9V-w&@8mJD6^_({x#@;n!c@=ogns*7<^@HVFllQ44`0C&+%Bo}J}D
znN*Ov{?PBLp7&AAzN;FJDHQMg_7Nxmi|71~aO%v1nLscWa2mZ2)gJ>a>-K9qIGYUS
z^hL2)vXe~Io0}dym8W+HvuH&wUor)A99QEUUA>%{taQrEsFL5Mz<*4VG{u2{C;y2;
z)%KSqS4R@{&QEzD5&3hB`8%`k$=j^_W`U<p1)Ml$@TX?IevSN6=`2~$(+HpQ#t0u5
ze>Ih7&(EzPBS>eq%rvU<_+*4@GADAm03BSOkcHG_=2_bw?MxYRd#)MZKT1v!;aqFa
zfb!G%IfBN?NstIK(%U9H(Qv_Mj~hbUm$LBdKcmXhi;=!~?&w4t{Ou>n(IQq|fxpL~
z?ub+hdP`}ZML0DnazItjm6Z<QS5)#C#IGJi=BAx>Guzw~2H{%>lttraJxQ=b0(1!U
z+*7`01ri@Q(cXUD=Ffr@B1>T2@RkOrLy^o0>>9vPrFSk+Gfpkd`A^E&IuuoF#9iw6
z5ek6E&TVjiwTlN*PW8sOz7b`=aGm@uv&ru1pBqD|Cv0HlJ$(kTRmw=;moRvdDaa1i
zTAXi!wH<hYxS=EQOCcI9XHBnb<=|6^-P;Q%_!S|cKgh(o@&uIb*<_@P_`I+N)mHf6
zsCYB%)lL&_jkjy)rh5^2@E$6#aQnw_#Dsh;gxr4cH*?ZWF1SXbtL>s1ul3}tWs4i@
zD`QO4KEIj#OoY*OxsSc1`s)gb&ucmMV8)d+B+*kqhX@8r&%)0^#I(Z#7q*bULnCb0
zIdllZ=6rpo!y;oSEMvhWVC{hwV(=vOHC=V*s2lxVKf0TRcQLfhxu7pKFK4iN#aSv0
z4Tb;n?ea1cHGA|A40$RDE=dDbRj}t&K<H1v+(emJxtbxtkg(eGk82-St;0W+H^84s
z74s-MM=Dq?{!!^%A>_rh3C*NgBi3*>Zbw^Wc`xWKcAmq8;iC_=LzR1+f<0zU^4B){
zz>Hru3=Hp8ljyCW@f?2C`Jj*Av;9eD_4iGO+t)#eaUk}XQ4T%%a}GUTTT<opi@6lB
zeiLNdR30U_Cw5ph+|_bxSlu!UPFIdhshV6)eSRpKdqsl#awsU}z5M;B@EY48Qeh%+
z1j^hZN@-u_jlaPGqYa#Wqlg3?0a9AermYulTD_{OwCb-*GktuBNJkSxDzhbg!)6!u
zzE<xMfyIshJ+L-|x~!t!yQ>(}9!d$B=JhK@KW^82AzmTr&d%n0yl>F=+WJTjJ3x8u
z%(Atfg^S1~$!kn}Ocy;(6AJJ>?ea}FamehWv$~{=(cE0AR1^;`XKG=*7COLJ$H|WN
z|L!;#Dl|lJ02u7IPoP5VA02RkmpR@`m)d22Ff<=xtmHI98mD9vQlA$!f<0(c@0*8N
zLok;^T?XoAR_8}3y%2RF7M`)53pfYLGUxvaN?R9CbQ<>0YtO61wZoK077Nr__}l!^
z<o<oxowa&Whm*k3RQAMX#rb6tuAcHVnNMoXt&p3-TGL|*9cYGGev4{A{wM9pqN4>W
zU=asV5Vbzp)oAr`rWD~P^UUYFI7@#?EaptfGk)AUXS$<@yz!CdZgFFu_bU*8k>5K-
z6wX9dR<`uRcU?CM1@`6M_26Wdy~ZNCp8W0aGMg-^N8|Jf?u#BCFs6XR?0q)6dJAK1
z{SfiOl6inw)OVh9X!>2Ig&GPczPqvv(Jq0-b+)POLYthqN^MWF1C#CFV>VX&p0jwB
zA@vpA1IV%@!VrW4y1|+Mi<#N&V=534LrB@<t50dMW(NZf0CKm+DKY_;pGPL(j#Tj5
zNB0XsJfqJMlt>4$uo>R*1f(C#%-`SDUTKz{>ZwCzl0!lO>)*r_G};Rli$tCgtJXXE
zAP<w;f$!1vnWT;087~|__uf*#+yoIv5;5Z$3!a1XzySOemS7KZ#YxeW!Ig>qsiKg-
z`n+lvT~7iI1zS&)iN>1&=izpRTw>UOb&RgAo*ae1S+3M!3Dl7ntm>T<@=Wxl7u0pF
z_e!LL2TNWH+pHAcmN17GZPU?s#pn^I6&U+l1UoVkQe#hbXgX717DbTqeRGHMChoMu
zeq3ryru_6I_cg8I?+CaVmUg%h*KG<!CE!_htCG(br_(i>vSZLK`ww`8`W{;liyXz|
z@5m2JQYHhQ^Xe00uF`OciG)->J0z(rYOfSzu=VYz;XB424eA2v1fBfIdE37qMgF-=
z7t)F|EDn8j)E9<0)x0+Fr_MwChR@iJuYUwXJmDHm@yl8g8LsbFj}}%_&ax4)jL;x!
z`3K?R8W0Ev5GfzrNt{bDSP(SUYxpJ2SZ6sZ6S>_fdc?R-cK3cA;ke8-Mf?lRFK#px
z?fl*JMz!O26vW8r2)y?|Dwd`mIP_8EW>z@Nx5#?Tu4YMr*9v9Zr{1hRt3g&k+z{>E
zZSuB6l9ZSjKa6W53Vb@8G?`>-9Od{&EP!}M7<>q<<Z6cnU&mG_?^%$)6iLub@4C5_
zshWp>Aa@81zSb>T1vCL`v)t0aM=|7UY!M7VAKE<fo<6w1LNyAq_YeMkN6?#`a4Aas
z1+9yeDPo$%xF^W2+DScBn^m`d_lb;fz5ne@T_z|uVb1Xt^!##yuq741TDvp*U~DwF
z^w_3IN0R}Z4wd8NqX?dd=?|JP9d$9Xj#AK^O7KqZO-6&zYram=B4Y-PXL?81TeYt9
zh*c<9LKWbX(Ed%!2oGCzi{kv@`#N`fOqYYMZ%fIbLJS=F)Xt0RR*l=b?^G;qY`~&<
z!OBcXv93>e@KnT(`qwG#WyMXiPs<Pv;aHd+WA5q#a@j4n6{MlPUb)sgtvvBXQIPVx
zyTX(hKb>%JYSh~YJBFuTsN^UbTmrqv)Bt816tBL1Xoh;relZ@5*F#36q4ccw6n~83
zht;7nV91WLC*j@R-0{GLk!2cZ<vH(G^W|+Pv>v7Y2gG?J2wwmp<8#5ft%qpKZl@q|
zXxSR(Y%u$2*OC3}a|p*HTJXxglKt$H@0X7`Yy%l|FZ+bMl{dj9%VI^Rek!Pzm&h0?
zQ10ylmFOWpUYK<#p{b7q=2*t99?f$B@yg^5dV&)cTQAdU1HEA#^8Q@j%e0bFLO%%)
zA@nuE?8Lr9^36;)2Cw3t8$z!tk{F_=mh{oL>`7-b`Xq5?7p$}R(P0Pd?-Z!W%w(zj
z2~COXH}0f86vd$XNiLlN+0oibg<T-$Cf_Rw9cyP4P*bC}FM4QGbKUV>pU?v*BL+*`
zORV746*C-P<qsB3L#TyCDGC!xDiu}O`bY-CviDRq@lkZZ*VWxVVy81U<E=VHoiF4#
zdTEZD{lt`i_tnWACTtiyJ9)Q7NR#{Tk>dm9dMqMBxjPi1`a`cztP=wp?LOgCxVB8n
z>gipU4tEPuTVom((z)v+7{$$Qcpn;M3Q69T5yR$5Yv1%l8BCz#8T6f*J^JzUeCV@I
z?7Rpa1!)_fCSxge^k1fMzh`6&u%gn14tru47hW;J?s<%mwrZvU=>!HFnqMLk6WwSw
zE?{qJ3OXcEKJ=#22(&FT(CwktihNgg?>l3oq>y6+y=)Horx0T0iw8Rh#AR7di{X@J
zoLiA&c)rHsd!YJn)9Rhh1Gt<c{n2H_<ETQp_LCL4Ytq99`fu-uC9Ps)Yks=4e3(S=
z1QwDKbt7ru>->td(9)AI^kZ$xG2(cjlYjYsUoSQ$SDqQMgtY&TZ=jxg5t-6(pj=hU
z$QWxsrs^7=pX^eQi=fblN#{OB(-^7A6Z7@?)}L=)u!rOKdnp!5G{D^ds`w@x)@g;<
zOx>&kz0`MD7lR*_FY#b;w>zd!9rGf`tbXQ(+5sp(HTR+kv>RsMgb^nOD_TM@^U@zs
z>Y`NdTPt2K7v!0eoXu*ns2)IGcs<gU<&6lEyy<EkxfahJ^X@yqG!HaFj9Z7rYSu)<
zK}diB%ANp=1dC+M6YyX26Q2t^$9&zs&9<C9+kdNp7^b_?>+vNuoNU7BvTUy73&Yc}
zb0;6h{Go9Abs}5*_!!26{0j8RaeDn%g9d@2S8$g}1=J2;ETL<Gt1_Q#ROguw=!_zn
zHk<@VvlB~38xFkZ2|fr?QLU}~E{rrmrM^m}D4nR=cBKP>eP5VYjts-V9ZK5}e%^yC
z2<$xC@~s%kbbgLa@d6NELaw>~<kRBc^!m>8`Y0S_8OaPBk~_FcqSB;&PeOsINsWJ-
z;RK2t$q}3t3uESC125Ig-cM;dR#*d0>;p*Y#YpjuhkO3cSQ6W;E2E9C`Ng6mxAU^k
zc0)|-Ho$M(peG9tdFv>WG`NRH2Lo<Xxm+l7zKeslGPT#<wFhu6lk5y0$;ssp1VIV^
zT!g(n{Ym{-!AP<1sjV$R;eX>PAys4t*%q}06E!KQB5~^38c1cObvf`>o4qxlEz_@%
znRxDX?>N9)WVoaMmNvHM2g6De32kfiijykIxz1mU1T9W(P5b1e!t0jdyN_6sB7fy{
zwM(ogp}@)@(l;W5mC&<1X}YF?llx|Z-vV9UY*F`0VmIJMU<m6Ol11P;J-uIS5WS2k
z_;16&Zu#W>IX>6cPId5XXg5~1T%T&$++(P?AY>u`Jr1ws+El6;-%J)v!kMNcN-qp0
z!tx!r$RyRgOdz8@4wZFL*EhCg!sZ3Cko&+U0e@X%2<2qJyb4zqKCeWNu$!6GtWTKP
zUyD>T<00Hbta0fpmH3Pr(Z=pBEhAY4GvS{d=%Unu=`Kc?sT133Mp8E*Jr*02QM)fv
zb=!s{#EVP2<YVEu6OM%{#c)D<z<JvZQ~TT))L9-73I3<IvGw6Z19bwk61fq7&ciH}
z*{VH+sudW?>EqRFMBpPv9!Y$yzpBxmEU=E877o~TVoW2*)vpDE3-!&}_{D5WLx}x=
z@?q!56D~k!3`s`ab8*_kppMrM>17Qj62iFMO-Kwi4k};eanMB$5fW3B*V;jT^&`M}
zwd1sL+eUWYPl;9oI|z-pLE^v9S8}9_ATglcf^<R|!)8q}dg?jZ*(-!kGUhFYg2>jg
zD7Q@Vk_18Yzu^%`RpQD?lBSzPP~0x&B=J5upJo04(dZHkYIcCK-Z><p-B2a6^+{<j
zl^`D7I>j>a(;L!V?33)~BC{aP#M1w`LW2zFYtZ|8*msyBd=RX|rxSFMpXe6jd9yzg
zx~bu)imeF{EiTb`GyB}>mRGey{FpVCElaq(CS8%511lv5e!gU30`43CO^@50uDde-
z<w<&wJ8Foz!$e?z&_q~bdy0<CrnLg?7Zc{4=^#oRQ`B6_zzb+RzVcR^Mxn7QQAl&S
za&}k=XmRV%`P*B0)R4g0z0sieQ&PX$ZnLe;V>5}{i+LMI)WqTrKTr+>Z7vg~cm;No
z=@lNuO`O{kl1d)onTnYmg}Z;one++iak-SU3EJhYJ@)7NW<>pAch7(NiJH-sR~kz#
zipr&4?cF6E3*15!e-ng_5YVw_|FOLF3O9a}e#0B<*;BmC+}Gbd(R|SwNu$2Cv@53i
zuSoWgD9qR#x$d}BVAFbvYOXdg6$>%8;)h;Pmhvvnl@Zq{uJP3_ZJgU@m)4Ph2g<=_
zaaG_Bbl!)!r$^nLW-P!*Q!Iv+y*4E;FPrCm1;DkAwsbJ!+>K5kKA&4es^PBx8GAW4
zv?=sgtdvo^Dz=c{j!u_n{CQX35FDG#G(F(G%fz77cQWmw#+QTw6jT{yFAzM*c?pR_
zUkI-!m|40q0isFxubq|qfHk@I!zCxV;N^AD=g2ei*aPVdUs4>yZd`%O))fB1*dn?~
z&T=+=ml3Z{eWhXB(eYVXP5j_n!n^CvYL|)@&RX<*<T4k@K-noP&K?)tD!4K_6F_yW
zV@w-LCz{cR1-)c}gTD$knMXw^rwEme<!BhObuClx8CvA#;dVwLrcr4R-`?^0&}qwL
zq;vN^jv(`!9w1`zHLE#<e&kzQc+{wG4|&#(?wTLnU_nY%zd@MMBo^ZPFMWtCZal*;
z*io=wKeKb7up0y~n1VAH+BOZ1tiW0Uo6H-i3%0^9W>+c_5CTF10s#N-<C4{Ng<mvd
z@#U`5b+w;ll|U@$R1y=pF=yR{>(CIsh~!v7&s+b{@>A3BS7yFv@s2iPX^-O(JptAT
zq|eNL+>w?GJojqx64U($-HmsB%zgO$THf#9AVXGsl(KUx(>$A;Eq+Z56~*ftU)Uk9
zRW0M=8#aC=k+Ly}stltzGX2*ismhorX7|zUu}MWzK0A+X++S<%E~f*kd)jc_0XsB0
z0-Gyf5eMvj5GqoM1wv&gwtJqCS{1VRTsy~~rsg}pTwfV`tUBG!={Y*4XF2jJCD{GD
zV4+ig7;*p2cv+oHY}jrRC_rv|-VpEQ5k0AApDr9p$r(g1PyrusOJ#A%ogdi&tIu;s
zDt>b5In($kwiUVsm`t)DCXr>UWtcQ(MeP+yvkp_c1G0looXDj*_lA^x6XKHAV&e6G
zy>{pl^}s_Rl{^@$Fn#(@_X-XhHCk+B%OcXHyr;kj?VuA7;D5wiu28^z)>RmISv5*U
zKtn*pKmZW`<-b7MakwFPeZaY#tuIhx@Kf9th|hy??dQx}-1Q>$wU}yjrDgG+7q1Eh
z>hB)DNyT~q14Y+L?`=s_1nMR&*Skomum7#)-#8XnNkA<Rmjl<aceY^pS-&*vmF`uR
zB<U0j@n_{A>*a5sXxVW3R<T!TP<bKx=ihJLKHNVfP)C2+^kIPFW=xWqu~+p-DGBWH
z^?M)uRMh09-2tT1<-rdY+BFJ@YYjgvi?Tl$>jYMs_r`#brxMnac4oxDP6ZeyWQafF
z?Pu*{_`%q`n4I$?gv>3;-Bc}NjizNnMZ^1N_hv0S2d~pbhIebnG?r8BeJ~iM!doFA
z<{$vb|3Y|YXrzCuahiyGJ3BLNiM=pAt^!|Wg+%j`7US2xq3=mYjg;eSlD|-;zgAqR
zY<28?<KIigaknEgh&|<NZ?orYA$<6OvIgmW8Hpp)%uxOrS_0q$U6%y=R%{8$vqBc<
zs4np)IR*6N=gSM1qT;yWao?E@1~@N^CKa@Id(2bcp*RIwWc)5?elcxjn{J;r=i5GZ
zi9e=#F(Q<1U~Pp8b!l$SKiVm8ozdeallTxYYS<Cj)wXBvVE24aLHe@J(8uUhaJH~C
zG<A0fmkJ++fJA@*p#IZk<S*KCDx8-J%P|VVjf^hb^LdYRH2O;kA%$Y0nLF^gkg>xL
zBJQ{vZ0bw!BxCZFbV0nE@2V~E;-9`Pi57Zdw+69^b>%0GFwaNCIiV%r!8_JYW$R#Q
zX5HzhHSbIJLrSZQ0)X$(T+%YdS~>rU#apiPv62?FI~9t>x%oDHIE7+M9nW=EU0=5l
zi&V1S%%l*_k~!I8o8vLrse6$G62_HMQ`};F@G4jn23+p=#*PmS%V+|+B~hY_`LT|C
z{&0(SOkzf-A<il`*s3id%jGTRzWqOC1qcB8KV>&5WS4E*9{xrUic%3($v`Yg!XDU1
z(Hw^9xz3c(Hwr;;^OGzrC<te)6Z?`7-*`cJ5vXV;fz|+>l!D6t${<K`{Ebv98@4QD
zbAc#*n2FU-jqhc>ksmR`BB9boNUS&GDK^P|lBDu`I-OBn?#aL`(N-=U!5`&3$LjCb
zK;!=VPt4HZ;Ic*VrBLC}a87h!60KLMqSb_H+94Ad(ZUv&ay9N$Qi1+SHJ;0CMH=>i
zALpYO(n7*ThyAo3GtY^BBF`kJ>TeLJTW^1gR!402l+C{FQsL<kkO>d~%zsK#C2%5O
z$oI7WN5LnwuY29u1+-?4qpQayzVmF$te4Sn8oU6#<C6){<!b8^sQ?!Tn@`JiI~sIm
z6f77*bGd0VZfSW9&DfOC2;a%24^<BdRm4n>wfP!7+V-F75|R+k#=V<oDv%T=M4UZ*
z`0%aVt|tB4e7hqf$cF2790(`87jK2J|8ZmwhnC=eKYL3X!5n`Q8EU3+nmEIDM?*r2
zlx9VBIC>)eOFOyOA@vS);$!uJ&B>IrPtGrpSEy99A_U|T1OWRV=V@Os#9_Si78QBG
z)#Dp>uppjdl1@9C6*hu}?Xozi?uqQ_!qDLvIkpT^QiWY%lfcV__W{4XBZ`+i5JpPj
zfwCwwoILnCa)M7@qDks`)Yve3a^j3Gc8F0D4u+_pE5k}JQrQJ=LB?v()iWECrS6lA
zXlm@Ha}H$lw-6?LEPsxJ{e}wH74F=Ars1yRL2xNt^ya^pAUO_VUyDZp5_i;P$?Y-b
z)&AZF1xo8yBPIxX!k}_gok`NP8QeEBx1}K0?^e!WM{w7x{CYCd6K+MvUn1yXY*e+Y
z92grp?2EP1Z%zx7VW5>B>Zj+eO>*YJ26>{Tg*?P9fZmTy@8{ksp}x?c{irI6%g87d
zD_dA*3_H&DNBnZr<}NjVToh5C8J?@>el5#>?_n5}xbUapeuq_CGZN906pIzKogq%N
zzEYux5MP&ksQIQ%WUG79Wq7fk)b_+Bg|k}sPu2V|sU$oCoHBU>k3=_0T<N#;f1@J#
zkR^7dB*vHkbFRcJ0_pO#5kCX|m?*`6v+t)bMn>#&V1*$-QMy9xq11Q7J54HB_j1ea
zfc-PU(7y>AWvnnIpoBfi$LMycfm?Bvm#)RXF!GeEh0WTkdf7uM&(DPPCm8_dHD1E6
z!?1Rygyg;9<y>vWVFl9=p;0CC&Fv#_*yT!IWfX83;{I$7wv^PqjLB+xLEC{XwDxEI
z8ihFUeBZ_iEv>}0I{LDtSnTZ41w`$&-!m=I{?_vMfN6#&EnnDpIWl>>@Aenl0<&Dz
z=(t%{YeY)N*u${WmvVXaisw>GeQ5FbE#vBsr#Pf+Vn@;X!u#mQ(e8lZO4#0Wrvug4
zEN%}g4^Ld&@L8$zmZlt8L9-slr9DlvFmTf{tACEA!b2edI1m8be@zwb^Om~HmmYoG
zW%$~7gY}5=kF!iyAmdVL+p-xD!@7igNfEs_?k*L#m;moA(%cAMZ~uLSQe`@<gO4%v
zaBr9TbZP-*k&*H>_eoB$kIp9Lr7g)pL8P7!a$e>b-X2lJUVJV$=80iRyMxhVAKtP}
zKww<_4LaPid)l2^-`o#djYjH0pvWEjsz!Ap6GvNw4;j(dioQ~?Uq%X4D>QJGTA1KF
zYIe0e5ur-TH@YxgUHL8*9u5J}g8<<FGl)rzNSj7=L5w}=qDrl*ZXiy8p2&3@Y~!+i
zMQFs_7*vguoW+yXY-Gn&E6-Ka)q`L#yJc16aIn3dW+X4}<kNN86tM)RDdqC|L|Giq
z^ULT}qOvd+AH$D)sgXGw4OA07$Ayau6Hc{UX>pOIn$_bsb{#<sJAewR-1g6%8r=3?
zGZn1pX>m^81a;)oKE@SjcKgXcULAZ`E5i|&sMI0T*6N05=Q;?6T*7^$?*rY9<`_$b
zFGB!|AOM8_5K9ZuU?fOte*yp}#nyB6o4TSxDhMJ5FDtd9%k7&w)wWZ#a%t48l`i26
z-(x?WYWQ86nA&YLy=nZmGbZ<V(VMU9_?`mo)a}w%DQoVQP7j@3dE<8JLM=KV-q!L=
zMbpEXKY)~gRMmrl02F9sSJw%n8ge+!@^RB#oYwJM;IQ%u2<-ZE7W_lf9|XjqMSTj0
zGp93>S+Q?n&sq0V=j|^SFsL>>tMQc~8~`XfI{Rb;y07K;qUQZH6Eo|-L~+gS^Kki=
z35276Gvwr8uieXw#zP?~Jd>NLP$i|p&mjOO5CGzT*tlnxgLaEAh%%v()O!l`jRhVN
zY}qPjdAoY6`S4AFSo_QJ$bk`c3?1S86fhffBE@;kKaNFxztyRcH4q!JexmnAq|R=k
z%yT?psmrAn8+)*o-#+g{&_m+J$=(#vMjaUDytO@7jdoZTIN8{;@KcO$dhl#)R@{S4
z?6q~D-ffWnfhBpmc;@R0WYEB;c2bzHmPDB}>Kea1Xq*L@YSt%}VDDN&E7P5al~?z6
z7Un%)gHa*d$(h(jWbxaxpNkGCDYr5QFpnfU<gT;Yyf%AJMSuUy2|Dv9NB{7olu@7p
zO-hH#B1?roLIBAi0Hpuib8D2UFy+3V5ZGW4EgdW$P7V>%wAn{Ug$wfFu;DpGf&~3I
zUJxOA+VwRj6_V1L6rVh!CM}B25H0wk?YT@b_W2%c=sqLYY<FeLNr{w_t}*#JC4ESq
zK&Z^~jdzXGMBZe-z8e|;86bkScGF}`@=m&&{;M<6&k0TD(ni2TQITovsb~bqt^7Vy
zA|>yYPKSX}+C+^{xbAlX(#L~@_Cv0kgOCdhi9Y?UAm_UU#Jq(fTzV&p^q>8%cGw)C
zOl?pS1uI3E0tPi_it!S<X+TKWeUizM$Ipm<8%VUj&H{1MraH+Q=&ldvb*b>*5I{2s
z0Qo<BBCg0?5A?-=b80b&FuA2h#L)Khl0sBqXJU-@R!SP_L$zfS6ScHPGwXXLWF^tg
zwou<iT^3C1wS>O(-Kn(c{z2Ifxu!^EX%d2g(C(!3;gyoE83*rW^o!o6{?}KL6){j`
ze5G*0Z4^(cqW<A2@)mhDT_R>nu{ihUvw5UI95iZ$b!`Ua_G7WWC4k_;uZkKzZsF=%
z@GeiG0G-TAh=o%UVv&^RxAujK$vr_L+sg6z@=Q+q1p<3xsqj?@U<m|(^8cTAiA^Gj
z-&-t}#rgQ=OeGtI`IDIQMinOhJK~SVyIt`gFqTT<(n*|ig(ve*yDzjzVb@CjuI`9u
z3QR96$|-ZVA<-C9S;)eIwOaW5VfiQF+O~Ks;@|7ux6e7Az7f|kF8PD}jloKE-5wbl
zbJm2~pOls9R+0Z$OS_A5RO~6}NyWMsKY@Vgvd~gR=4v4Vj4k;@w`?V5DfTw_@P@C0
zttzZV;NuGec!%IR+gL5@w6fFm2p;Vcg*CZ?8t0a8U|!N;4jcg)ptSwsSY8=C<&bH}
zgsgbQ6?^S~nZHzcDFpBg0zmzb`>P7J%~_rCA1nW0)6dO)2|<rLfLNFjsWa7fpJb6m
z*{VT${O2d{OS*W&0Dhxj-^pdLiyT>aqrKqMQOKW0U|W;d!dLYc5t=naYqc(RGMIGL
zwxTyaOF|9Jgz!ATMlSnHb~aUopKw4yQ#i|5lMGsTr_J^^Uu#|f6(2g~jEkW1&+XrU
zsz2mW^v)l~?MGK6cZ9LCUnjTqLw&AN#XOz)F(9H3MtstrYwfZmK;O_p8gM7Chu;#x
zAx`aS-^2wrbau9-;<X{5m>>YO|5OkQu@%mo@cQp57<o5?NxQ7(5w5#u1B;;DwYYO{
z%W1Gk;&}crGRKvIZpkFAXqBKHVVSvsw&g3Yw;4l9oN3rSK}535bX(yVV*(mKy(-83
zV7EF6f8SJB>>Y5D=!$#@hiC3TAPKDC1<GiX<2r|VHX@vvbu-MA+}J)-gr#qXxO{yn
zg*^c)i0@nAgJqD{SUliW{V)42Y%)NdwTl7Gb~688(i&NYu(YUAM%p_u4<af?-V`@M
zYHf(>+|2#$;hBHj?W$W`%oXxoNST1G0?Nq{m@DeT3b1g6cg0+OI4TN9IzDJ+Ys}0&
zr6hShBuGnaaPv~N+TbYYbi|NUOZH4Wkmpw&?q=Exq0S0xT{Ojz7(ZPmvlT!T0)Edb
zK@~>7l_bxQJVwgTc`zk0tbQp3=**l=!Kc=JBH<fG&kEw0X3XI2G8S8VdiocZY_@K_
z@0FoeL+a?3H2n2$rqOoQ90tXU*)ZGKXj7ibCEjm+#%ZdJ>PM4_t6(<9+XDaGQ6mV!
zPHWNWH<7uJ2;{#~9o6?^$jnV$fs&UMV6>w6ywBrzwLa1`e>kyfY_r<JovH#jrsFjU
zMR%3K>q#|Vx#>!Dc*U2EQEAR9=|`gZQ`2;Cav60VP$O`wwg~T8tH>`NUhRw&T+9}2
z31#Fy$2nZWBOR<GQb3yVl{P#APy?kHGfkl6B~8}z$jwily6!h4wQ<`LniSP0&9dS*
zSL66Sb%>oZZiHo$7EoaZ5yP;CS@sNRE9^=IHN#e&<)rIx9ac8yv0Vy+NeP;ZcaBBV
zQ4)Rc1RH755++FH2e}f*I$d+kY<6bCk!9Y%UNN2l@aAAbGUF#RcZ*ZiZg{{Q`-Cph
zC56qfl=OxwA;3t&Le%eBFN2z7#PM!>IX#rl7pfsjaSWBQ;D6jJhgyLzd-bR*NtiDk
zO}J^1%uBkwH^_R5t=aWqH;`Iwe(6qLySugLz{Y`V(-n%SgJ2&a6Kud-tWuZXv3FDK
zA4D41c?e~BLx;(B3+yw*s5ymT2SoIa3Q-HQ9dU6nok;wH2BMixch?%unqSK+seC!O
zofZPsO?u1eb1S`?<2R55`m7!O{E^ch>>-7Ik41EE_Tg^F1JjDt>>Vv#9j>R@CnQuA
z=Mh<eHRKKSS+!aiA#R+bh+hyAbvBcMQ!<SP{#5?Eto;){-cPzgqq3B^ah+E;@fb=L
z#PBVsTa?4T!@rJ}&yYIYb&ObsB#E|&Y2?(V?Dium04J)9X5}>!XM~cX0U2S)!^N=j
zQyH{<^vzHD<R56aD6aD>Nn`!A`G24eTTjZuJ>$1y+t4A`j8{Q2l!IVqlDN(&%H<e|
z>NMya&j3eI31@!~IGQ*2Kh{+|K+O+V;7cJ)z)FV>3E0;3^sT9pmM?-k-jq!|AeIbw
z+cDf`(K`yA+_Ji332DEJ>e0}P<3s$V)gzJ^b5d0-Ui0OgMX>e_dAVt66)05%66ARO
z_IHw17mW5cx(gmeOFDt%3fY3<fjVFjaa#J8pJsKRf+%LKW+7O3jQUFrisN!aGCXa;
zGvE*>K-liP{!-zn5Kt}<0Q!H|6`;>@Ga={i@AiAo+a}sP-k)kp{bdH&$K@BqF^$<3
z?6f-CozAh{v1Q!QH^%yNcZb=#IX%5Kdp8I7xtw9(A*bSPHt7)0pTN<2oUnv=Co$}>
zhQvFmnV^y0L#d{^GY5U#cI;wqQ`Ny3JL#esf@IH?W94De%q5Fb5uy-K9S{J<e<vY9
zJ!+a1HROmm#OacNQ&=-khh6Zj%-D%!C66CNWDKRbgx+YLH>vwzB>bIp;VD%7wr!Fu
zJdA5*8uI%WPSUwPcYvnk{K%K-At*uHQi28>iL-}{W*@=Lw(3^D$r6~1;%t-vVzYXu
z8C71aaO>lzLG(g1wer=68ZvQ@oLpT97lmlG1nHPgy<lVnT+$K}ReE!8AI|%4@To6j
z_ox({IF&{n3~Q}x!c)Kws;GBW6Ok80gNa?~Q@{~CVm9g`ar-}{uZ+s!ZL((8g0EEK
zLbK@~zM(QTsAFn$`{4_-%UMD{P~0_x_mT|0CTO~O#}~TFcBD8isC9@4OL0)GH-S<Z
z+-|ASF1o_E)UNXA>QZ4~2&gXz0Q0|uq;Zdlr4unNE^T7DC%HKnhG@o&(j^mr`&JC%
zermg2q;ZWYW)4Pzuy5n;qal*spV`@;%gU1O)(wEmYn7y6j2SD!uyx2qQS;*9wT7As
zYB1(k*Q01#txYDp{L7<P5aW|vbF4k1?mOAz6{~@j9Y|42gM&;%_TK&lVW6{%Img|+
zb5KTB7{bu0k334BR|3;1AEv1$Smf4sz!Ce=Q3Xxu+nMk$H?Jr=UxMDyG7#-GC8_qt
zgqPkXPDHm_4$k^1ajJD!@WRgLNLhOEcl#BGoxl(V{w&wE2%>Sc$4H+$#0+8^pFQ}X
zW(^%$N2!Q&2xuM%0P8<LBG?|zCp1Gjo!}hW($U|suI24~Y`%1ij~ih)+C6iAOOc6$
zoQg$c6q_S6wPC`@_#U1-uCKp}Tb5OcUGS2Y2fCSL2hnL+tJ9eE9U+X2DpeNkeoG4z
z{xG!4xqx`|ux15HQMj51-8U{9F@vTlTqX4`261?df#x7`j~2J~sEw3g#?50J26=@8
z{o33OOy)I4)1`2Po#RL)h`&FQr6r>)L~qZMK<kP78Er1Df?e~dt6()YpXx-2yDg~R
z1*NS=r6HxM)dA(ue@)~`g!}@5^AHt&Ax5$dLWV2Y5j3rj5KCF6v3PA@c<fH`<YyIz
zR&|-A8%6l5_IafE;OtSRLgAMCmZ@1RB_m;@(>vbG+}3qMgqrIB?_-gN=nG=uxE_jt
zC8V>KGv$w#VtEl#y&4VKz}RW1dj?mF{SZd>&lB>^hL$0jBfFPrlH>#B<1%d)BQ?l#
zDnw)2KQ*zXB90-TeINkr{~~7vM|b0fRvkU)<vzp|Vzu<X!Gb4Dcf8&LAi6YsUR1^_
z-3MvRO<~*g&o4Ghx^4^LPzNDf6iQe4`_eGwj&BMRE_G_cB0vSzh9(yUe5PYZeiDjy
zzn|SiOOJXkB}bM&!k7it%cBVn7Q=-<kvEcjF^Uw8U0&W$RD`IKqbvd7Th0LjbU5<B
zynL5vmSU*~6JdM&&ATGBDFYCvRF&<7Z|0<K+P>dwV})EfHvbOwsxG7c*=KlcWZlNd
z=&fN%FE()Vf|U->_a$<bZib)rK_3$JHP`N;xQY^Evr1Jb{=SY6NG&K^8_M;JFmMlk
zMN>mbL6#~jjAc(K1MXl1nWu6`;sS%#rVRAF@VsgImLv`Rz5@23r`I?Z`DyeV*IBKr
z9KPd9$+GfF0|pBQcZId(3;0waApL4@REVdYS6t(>mzSVJy0)!W_RM`~d=m*wG!bwg
zmwZC6wr@>JMP@=kcR>I+{{`+8X9pd59rB_;VoBbvhCd*R9PRvMtbRW(q|#W^(|99+
z@VhbT$QKA1BRBMZ^$Q&QUqlL+H_Tz`Q$qpQgGVf<WRzJ5r=;>_ItOR3k^(chvS1lb
z&u+v!B@VR&7qVzYW~F{V3Q*qduT`664KFZHdx%zUlrGBe^<ROreK|+)FC|f4GS?t?
ziOl|jOr>bJm`J;8tS8zdJ@QR%jl@q%Ka}i@n-oC{+J@F&$$Xn`F66`0$g^)bPeC0V
z|Kd%bAH77|h2K5Po1mxVH|WK+Qot#~WCI;KS4xmo)ti+^++frH4P?=xe}2&Utq0HI
z0huo5y#WqRCA?5q!iq$Pr~jvBb6~E6Z5r@7v2EM7oiuhD+qP{swv!XvZe!banlyIf
z<a@rEzW-paYiD+L@4c6DCri!1bp-yA8GJpUK6r0jQ_UpBCmoAf*`R}QkFzA27oWBI
zL(4#g+E9AyZ5vA9hBRSGc(oe;;`>MwSx!<S_`NB4C&A;y6m^PWW$$L?-*}y>EmNE_
zz?a?yt{Jq7QY}#4c|K(<If_(T84rF`Ek{D_K?%`|i#r^Q{}FzYGK=3+BuxE*LEX^V
z>pnV1#wOemkpP5Tyn&#C2ro@fj9;leg6tk{hBdU^p$DT=)=dl9MFac$6jf{#3M9%C
z4E6;8;{D5~?v#4Q=b7bGP)5@1xozUYu`w~LJ3dxS+{|%{RYYm>j<^-MTqykJknEKX
zXfAR#Q0^n~62?6%vP-r6d!M7HEla_QEaotR<>ULCiHAk;bTpNj`50UdFsc2iyX&4c
zrWUS0oiyzTNlAG{$!1z+7F_L)6@oiOcu;K|8R1e(y0cN*O8JzUgrSU5(JF*ePce5}
zBdQhI5_=Cx7aXx+Q^S%pe|tZayU2|birGWo)jx=pU{5!<D&V~b;dOGS<VTw8vTV4a
zyX4s+C9hH5A6s&~UB7M_``)@1x`=dCmsnzfR>gp!JGeGhRGVrUyB8gV$eH(Hvv`?5
zAzBWCfv#5z`7tx?RmVlSE{l+8!(l(eRTc-qOWc@J0O!ULk1k3)ukpgq<u@uI(U@Rx
zQUDPDfA1PIGjl!QSPbl_E;>qq8(K6fGR>#rVIZW~T&>RS0fU$+8>pqGzInmFK0TZ}
zI>H*q9PF9?W>b2vz+pYzKTsUJejE!*<pV%fizM8I067EdzSA8b+(V4DY4<{DA`ucg
z3eR>2okx|ON>7SjNm(CH=!j|?<s356%ihzvZFuvJ^i>Gz01O7XRh-SYKYrN48LQ&1
zR<qZckY5CSvrT7g>kfDdjCp9Tnak5ODE469+4-C#j`+ZO#%W<6*2sh8=a@kk5u~nx
zbbbr2ftlBlJI!=<Yz>3sip6mY6NH@44^UevH>@YIuhCa<)Ni}4-ztV_&D3Zf_I#X8
zS^Xu05MDa*l~P(`aOIa2;<rbHUYiAfMvXIY)j|)w3%=A1E6oz}8#g1Zd51`I-dMHH
z6mH@Vv~G!0Q;^m2)lw$yrg-_^(@g-t!<M`{%VNCC!O2P(kzUIm8;m^`?$9gs7ae!!
zXwV$dQNU<@(T#&s@=i|pa9wsHuB&vAC|5AJ7ywA{FBzHF-OmwW%jF}!1VRnar@UTq
zLGMVjIkNI!r(dNn!knYgKSuH7K!~2SMNau>_l{K#3lEL5adKiF9cI)H(e{LYZPU<f
z`7h8<EZBeGdc|Dmv(cY!u3I$Y9B8*4fNP%VH^Xb8hO|lUO3SRA?mwIrrl3~JoiB`S
z-v)z$oOexev)Q!9h=r%G;U?`$u8;XJW8W@p1Mae*0OwFMwx<#_>^?CHlDrQ9GMzMe
zevT2y=MQ<NvjIq4-BYb$n`6ApTz_Plq(!MQ+I)Wd!>x91Wk)*k3cs4!k916<*F5*f
zruVs)ETSw;U0%6DX`<)D+}wncIO%h+NsiOUrn8)GdT!a>tr2vTL^DR2%aS&wAc0=5
zW@{Tr5OhH;*8$0w_%aH*o<A5!;&(8(830K5Pn>AqegPO(pnYH)g3NA+d`$U}ED2DA
zrqY3@wOwBQ?VD-a7q%&F+8a(#ItJY$*<IU^rik!jk=zJwF{)ai8_r*n-i!Qi$UUnK
z)3+?pTu&Pe26_a)K;nY~U$=F=v#qgqAwOVt!)?B8!`V0Faj&c!jM)ap2Z~b{f2mCl
zoi-oRiyCh~&eyIUw*4wy1=eYVe}J|k8GW><-aUJjclFvJyf0sw`39gM6G!?p*G0?d
zh`iqZL}-~MCAfOi%Gq>Djpua8SXVd5IVnznnbtJQY0fz3QPVXi-Ai}?uAuatcC2xZ
zeA>gPwoWPb$~GtNR^@~!xCB8kRZ#54Yl>K8W1dw$J>ApGpi~0My-_Hj!}P4zH3RGK
z?z}ikO4l{e6$WU=!$EQSyn;=#@5e^kd$er+B|=`Nv3-umrDO_9tI|lTPi@V8dNy}%
z^ZA)unuG6q^<xFz2t!SE%!PF5!kkR*8xDEy64)2T8v$NoKW1vfWYtnDUYJ0;iA?}4
z1}nxLqvB6MgA=1_KWE3m{Pv#?x%hTfCQl{v8q8teL5{jbhCw-|^$1faNfLpo86mn`
z2R5PZE#kB~WIWNQucSP6rKt65i#YAtyG$IaNa-<)MA~uoWChA5)}D$TTO#R#uFgaO
ze+uS^({w3BqO4hc1d5pt65SuA3kK)R%3enHRwHgx=&g%<q@0US5-LPvJ1KUvnKx-+
zTJma9Fl2zzp@;-G-LL6B8hQcAj=tOmlG?$X{Pf({T93j79O+S;P_P|Jr@}2gZ<13g
ziy&twlD$ox=+#NX3tf8%GUf-cg7g>-)|=?om5w;xQ+UM;x&~qK@2S(I706#0U-fg$
zGMzFuejT;Ti|8sLa^_A~hP*7}FAa}`DZ-trvGy(zXFt3nrhewcyB^%=|Bxdsy^MR)
z93lZRhNyNwZjhLix|=XUD8iXE&E0@qCi;1{b8(wHfFLZ!!4z5P@ViV#qhMZxEt-y=
zeo&QcORE5aS4Z_P4j7mNyaq*<;L!U^X5lhocT#qBPiBD5R4+#k+w)0OEHL-58?`W)
z-pL<PaL>A2YotDXZ-2#`)X6`}EJh7rrQCC0$IS{R^E}p|&N6q2W<9DoVO^5RDsJ8`
zYEhsI^6af%PUuSL7<*}ml4jE8+xG1cVm91*xg3?>F%x+8XMSd-rO-hUtaT~cF&H7A
z73R3wkS}2*zI_WwBTc+5J%@M|>I~Dspm<1~#gU8?N7wLC2*Nr~tCQPHQ;8#xL&3%(
zdyhDZ4C3>-2Yy2(T9STmd1&x{%5r7S?Px@MD3HNgRYji0Sb3x@r>jPZVNi1=+~xh)
znWEqMt9JFq1-W_4)6;j{Swy&%<Nav=yMpn2NwO>WMkB;5m3z?-als(6q7F(e#Zk9Q
z&8#)5Y9~#8-#OwFWIPTa#cXV2bT%#yt)sQzz-jfo?t=U1N)$?vqSZ-cPt~?QY+q}}
z)fqMI@-0ean<ljrpkNzb!KNNzQ6R&`slN%Cu<R3JzQq%T;^Vkpn%9s~b1puvg&SX<
ztnRTK1B2h}gEk`n&+Z53MBj<DB?63n;i%yAkA51`+Bn%Wt~rP@rAxS3=>Segze2%6
z_IIT3eb&kzb=Y*z$@;O%S~*Z}0bU?c^I-6B0FdZkoqJU%qM%5&F-x`Z8Nw9~i&zVu
zV^`Hq&1!`vopNr@sw-3k|6w>!^ehg1#xwdED$nz3&v=(;LNMet6|PtIY87*X3}IK}
zJfSueIm*6KEvxqgf9U&*Xvm3HBSGQB^RprST^v#sG54z<h<+K(5;$zd&KO0i1kczT
zx7^t=>?j9Wf|M2GzEwGI5WCbw0n2q8CMnt^6LoM!cIwh)RH`{MD-`aoxF)Iv)Xe6M
z6J(GG`k_Fk5f}t;e^uDF{^WcSo4GzrJcu-ZmUf?-^%BR(v43POG$x+HwaOh=&#~Yo
z`2gj!5vUX5H2$K9iIzeSHqH@M%Z-4-F1IXB`_$9Kk#!E>jRf??I;70uAUPa;KF#fE
z)UL!@sE@y?AefTZJwf*})6+4H{Y{(fy2VK|S}m#up%XNUjJ}L(Wm4mgRhi-1nI0KC
zMN-S+iU+5XdunC4Pk}@wgTc!HK;r-HR&k)iZ`{uX-~x2V(JEi}$Mo}T(WEA^^L7<c
zz^fRZ#JWU4ywCDe2+@IQn=OV^L$UaaC<{=J`U1VO$06=TPjO7z>1yya*GN3_%k5Y?
zB6Qdm5Y8T`tb(gcD&Vr}rF`uIatPB@{aqazPIT*PuW33H<7w>poeN(%N)W?#1)G11
z;p15u6j+Sm37cf`d1W=6<B$`+_tm5O(iWdb4nQGQinfLu*9Eh^L5agJan|2Dcx4_V
z3G7ndctz)3yamOPbZKIeO_Qig*}P;!JYJWpR@Z?=qm!*T2kqkiE&lACG?QC4O$uaW
zbbc_nUg;90c7Gz5qFZprrtDyjH<u(nJP_{V=&Y}LQ0)}hA`MBn8ixXz&Zj&1X_=Ex
z$Y;qB6}Y>qEpFpQY!cz=)I1$4_7>-egG6P5!N&kVl7AixBR7n8aE_8I{?5Vf7u-rx
zbf(55sM`1*?jLUx!;@@{qlyk#L`6ONu!Lzp&4z7S4UssiV2F8-Yf#TuY8y(9<Cuwf
zEzR9~Me=g~F7!Xx_#n?F2S(wfUztr(t8bY!csd`^4A78OOJ6@^aWntQkYs`^3P=o6
zq?efcG}_>UqWn!g5bM6>(CI!YUc6kfm3TzWJ}qaLmMctyxWDyXK}UM$BLa5`QfEvD
zZ83$oXh0=XTngclW!Q#NC`OzQ=t81gfZXyP^Cywuz2=vQIy?i;j!mY`#mF$)o~&x}
z$4P*eUGNTtRGbJM^t~p}y*(R;^GCE%cqr*Jx?fI3&TR>eSNrv_8-zDG$w|}DMAP_H
zm*gDY^OQb~yB(pjAVGF`r%e>WT%vZd(t{taA42`<Eula^-UCh`NYW}8{1gBr{pS*K
zlLgtmCwI|ccGBcI5<Zq<0&It^96ID%_rnd=Xmg^J0LrN{nt~(>aR<;tiDB1uY0^{Y
zk5a1dbeXQ!FLm5pW4fp^+=tKFgk&v2#WPq&^W;e=1Cfh-G@WD*4e!ASsTOl2;v%1t
ze0>VW9mI1bXVU&peS}-NOtrLz9cFzxHx+|`230J%ty1+uvpj#H+7m(-xk?}bSKG&>
z!r=WO#Tuzvj=@1Z2`V_~{IM4DV{Kq-9|aM0S(h_rfFSsyRm9)K!iU+z8YKkE@WYSl
z<rE!NF5@dfd~X#a^Ak#K{bi*5h{KM0&6}{b>u&a3$*VZ1>)U>}lQHc=^4=WN<4f+T
z<pGI4sfZ&{(fOFgc@V?^J51c-1Bc!$ly9?qz;4GSeinLv)eaAW^X#D{q&a}V{SAl0
zI@AHOLn3z*8!Vg7YQ)Kv+QudXv%=-b(m~g}Vt!W!qK*e$7}<xB-M$x(`l4d3O^pJ5
zdVS<nYF|8*XguwhXze^t;Sq=>s7%9c3I%H;A?AIN>636Z=T*it0+)URfD3Gh(C=E4
zlE`X2cDM78K^U1GD(zqn$EG-k7dYbGv_;KJ6qy8$Ffq7@ENj8<j>p;=^)JpnOGHyT
zD{tQ!{P8PcK0fN0pRa51F7tawPJHvAww85~Acz;9uW;E1@{g8%Q<CUGBC=Gu7=8=z
zZvDb4)3mxr+tKGMy&^cjv2dK;D;r@m*urG_tN#uMU&=rldA38b|2fhbUNPhkXe2Wh
z{Psq#O@{=nc8L`0)(}q(g?9?lN%t4Kp(5jsd*ipx`gsB2d)3JO6<RY`pHu&?N|$|8
zw`ze=u+1UTm0Q(n%Q~E|HC-VbUuj%>PB1@}_{_Qo|C}q^!=2HoMTTs{H0(4NCGkVh
zC(skYkdP62Fm(+`<Fv#jwW&k*JJ)euduV>%Ur{xz&Nkq<$<zywP-vgk@8{_TX$g-`
zlaY_e!JeweW<>7e7l+wQ?JUCMNO}{>Zqpxt`gPlv*bG$OWu_iMw?+cn2x*~5?Q1R|
zQ(vLsqyZ?`ygcB!{Mr|`t0j}hj}o8tWEz6Y+R68-nQ*CFPikCt*I@F&&i8<$Oq@d7
z`SrT0`9`w{8=_erzk=+h&!33sQY4j-!pgQKx#gC-yg9J>R&q|f*{i7L#_vq+$6;7+
z>(qybqxw;!)+_n?I8Rv>6CiYVL-m^2SW9$gJfW7sa?P9a;@mTB3(JMbdVi%&(D>Sn
zKG}?aC6lsKecD5ITd5wm<DTE*+PqhgJPzsz@-SShgyl{ohKaS#PMyi0u4I5(r~kk|
z+>XW=VhMTBguP3fOYo$kRp1Rn4*t9y5+6m47K(RR%5nV8f0L1kXA2#;KjNZY4hW-?
zv_kC5opVeOa#kX5IQ5lMP)g#0M2xUWds!Q4^Al2OaK_CMj|w#yy7qDiNXYm!pv6gK
zuSnun-{ME#N@WFu`b)2@8E~$yAMUIUp1pz5x=XD~Dt}0I*Vs#Mc^4*HOI*<mM{TQ;
zK!f#@s4GW5$eF_`JG7C{SjsnWri`r$Y|(}Hg4(V#^CBZqiSJ7+;YQ=*PuS>*1h4ue
zfrPNal%p6*L>GaaOaIb9^%VojJv)Y=#Ia<VYU&&7#q<#4vw2Z3j(WE=Y<7tJ!{dgh
zbrEqNoT`eERbU7-yNh_S4q!V+PuEgjy14l{GN-@=Z!v(0lANOo)=ea?fUhgkw(a{*
z!_WLv|6`cmih8SC>Mey|<knsrIBp{bH^?{H<raqC{eFA}JyeE5<a7EWKX0#*?S8eS
zW2EC^Qv?dMiMi)&V3gCY(hq_}34<Zv06?;Tr4}VsLFcOX!KG)>)`T~eBn!qo<@P|e
z)4{i;F#VMr{bKEcN74JQNf@N?t$Lq7w~?byk_s&oD_pg>I*cjgrKC4rA_J~Oi-8jk
z6?OO<b$k0n!g?$o;!CGn%+CXF0%n?(yeoU(Y$Hvy+vy61-qF@({Ys{gayfLD@f>&5
z;O3w2OB>ky*H0^3al4EmgC6}h&(uy>N6IerJSxRNOArNynbdC-2$Wb}L$f{BW_KRX
zT5_aG{}23+?(DycsSqLyK9LLEA~er(A?Kms>>?a#yKfS`^9(03W-a=tWvx%IzwR;3
zt|>%q@}KrqmUEb}`|HYTceO6Zei{-+&uQbybfAXJp^U#6GMX_b7&wANx`QDY06_A8
ze%d>1Ty_^`FrY2Aw;RG-Vwb(km#n}lFvdK`sUis^O%6t*iHr{jy5EX?NCM+JSOPk?
z&Y2(WZeIBZ$1C}`6L%r|(I1F<WrEFdq<+I?XB4L8P!kS8zZ!_CH6f2jS9Bdf8BNIu
zv}uM)OGhL}qxc^oN|k?uJx78qa`GFw%E_P=beWFmMx8#7ScVha$~#SJkoV^zou$FB
zlu0p7FB@prt}ywt2{n}=f2}_X5}6H#Pyhfa{zdQGIf&Z*n;Hy9;u#kLc!lOOAaA;S
z(iE8Yv!C&@ttf)(6P<n$6kpH8ueSc-y5L@A%Nk=4ndN5kJz^IbF_Izf)1_8@YQxj+
zhV&&$h-9xEfI81!`Y<V!bNjxrFk*Bt=^Zia&;$8+5l3)aGNN96Vm*U>+v61ciT7dr
zMNK+|QV6l3H-zZS?wz3bMKi&rkY%ePuMh_av;8wWA21#XVUt1Xmcy5YsjWx-24flw
z-)5mmU>&p+w=B7&DML!m`Tw7?ARGWd%6~)x+#oQJknFYP4@tFOYq6ASSvARgp;DP2
z37p0_=C6#$o?_953!q@<A6AO9lg%f6tdb5iiJ>JKNRTB7wuD}H>?9|Avfm1Z4C!8W
zp(OK1eHQNXsA*+@NW@x!L}$Far&vs%@>u3Z{bvSIVik=fM7YXh`u#B{ehE*k(Y;Gk
z4cRa#-Vn_DJI_A{p3>~A_CopO^RrHqpDBq%YpBEw>(iGQ$WMNbnNcJbtlm(CT8s#y
z!5gy%n_T2^Rn%<dxl4ZU)T^3`{wCtsGyj$n=mWS_L-fDmqpgZit>U=u)aPk~?vNn?
zi5vw(Bm#g`|F}QwM@K;t!quE;^kTx{#+@U&5d+cO8-Z{{s15l2^g+=Z)7v4yYy9Us
z+r{#aIPE_IzuLZWGuK!QEjjMSuOPxlsB+9gf2Soh3yj}5snmIO7U=g~AYCDP)@w>8
zfwYX9IM$&e35^l!g2Z|>*muyT0*H)>pqCF!b00a#ziu~n-!ChcRJ<iNt&HSEf;Z)d
zPxDF7@y>+5L2ffvN~oht%6iR)izIulTV+LdB^{>IrEu^h%d>frp!D4J5EzOOXwysO
zMNMlp&ZI5t<`pcb=^G_eLV=_Yf*~3KK<fYPhrSiLLZ1EMIB_tCePE9Ni>PNCAfPes
z_QT%ufSQ9++a=rT#5R+)tvQwtM+y)IAp@$jj@&>aXE2^+fUc%9ukt-ZSo050=a`E6
zt!(4=to7trI!x5^{L8&uMFz2{u7v25xum0ICFP`904c*Z{#dxGa#}L-DN|;W)ZF#h
zKzTkFc92?icunbTjH>3a@_hz&)@E1&Wv?05EKe_oHg)NE<hyVNLJd_tX}!_p<G!7p
zp~J&oGcx9KQg73)HuhV|mXZC5Dp(uG`5(QQJRA#oB6fsy+7@FK?Dc_Q7j|W?u&l&%
zhzuJD&AnR8_L+*+vH=lif*^WKZ87aA7{ZNq7Ol;!=dTovcHN3G2Tgvp8puQP=yfQl
zlXP0AXmgSgrh8+LPSS%JQkWH`6aLn+oxtNwEAGg`#uP@{1aq&%v0<K)KMr(f7kBG(
z-cunPP%39td#^{+@*hjjS8UgDG_uBBNYc*c58tGxA96W2$xeqf)88SfSy4et;V{<U
zZDs52E~m6#C2YwC#YL#_Ue*H$N1zw@+B4C$XA!$88Ekg%@U6=F0R_$zJV8uSfD=yl
zmYEKf@xF(dk00c+ODf{$v|5fny_Dp8qZE%GNs)f7LmvxF?IZFI{`S25WjPxPo7kCz
zUy$vWBQo}ZY_c`Vi>NQY5ttm}3cL>9N7FEkBoq+KO{2@E0sb5xcNazXpa$Nqs9VoL
z!h0W_G__BKfsDhhbi#}5b1u1fdRpyv!}Bbt5}O6RKB@^YiFpzcNgQ{2QNG269ww6-
z-{?qwKTBQ;g1pH&x=c=ZoMMB~NgO!^0PMbx?Qf0i5zfw^F0QO)vz}8slwxxXea6n_
zSprVf!jEvZFvC94Z+cM3#})rI7@`0lJ*NzroC&EF;}V=$X{9=e&oVpI1?FTSK==W#
z{HDCz@?{7+XP%7R&%cwps@$Z!KSRtE7wAN0`6-1w2X$mgNr-B`d0E6s`5we(RuZD1
z;SoBXoOKM$NK?|labc{rKawq2jqjXm<^G1d(Y5NDqcr-M?c*~&$zbKt>TNo=JrbAH
z`T3){Ox(zw#)B--AAyyvgVVbV9;?6P+Gw-EY~aA91wOrFf&CA+mw8~H{25Yf2{o%1
zR(h^Cnp>fKoH4}C0b(bE>UbA3Hze&q+bcYW56%sZA-#V*ih3jOuncL}^R`s3>5X<P
z=kYu^Pg(Q09d-kPEskuaJHk9~`Ly)_eR2U5F9^~De_=04N+fW*`79>LDb}t<c&tU~
zRO+)#78F`C)`iC;IX%P7yiPh(qwG0>gUWZft0RFaFr0_sp6Vkgl7%6!vJb8xK!{q%
z2q|ceqls%%&60B;-HY^wBb{jgjpXk!XM0u`r}1krHrr0^;n7@kBCP_6(=Be*I8SNq
zy+5>6wjM&1#dsz#;ppTZzRRI|9=J^VZK~mMVr+M>q?&3WT($H+YmSLXt|ZLhKuqHb
zGEh!;q%#UjGCBCg*dGUu&->-LZ6={tLTk2gd&T9r!WYvF@U47Z;vV~+lQLr;b@vMv
zVvh^m9yWs<&=mn7fIY0q1lF4IeqU`Nef37)eSlocz-*8(^l39u$;keFMngx)1;^ud
zPWz5n%r0=~DqymcH8l)_h&zTI=1p8N4-YXKStTETLrkI?A%(HB7F0!$u~?TyHl<v(
zcJYS%=r*p)K)e6bEcW-{Y~n}yJIv4EuJS6Y{I1UK_U|5M=r@!bgG;;wL77y?KPF~%
zh5&(J%ALi3<nF+*<DEa=Bl|F-4ML*kKkEmh!bIsi5ci1=pL&#DN)Eo7p`1eRk}}`T
z?g`0^bib!XNAI4ErqCxn;anL{S3*iF<8R>F6GFH_%`F+YmRs1m#pWvGq_vo&)$+Ch
zMGlslj~s6fI3106h(GEr;qkV!#G(izUKi{9!1xT#T1g9+7<chS!kcP;n-*emMPkq#
zT>p~F)LD87g=13cfgo~TLQW=wh}8kYlXGN&2Y*k1Ew7?ZzN+ptx*gs&<@m+!Cs}{Q
zCdQ9Oy8RaAQ4OW%=V)Wbnwwl8NoDg#?iLbJDB}7R!I{7TQn+a<<j6vyrXrX&t)0p)
z)<Cidx_1qrKUha*b3;CIl2E5@LKPmqwu+fIvux;Mi`wudN63DZx_VQeNSG+DWT)jo
z%i0}us&0NWX-(Y>#4MD!`?Y<Bb`u`r;Q*n^bB0ld-+)XlIUxSs6iDy`(s1r_-iq@S
zE++#mTaSQ8U%j;s<pM*tT%PWRuCsi|s-1&MhF+y9ag-EOXESK!epu=lneto91!^u@
zNA6$LinHG#K0dXDk~5DkbCxg0F1iw|{_(t(suVqlV*0ic6a3#dGWoqKzg3h~=ZR{6
z33(TmgDo|V-i+B7|2zz4_g6aLr;1|JKEwK*9;+7$b8>Bvgb;_h6u|^`Fld^*4X61>
zBAfCMc7B5(>`oP@M-UY%WTF_|m048u(r9gShlm84_U`>b?L9$0d07V`uc(0RqG!LB
zL5_zjwyoGrmocN-trD-*>-D;Jg~p~&b0)PhAN3rL4I`QRy^ZBf#x}%|80-hX5e+1=
zsssUYl+90@BF>vIHLTb+zhZ`Zb3{|1YX?`fcSg|It!)YCUfp!)#_W@%?#|G5E*r|#
zYIW9pk05OXo9gIUr6%nfMI082s(Ltk4fPd+JkP=xe$7+kig&^Wn1ZR-sJRWo(+4S9
z<PvJA@d?5K66th6F)5*>0Q0hp;SC!Jt8)nYrtavU`|OY<4}aV380Za7F9uTym4<Z7
zCe{b>K1^C{z?20?4Li2LS&2h5VdSjANyc%u!S69w;;%q*v@ny_84}j+CIFTg#gN^;
zSu#cm-nqOevK+*O@B4`meC~$&^v*O(fHhSVW~K0{kR8+GAo)^kAXHGrs^xr9O4^+q
z4Q^KEZV21l(|4Dc(HmlC6>rClkiYZFv{WLm;bgH5e|wn()jqTiw||QN#%E%4(7>&Z
zF(DVD1&QngLo5P-H2<2MxQ9O>FLp0ejoDN5%Cux5=odEMstpzO8~O`<^;vrspZWGd
z23BwUq;e2nB?TibPVWt+0zM_7n=iOs?jkoa-`X9!z#XvJjb^8($|7I3tI?Lbweku$
zdy`lIQ=#ZH@&fL>KWF4fbL5l5ETPMNHRZ3gLm8s`@hY*>!hGw8Hi!rk9?BI95EA9O
zjVJJ|lV%b+tG6!N0V3GLwc=ia_5x6_R`Lfy8%Az83Gv3KsHx_)V-d$W&%Ub;t7<v>
zsP@R>^<#<$v!hxtSH+(Q+5dki2k`&^(*6T<fF&v8i^s@NV6Ut<a+zKXpBx?xxecP4
z3yS1*qy}9IXFnXidVa>O?w*qiT5U0kzxc8-F=p<*eh^jxO;`~b(EJ_lif}pV$B0U9
z6)cVm{xT!|hW7z}h2N%|`xQxGZ}pxfZazDDh_qL%bHB1}@ErPokK@)zE<+dLea;-N
z3LoD^@+~^;@9Ho6p)LTCr6zf}sEnaWb+$UtI+%UqyhheWUVr)IaFB&@1YnqONT1b7
z?J0z7?^1%q+=T*(Oawz>0)TY?aMVu`$Xu~cNyn&Q#|0ZnQ3G$4URgZu-b^njn2tvg
z*aR_LgKkB>rNKc9-8sE=*#ur52*feHWSdf|End1~*nSh{YH9hlU+Dj}OOWc8hhQZC
zB!URjscj*cB_fG}(p>W+u(lN_If6SC+wG^wIUC9?<DCg(jfKoVlTp$yDayHC)qZKw
zWpj5%?J}(pOj_79=;*R5_OsmOx-!9)GtrQKKWXj2>4a4`HAoqm4+5ZZxN!MhfWv)N
z;ZXu4iX05d4FJ;rZzRMiim+?$WyX^GZ)$u?X*2vwHTjdG&J{v#Y{VgFl5r2V%u+Co
z`vEz{szqcJ+Zhu|2vgd(*5V4z_RzZ9-8Ur_EUrJK3F)vkFE&rh%gbNGS2xFRTmr&)
z<|g;<2ocf<*EA~n3xEcVuAp2Gg-oDlneq`k;IAn%GNYWlUiv1XXk_RE+p_w7sFC5m
zcT)z{Gl}JFqeP8$+_A+v%HeZ?A4?^}rM%;1?FU#v@t;=WXVp@gy>b=lI_d~Q=~=?!
z*R=UqegDIf;;_u3_a2mBH<}*3xC8lu+;@JflRs!S*j6&<$bSr_uOZRf`T3@}GQKEm
z+6V2?p>4kuvdOoEL85?QNG$-6;eQ9n@y&lmZ6uUd{#^MTZAQ<-Fz1$4&Tx^7`0)_^
ztk{z7tf@R`(4uq;XE#oyCM=OUoN#IFL3<Khx{A$ZcW7+RLkGVZT7{_a77iY2`Gpza
zc7Y~DAVD)Duphg4W>%)$t|}BbcrLwOGF={<R99P_2Q5r9%fuK=O)(HpwrXiEic~6&
zjukCdsS+OvL0`;)JTYytg^Pz2Noof35Je>(BrCtL)=wWEomHzU-Bi_Edqu`UTBft^
z{`TnLD1czD)kYTp3Crhc;8SxA?>}!k$-6?U82bJU4=ke%@%Tb~>BkB=CXnilT<p4O
zu2iRXK;{C8R0czO1AvVG6qJlpM~Nr8$(g7rA^xc2hH*86z%pFl{$74Mzjg}UNb|O`
zMqYh;GUU(q3NyS|D&#Q|U0Be@lcz>$3l0A0h9Y&rk~wa$uI(}z|1M^yYxUs{hp)x1
zxgNObiHbszHvyj5u=*3d;h_9`4Q*c{rm7raqWfZvd?)JJ@d(ECph-`?woo8O4h0+X
z%$#KfdfFoy$R74)vZ!sXxF00a7YvyL05biHH8ms2baWWwMnm>2z?d1lytc%bj^7;1
z+(~p58>~3y+{L+HZIqnoT4#cd_*LN$U7-Tb+<9qveR^9%bh<ppW&RZx=9hY)T)R}2
zktkM4tp+6oYd$MCGe%FRQe-=8E^RFraz=+dT#-b+Bl_}Sx>sV>4`&0d6|kKZ{+l}i
zH$!n|XNce5I%_lwBG&oQr|PGnCsZ`=6;$L`bB2mpYQb6d8S^tkTL&*M^`XC|AxkW)
z{{Mv)WDfwy{14$5Tzq~cRmmwWS&B-~CqLLe{W&Es<s~EUZ(Va}b?P~{*nzkMnbrfK
zJx5Y?v4}0mlr^hUCYU8;xocH({8x>Xh939(Dr0siH=D?VrestSMB8IUp&8U3*d*#I
zXEue#vZmtM^s{H}27=){o&7{OfpB4&M*1D#?f?;)YZNtZo{YwXLI@YAGmdqL@`B+u
z1baw3_4-}g2eYW#ZFQ;MSOv(3Uvw<Ux67k|U&cVt+C?4AJJT)(0Y+fp!RK@TcO%j?
zYQ?O0*U3*)79p(Cu(S%b5{>Qm^pT~eq~w^6={3)&G~PC`HG=IJNMtn_at8op`3L`0
z2!>EfWpAhJ<G2#q$Z_(cSCF!QPy;-cb7g^2pf$zUMbySg+W6Dvx2`-JSU)FTVV2Oo
zY&86W4*y8REe(`vcHJvS6Y9j9X(+|!3fWUb{leELv9>i-i?ofOvTb-6m9B%&;*0Wu
z8DygENN>UO_MM$yOb0w7Ddn~mv412h28)AhNDgDTW(Jd4qP>Vv7g5svHBGo$mt7aV
zllU?_iS8HqV~KnL{sbru7tFqA>tS(M5z3~qws-ANHN7$;HlJwV1rCdH6C{Be4EY5B
zvi>9Uva*+Khxt{u9UQht+swGZZQxC{!PZ~bt)67Ihx^NT%U<fEdrWbvfAcHzJEp0n
zl9eeG#&r~B@y9+)D{(kR64=VT3wX*jJ}rdigkB}b70bx<ePbsAOegwM<-Q#fc$zvU
zbC0g!n|p4om;0}+cPHrY!DWGj;5*p`Z_(`dSAv4q*9rS~J)A8cztJ7YbSa(FaVTrb
zkLX&_iM}-}tX{DzS5GEXtff$L3z^snFu!^}=xGiIARZA8_4&50Xiv~z#P|LFzPtNl
zu~f5o&T4x&gX0=HBQ(5p!CD+L!=~OI0ZG9Mu?^--{p?Hk?VGPn;TGPd?2N{W^Y&4R
zoIs(N*`_r*3!`Nac}u^=I^)wv^8NwUye=aaC-`rJNO=1j&D{f9rzX}<Viv_iguqk^
zv2xmmV+g}#L7Maxm9FFAt^A}kSik9uYKa}4NXRqVLqwBg=s!n4ID892WhjkT*etPW
z*nCMofA|(e^LjU}%E}MzJXa}}KOm<ch1}3#X1I^lxM_)Y2=69Y2oLb%`_}Vpw?Inm
z3Y!TDNAUIj&3N^#Hw{ImC^l)j{{G21ZolN45n4;&f%7*El;PDwLo@5o)H5y>!q1X7
z=Q7e?4Zhyjkl-vJl|}B?7QUY8mrdZI<ioWeow^F7M%1k}XGFg}Ecc*8&#s=5?<R}A
zy$)#n+@PVZ7yhDVcnso$93)|1osA7T&@bsk>`>-|ko|M5Nb<7<>$ix9y>*6BUFEe%
zp6!t44&2aHJ!NNI=<VOx#{m1twd&8P+|m%iN0V<NtZG)BnEJVIVu>Pgr?(Jlcb6aW
zM?q?N@<aq^?IX6O<&ZnEp{N0U9o=!_GEec5<gFCLO(Qx#Fy)gTS}!`?s(6GoZs<v-
zk8Jh~go&k!_0EI4wj8mF4TVIek+N!W5;IV80o~nKF|4}6HP*r>U;?|><{^K($uaG_
zf+$PXxDJqh0#@drNv*0nTSGuUkBgVyb@m#+WX+{@EM-@OHi;(t^#88ZQCB1Dg6@YL
zD_r@A!$b6_-#!lsbDE2DeTh-*)#g*J-gkYy?5a`#p7G4G2Fr-#aR~;;m{=i0I2-RF
zDDn``bEDR~_+_YY{yB^Hq-t-NYtr5_>o5Gl?-*^~FZ90DaBIGS=k5)?e_^_Zl!x{#
zBUPYjb}mjFJ)tL7ocp3<`_34Vc8EnJ&Pb}QuGf)#V_3hps_wP9t|8rd-o6Z~YmAWe
z`O6~q_FWo@M1!GyFnxn5S$0(zK_q8%gJqMp*OA5Dgz-GxQaL}Q?5-vQXjIF<S5+)-
zIUnvMGD7}@Tnv(UB*C=Q*T1vQcw76;*^A~$Qjc20FmU<jpx7EoQiPvt5X=uG3KtAT
z0syl8s|meNG&@?ajZ?6K<ow#Hm!G`a_cIDLrZF%`!>hDk2Vbu|gZ_G(NadBq>0@#?
zSA)dDISz^PyP~%(4LdAafyF}oX{D5Do((=MDD+>dStnZl#%pCh@7upokiP?d|B`=W
z6$77?Uc>eY$hVl7Z7*|EgSbe!XzOkef_2mrh{B^<v6>@*Omyr%kyfeV;ptSO+8V}R
z&Ox>3G#-ehA2oBwbPmGV+F+NUHA$^qbMg1<HN<iF`)1b8l)K0w^AuT+M*t-c9#0}b
z4m||$g%5X9X6=B<ODZO#zHQ6r6r_8tKr{f+p`XK6wzJfO*ORj!>IVi2lhn$o{x|ay
XsCC=McgHmc1KJ~>f`PXoK=A(qjnqmU
new file mode 100644
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9f411d0e34dc3bbc0680fc19ec94ffdcee0e864b
GIT binary patch
literal 46030
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
new file mode 100644
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
--- a/dom/media/test/eme.js
+++ b/dom/media/test/eme.js
@@ -414,16 +414,17 @@ function SetupEME(test, token, params)
   });
   return v;
 }
 
 function SetupEMEPref(callback) {
   var prefs = [
     [ "media.mediasource.enabled", true ],
     [ "media.eme.apiVisible", true ],
+    [ "media.mediasource.webm.enabled", true ],
   ];
 
   if (SpecialPowers.Services.appinfo.name == "B2G" ||
       !manifestVideo().canPlayType("video/mp4")) {
     // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
     prefs.push([ "media.use-blank-decoder", true ]);
   }
 
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -1344,16 +1344,66 @@ var gEMETests = [
       "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311",
       "7e571d047e571d047e571d047e571d21" : "7e5744447e5744447e5744447e574421",
       "7e571d037e571d037e571d037e571d12" : "7e5733337e5733337e5733337e573312",
     },
     sessionType:"temporary",
     sessionCount:3,
     duration:1.60,
   },
+  {
+    name: "WebM vorbis audio & vp8 video clearkey",
+    tracks: [
+      {
+        name:"audio",
+        type:"audio/webm; codecs=\"vorbis\"",
+        fragments:[ "bipbop_360w_253kbps-clearkey-audio.webm",
+                  ],
+      },
+      {
+        name:"video",
+        type:"video/webm; codecs=\"vp8\"",
+        fragments:[ "bipbop_360w_253kbps-clearkey-video-vp8.webm",
+                  ],
+      },
+    ],
+    keys: {
+      // "keyid" : "key"
+      "f1f3ee1790527e9de47217d43835f76a" : "97b9ddc459c8d5ff23c1f2754c95abe8",
+      "8b5df745ad84145b5617c33116e35a67" : "bddfd35dd9be033ee73bc18bc1885056",
+    },
+    sessionType:"temporary",
+    sessionCount:2,
+    duration:1.60,
+  },
+  {
+    name: "WebM vorbis audio & vp9 video clearkey",
+    tracks: [
+      {
+        name:"audio",
+        type:"audio/webm; codecs=\"vorbis\"",
+        fragments:[ "bipbop_360w_253kbps-clearkey-audio.webm",
+                  ],
+      },
+      {
+        name:"video",
+        type:"video/webm; codecs=\"vp9\"",
+        fragments:[ "bipbop_360w_253kbps-clearkey-video-vp9.webm",
+                  ],
+      },
+    ],
+    keys: {
+      // "keyid" : "key"
+      "f1f3ee1790527e9de47217d43835f76a" : "97b9ddc459c8d5ff23c1f2754c95abe8",
+      "eedf63a94fa7c398ee094f123a4ee709" : "973b679a746c82f3acdb856b30e9378e",
+    },
+    sessionType:"temporary",
+    sessionCount:2,
+    duration:1.60,
+  },
 ];
 
 var gEMENonMSEFailTests = [
   {
     name:"short-cenc.mp4",
     audioType:"audio/mp4; codecs=\"mp4a.40.2\"",
     videoType:"video/mp4; codecs=\"avc1.64000d\"",
     duration:0.47,
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -177,16 +177,22 @@ support-files =
   bipbop_360w_253kbps-cenc-video-key1-1.m4s
   bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^
   bipbop_360w_253kbps-cenc-video-key1-init.mp4
   bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^
   bipbop_360w_253kbps-cenc-video-key2-1.m4s
   bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^
   bipbop_360w_253kbps-cenc-video-key2-init.mp4
   bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^
+  bipbop_360w_253kbps-clearkey-audio.webm
+  bipbop_360w_253kbps-clearkey-audio.webm^headers^
+  bipbop_360w_253kbps-clearkey-video-vp8.webm
+  bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^
+  bipbop_360w_253kbps-clearkey-video-vp9.webm
+  bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^
   bipbop_480_624kbps-cenc-audio-key1-1.m4s
   bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^
   bipbop_480_624kbps-cenc-audio-key1-2.m4s
   bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^
   bipbop_480_624kbps-cenc-audio-key1-3.m4s
   bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^
   bipbop_480_624kbps-cenc-audio-key1-4.m4s
   bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^
--- a/dom/media/test/test_eme_requestKeySystemAccess.html
+++ b/dom/media/test/test_eme_requestKeySystemAccess.html
@@ -385,22 +385,94 @@ var tests = [
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
       audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
     },
     shouldPass: true,
   },
   {
-    name: 'WebM CLEARKEY_ID not supported',
+    name: 'Basic WebM video',
+    keySystem: CLEARKEY_ID,
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['webm'],
+        videoCapabilities: [{contentType: 'video/webm'}],
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['webm'],
+      videoCapabilities: [{contentType: 'video/webm'}],
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Basic WebM audio',
+    keySystem: CLEARKEY_ID,
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['webm'],
+        audioCapabilities: [{contentType: 'audio/webm'}],
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['webm'],
+      audioCapabilities: [{contentType: 'audio/webm'}],
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Webm with Vorbis audio and VP8 video.',
+    keySystem: CLEARKEY_ID,
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['webm'],
+        videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+        audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['webm'],
+      videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+      audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Webm with Vorbis audio and VP9 video.',
+    keySystem: CLEARKEY_ID,
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['webm'],
+        videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
+        audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['webm'],
+      videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
+      audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Webm with bogus video.',
     keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['webm'],
-        videoCapabilities: [{contentType: 'video/webm'}],
+        videoCapabilities: [{contentType: 'video/webm;codecs="bogus"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: "CDM version less than",
     keySystem: CLEARKEY_ID + ".0",
     options: [
--- a/dom/media/webvtt/WebVTTParserWrapper.js
+++ b/dom/media/webvtt/WebVTTParserWrapper.js
@@ -49,19 +49,19 @@ WebVTTParserWrapper.prototype =
     };
   },
 
   convertCueToDOMTree: function(window, cue)
   {
     return WebVTT.convertCueToDOMTree(window, cue.text);
   },
 
-  processCues: function(window, cues, overlay)
+  processCues: function(window, cues, overlay, controls)
   {
-    WebVTT.processCues(window, cues, overlay);
+    WebVTT.processCues(window, cues, overlay, controls);
   },
 
   classDescription: "Wrapper for the JS WebVTT implementation (vtt.js)",
   classID: Components.ID(WEBVTTPARSERWRAPPER_CID),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebVTTParserWrapper]),
   classInfo: XPCOMUtils.generateCI({
     classID:    WEBVTTPARSERWRAPPER_CID,
     contractID: WEBVTTPARSERWRAPPER_CONTRACTID,
--- a/dom/media/webvtt/nsIWebVTTParserWrapper.idl
+++ b/dom/media/webvtt/nsIWebVTTParserWrapper.idl
@@ -72,16 +72,17 @@ interface nsIWebVTTParserWrapper : nsISu
    * overlap avoidance using the dimensions of 'overlay'. Finally, it adds the
    * computed divs to the VTTCues display state property for use later.
    *
    * @param window  A window object with which it will create the DOM tree
    *                and containing div element.
    * @param cues    An array of VTTCues who need there display state to be
    *                computed.
    * @param overlay The HTMLElement that the cues will be displayed within.
+   * @param controls The video control element that will affect cues position.
    */
   void processCues(in mozIDOMWindow window, in nsIVariant cues,
-                   in nsISupports overlay);
+                   in nsISupports overlay, in nsISupports controls);
 };
 
 %{C++
 #define NS_WEBVTTPARSERWRAPPER_CONTRACTID "@mozilla.org/webvttParserWrapper;1"
 %}
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -1115,39 +1115,54 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
 
   var FONT_SIZE_PERCENT = 0.05;
   var FONT_STYLE = "sans-serif";
   var CUE_BACKGROUND_PADDING = "1.5%";
 
   // Runs the processing model over the cues and regions passed to it.
   // @param overlay A block level element (usually a div) that the computed cues
   //                and regions will be placed into.
-  WebVTT.processCues = function(window, cues, overlay) {
+  // @param controls  A Control bar element. Cues' position will be
+  //                 affected and repositioned according to it.
+  WebVTT.processCues = function(window, cues, overlay, controls) {
     if (!window || !cues || !overlay) {
       return null;
     }
 
     // Remove all previous children.
     while (overlay.firstChild) {
       overlay.removeChild(overlay.firstChild);
     }
 
+    var controlBar;
+    var controlBarShown;
+
+    if (controls) {
+      controlBar = controls.ownerDocument.getAnonymousElementByAttribute(
+        controls, "class", "controlBar");
+      controlBarShown = controlBar ? !!controlBar.clientHeight : false;
+    }
+
     var paddedOverlay = window.document.createElement("div");
     paddedOverlay.style.position = "absolute";
     paddedOverlay.style.left = "0";
     paddedOverlay.style.right = "0";
     paddedOverlay.style.top = "0";
     paddedOverlay.style.bottom = "0";
     paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
     overlay.appendChild(paddedOverlay);
 
     // Determine if we need to compute the display states of the cues. This could
     // be the case if a cue's state has been changed since the last computation or
     // if it has not been computed yet.
     function shouldCompute(cues) {
+      if (controlBarShown) {
+        return true;
+      }
+
       for (var i = 0; i < cues.length; i++) {
         if (cues[i].hasBeenReset || !cues[i].displayState) {
           return true;
         }
       }
       return false;
     }
 
@@ -1164,16 +1179,21 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
         fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
     var styleOptions = {
       font: fontSize + "px " + FONT_STYLE
     };
 
     (function() {
       var styleBox, cue;
 
+      if (controlBarShown) {
+        // Add an empty output box that cover the same region as video control bar.
+        boxPositions.push(BoxPosition.getSimpleBoxPosition(controlBar));
+      }
+
       for (var i = 0; i < cues.length; i++) {
         cue = cues[i];
 
         // Compute the intial position and styles of the cue div.
         styleBox = new CueStyleBox(window, cue, styleOptions);
         paddedOverlay.appendChild(styleBox.div);
 
         // Move the cue div to it's correct line position.
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -94,16 +94,17 @@ DIRS += [
     'camera',
     'audiochannel',
     'broadcastchannel',
     'messagechannel',
     'promise',
     'smil',
     'telephony',
     'tv',
+    'url',
     'voicemail',
     'inputmethod',
     'webidl',
     'xbl',
     'xml',
     'xslt',
     'xul',
     'resourcestats',
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -1032,17 +1032,17 @@ Notification::SetAlertName()
 already_AddRefed<Notification>
 Notification::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aTitle,
                           const NotificationOptions& aOptions,
                           ErrorResult& aRv)
 {
   // FIXME(nsm): If the sticky flag is set, throw an error.
   ServiceWorkerGlobalScope* scope = nullptr;
-  UNWRAP_WORKER_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
+  UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
   if (scope) {
     aRv.ThrowTypeError<MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER>();
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<Notification> notification =
     CreateAndShow(aGlobal.Context(), global, aTitle, aOptions,
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -148,17 +148,17 @@ public:
   GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
                                 already_AddRefed<PromiseWorkerProxy>&& aProxy,
                                 nsresult aStatus,
                                 const nsAString& aEndpoint,
                                 const nsAString& aScope,
                                 nsTArray<uint8_t>&& aRawP256dhKey,
                                 nsTArray<uint8_t>&& aAuthSecret,
                                 nsTArray<uint8_t>&& aAppServerKey)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    : WorkerRunnable(aWorkerPrivate)
     , mProxy(Move(aProxy))
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
     , mRawP256dhKey(Move(aRawP256dhKey))
     , mAuthSecret(Move(aAuthSecret))
     , mAppServerKey(Move(aAppServerKey))
   { }
@@ -355,17 +355,17 @@ private:
 };
 
 class PermissionResultRunnable final : public WorkerRunnable
 {
 public:
   PermissionResultRunnable(PromiseWorkerProxy *aProxy,
                            nsresult aStatus,
                            PushPermissionState aState)
-    : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
+    : WorkerRunnable(aProxy->GetWorkerPrivate())
     , mProxy(aProxy)
     , mStatus(aStatus)
     , mState(aState)
   {
     AssertIsOnMainThread();
   }
 
   bool
--- a/dom/push/PushSubscription.cpp
+++ b/dom/push/PushSubscription.cpp
@@ -59,17 +59,17 @@ NS_IMPL_ISUPPORTS(UnsubscribeResultCallb
 
 class UnsubscribeResultRunnable final : public WorkerRunnable
 {
 public:
   UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
                             already_AddRefed<PromiseWorkerProxy>&& aProxy,
                             nsresult aStatus,
                             bool aSuccess)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    : WorkerRunnable(aWorkerPrivate)
     , mProxy(Move(aProxy))
     , mStatus(aStatus)
     , mSuccess(aSuccess)
   {
     AssertIsOnMainThread();
   }
 
   bool
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -1,15 +1,16 @@
 /* -*- 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/ArrayUtils.h"
+#include "mozilla/Preferences.h"
 #include "nsCOMPtr.h"
 #include "nsCSPParser.h"
 #include "nsCSPUtils.h"
 #include "nsIConsoleService.h"
 #include "nsIContentPolicy.h"
 #include "nsIScriptError.h"
 #include "nsIStringBundle.h"
 #include "nsNetUtil.h"
@@ -115,16 +116,17 @@ nsCSPTokenizer::tokenizeCSPPolicy(const 
 
   nsCSPTokenizer tokenizer(aPolicyString.BeginReading(),
                            aPolicyString.EndReading());
 
   tokenizer.generateTokens(outTokens);
 }
 
 /* ===== nsCSPParser ==================== */
+bool nsCSPParser::sCSPExperimentalEnabled = false;
 
 nsCSPParser::nsCSPParser(cspTokens& aTokens,
                          nsIURI* aSelfURI,
                          nsCSPContext* aCSPContext,
                          bool aDeliveredViaMetaTag)
  : mCurChar(nullptr)
  , mEndChar(nullptr)
  , mHasHashOrNonce(false)
@@ -132,16 +134,21 @@ nsCSPParser::nsCSPParser(cspTokens& aTok
  , mChildSrc(nullptr)
  , mFrameSrc(nullptr)
  , mTokens(aTokens)
  , mSelfURI(aSelfURI)
  , mPolicy(nullptr)
  , mCSPContext(aCSPContext)
  , mDeliveredViaMetaTag(aDeliveredViaMetaTag)
 {
+  static bool initialized = false;
+  if (!initialized) {
+    initialized = true;
+    Preferences::AddBoolVarCache(&sCSPExperimentalEnabled, "security.csp.experimentalEnabled");
+  }
   CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
 }
 
 nsCSPParser::~nsCSPParser()
 {
   CSPPARSERLOG(("nsCSPParser::~nsCSPParser"));
 }
 
@@ -1002,37 +1009,32 @@ nsCSPParser::directiveValue(nsTArray<nsC
 
   // special case handling of the referrer directive (since it doesn't contain
   // source lists)
   if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
     referrerDirectiveValue();
     return;
   }
 
-  // special case handling of the require-sri-for directive (since it doesn't
-  // contain a source lists but rather types, e.g., style or script)
-  if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) {
-    // handled in directive()
-    return;
-  }
-
   // Otherwise just forward to sourceList
   sourceList(outSrcs);
 }
 
 // directive-name = 1*( ALPHA / DIGIT / "-" )
 nsCSPDirective*
 nsCSPParser::directiveName()
 {
   CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s",
                NS_ConvertUTF16toUTF8(mCurToken).get(),
                NS_ConvertUTF16toUTF8(mCurValue).get()));
 
   // Check if it is a valid directive
-  if (!CSP_IsValidDirective(mCurToken)) {
+  if (!CSP_IsValidDirective(mCurToken) ||
+       (!sCSPExperimentalEnabled &&
+         CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR))) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "couldNotProcessUnknownDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
 
   // The directive 'reflected-xss' is part of CSP 1.1, see:
   // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -106,16 +106,18 @@ class nsCSPParser {
                                                    bool aDeliveredViaMetaTag);
 
   private:
     nsCSPParser(cspTokens& aTokens,
                 nsIURI* aSelfURI,
                 nsCSPContext* aCSPContext,
                 bool aDeliveredViaMetaTag);
 
+    static bool sCSPExperimentalEnabled;
+
     ~nsCSPParser();
 
 
     // Parsing the CSP using the source-list from http://www.w3.org/TR/CSP11/#source-list
     nsCSPPolicy*        policy();
     void                directive();
     nsCSPDirective*     directiveName();
     void                directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs);
--- a/dom/security/test/TestCSPParser.cpp
+++ b/dom/security/test/TestCSPParser.cpp
@@ -23,16 +23,18 @@ class nsXPIDLString;
 template<class T> class nsReadingIterator;
 #endif
 
 #include "nsIContentSecurityPolicy.h"
 #include "nsNetUtil.h"
 #include "TestHarness.h"
 #include "nsIScriptSecurityManager.h"
 #include "mozilla/dom/nsCSPContext.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
 
 #ifndef MOZILLA_INTERNAL_API
 #undef nsString_h___
 #undef nsAString_h___
 #undef nsReadableUtils_h___
 #endif
 
 /*
@@ -161,20 +163,33 @@ nsresult runTest(uint32_t aExpectedPolic
 }
 
 // ============================= run Tests ========================
 
 nsresult runTestSuite(const PolicyTest* aPolicies,
                       uint32_t aPolicyCount,
                       uint32_t aExpectedPolicyCount) {
   nsresult rv;
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  bool experimentalEnabledCache = false;
+  if (prefs)
+  {
+    prefs->GetBoolPref("security.csp.experimentalEnabled", &experimentalEnabledCache);
+    prefs->SetBoolPref("security.csp.experimentalEnabled", true);
+  }
+
   for (uint32_t i = 0; i < aPolicyCount; i++) {
     rv = runTest(aExpectedPolicyCount, aPolicies[i].policy, aPolicies[i].expectedResult);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+
+  if (prefs) {
+    prefs->SetBoolPref("security.csp.experimentalEnabled", experimentalEnabledCache);
+  }
+
   return NS_OK;
 }
 
 // ============================= TestDirectives ========================
 
 nsresult TestDirectives() {
 
   static const PolicyTest policies[] =
--- a/dom/security/test/sri/mochitest.ini
+++ b/dom/security/test/sri/mochitest.ini
@@ -32,8 +32,9 @@ support-files =
   style_301.css^headers^
 
 [test_script_sameorigin.html]
 [test_script_crossdomain.html]
 [test_sri_disabled.html]
 [test_style_crossdomain.html]
 [test_style_sameorigin.html]
 [test_require-sri-for_csp_directive.html]
+[test_require-sri-for_csp_directive_disabled.html]
--- a/dom/security/test/sri/test_require-sri-for_csp_directive.html
+++ b/dom/security/test/sri/test_require-sri-for_csp_directive.html
@@ -9,36 +9,38 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a>
 <iframe style="width:200px;height:200px;" id="test_frame"></iframe>
 </body>
 <script type="application/javascript">
+  SpecialPowers.setBoolPref("security.csp.experimentalEnabled", true);
   SimpleTest.waitForExplicitFinish();
   function handler(event) {
     switch (event.data) {
       case 'good_sriLoaded':
         ok(true, "Eligible SRI resources was correctly loaded.");
         break;
       case 'bad_nonsriLoaded':
         ok(false, "Eligible non-SRI resource should be blocked by the CSP!");
         break;
       case 'good_nonsriBlocked':
         ok(true, "Eligible non-SRI resources was correctly blocked by the CSP.");
         break;
       case 'finish':
         var blackText = frame.contentDocument.getElementById('black-text');
         var blackTextColor = frame.contentWindow.getComputedStyle(blackText, null).getPropertyValue('color');
-        ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
+        ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should not be black.");
         removeEventListener('message', handler);
         SimpleTest.finish();
         break;
       default:
+        ok(false, 'Something is wrong here');
         break;
     }
   }
   addEventListener("message", handler);
   var frame = document.getElementById("test_frame");
   frame.src = "iframe_require-sri-for_main.html";
 </script>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_require-sri-for_csp_directive_disabled.html
@@ -0,0 +1,46 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for diabled SRI require-sri-for CSP directive</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a>
+<iframe style="width:200px;height:200px;" id="test_frame"></iframe>
+</body>
+<script type="application/javascript">
+  SpecialPowers.setBoolPref("security.csp.experimentalEnabled", false);
+  SimpleTest.waitForExplicitFinish();
+  function handler(event) {
+    switch (event.data) {
+      case 'good_sriLoaded':
+        ok(true, "Eligible SRI resources was correctly loaded.");
+        break;
+      case 'bad_nonsriLoaded':
+        ok(true, "Eligible non-SRI resource should be blocked by the CSP!");
+        break;
+      case 'good_nonsriBlocked':
+        ok(false, "Eligible non-SRI resources was correctly blocked by the CSP.");
+        break;
+      case 'finish':
+        var blackText = frame.contentDocument.getElementById('black-text');
+        var blackTextColor = frame.contentWindow.getComputedStyle(blackText, null).getPropertyValue('color');
+        ok(blackTextColor != 'rgb(0, 0, 0)', "The second part should still be black.");
+        removeEventListener('message', handler);
+        SimpleTest.finish();
+        break;
+      default:
+        ok(false, 'Something is wrong here');
+        break;
+    }
+  }
+  addEventListener("message", handler);
+  var frame = document.getElementById("test_frame");
+  frame.src = "iframe_require-sri-for_main.html";
+</script>
+</html>
--- a/dom/storage/DOMStorage.cpp
+++ b/dom/storage/DOMStorage.cpp
@@ -107,23 +107,16 @@ void
 DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData,
                     ErrorResult& aRv)
 {
   if (!CanUseStorage(nullptr, this)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  Telemetry::Accumulate(GetType() == LocalStorage
-      ? Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES
-      : Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES, aKey.Length());
-  Telemetry::Accumulate(GetType() == LocalStorage
-      ? Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES
-      : Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES, aData.Length());
-
   nsString data;
   bool ok = data.Assign(aData, fallible);
   if (!ok) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   nsString old;
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -484,18 +484,16 @@ DOMStorageDBThread::OpenAndUpdateDatabas
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 DOMStorageDBThread::InitDatabase()
 {
-  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer;
-
   nsresult rv;
 
   // Here we are on the worker thread. This opens the worker connection.
   MOZ_ASSERT(!NS_IsMainThread());
 
   rv = OpenAndUpdateDatabase();
   NS_ENSURE_SUCCESS(rv, rv);
 
rename from dom/base/URL.cpp
rename to dom/url/URL.cpp
--- a/dom/base/URL.cpp
+++ b/dom/url/URL.cpp
@@ -1,192 +1,306 @@
 /* -*- 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 "URL.h"
 
-#include "nsGlobalWindow.h"
 #include "DOMMediaStream.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaSource.h"
 #include "mozilla/dom/URLBinding.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/nsIRemoteBlob.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
 #include "nsHostObjectProtocolHandler.h"
-#include "nsServiceManagerUtils.h"
 #include "nsIIOService.h"
-#include "nsEscape.h"
+#include "nsIURL.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
-#include "nsIURL.h"
-#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URL, mParent, mSearchParams)
+///////////////////////////////////////////////////////////////////////////////
+// URL for main-thread
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// The URL implementation for the main-thread
+class URLMainThread final : public URL
+{
+public:
+  static already_AddRefed<URLMainThread>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+              URL& aBase, ErrorResult& aRv);
+
+  static already_AddRefed<URLMainThread>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+              const Optional<nsAString>& aBase, ErrorResult& aRv);
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(URL)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(URL)
+  static already_AddRefed<URLMainThread>
+  Constructor(nsISupports* aParent, const nsAString& aURL,
+              const nsAString& aBase, ErrorResult& aRv);
+
+  static already_AddRefed<URLMainThread>
+  Constructor(nsISupports* aParent, const nsAString& aURL, nsIURI* aBase,
+              ErrorResult& aRv);
+
+  static void
+  CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+                  const objectURLOptions& aOptions, nsAString& aResult,
+                  ErrorResult& aRv)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    CreateObjectURLInternal(aGlobal, aBlob.Impl(),
+                            NS_LITERAL_CSTRING(BLOBURI_SCHEME), aOptions,
+                            aResult, aRv);
+  }
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+  static void
+  CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+                  const objectURLOptions& aOptions, nsAString& aResult,
+                  ErrorResult& aRv)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    CreateObjectURLInternal(aGlobal, &aStream,
+                            NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME), aOptions,
+                            aResult, aRv);
+  }
+
+  static void
+  CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+                  const objectURLOptions& aOptions, nsAString& aResult,
+                  ErrorResult& aRv);
+
+  static void
+  CreateObjectURLInternal(const GlobalObject& aGlobal, nsISupports* aObject,
+                          const nsACString& aScheme,
+                          const objectURLOptions& aOptions,
+                          nsAString& aResult, ErrorResult& aRv);
+
+  static void
+  RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+                  ErrorResult& aRv);
+
+  URLMainThread(nsISupports* aParent, already_AddRefed<nsIURI> aURI)
+    : URL(aParent)
+    , mURI(aURI)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual void
+  GetHref(nsAString& aHref, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHref(const nsAString& aHref, ErrorResult& aRv) override;
+
+  virtual void
+  GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const override;
 
-URL::URL(nsISupports* aParent, already_AddRefed<nsIURI> aURI)
-  : mParent(aParent)
-  , mURI(aURI)
+  virtual void
+  GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const override;
+
+  virtual void
+  SetProtocol(const nsAString& aProtocol, ErrorResult& aRv) override;
+
+  virtual void
+  GetUsername(nsAString& aUsername, ErrorResult& aRv) const override;
+
+  virtual void
+  SetUsername(const nsAString& aUsername, ErrorResult& aRv) override;
+
+  virtual void
+  GetPassword(nsAString& aPassword, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPassword(const nsAString& aPassword, ErrorResult& aRv) override;
+
+  virtual void
+  GetHost(nsAString& aHost, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHost(const nsAString& aHost, ErrorResult& aRv) override;
+
+  virtual void
+  GetHostname(nsAString& aHostname, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHostname(const nsAString& aHostname, ErrorResult& aRv) override;
+
+  virtual void
+  GetPort(nsAString& aPort, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPort(const nsAString& aPort, ErrorResult& aRv) override;
+
+  virtual void
+  GetPathname(nsAString& aPathname, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPathname(const nsAString& aPathname, ErrorResult& aRv) override;
+
+  virtual void
+  GetSearch(nsAString& aSearch, ErrorResult& aRv) const override;
+
+  virtual void
+  GetHash(nsAString& aHost, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHash(const nsAString& aHash, ErrorResult& aRv) override;
+
+  virtual void UpdateURLSearchParams() override;
+
+  virtual void
+  SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) override;
+
+  nsIURI*
+  GetURI() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mURI;
+  }
+
+private:
+  ~URLMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  nsCOMPtr<nsIURI> mURI;
+};
+
+/* static */ already_AddRefed<URLMainThread>
+URLMainThread::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+                           URL& aBase, ErrorResult& aRv)
 {
-}
-
-JSObject*
-URL::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return URLBinding::Wrap(aCx, this, aGivenProto);
+  MOZ_ASSERT(NS_IsMainThread());
+  URLMainThread& base = static_cast<URLMainThread&>(aBase);
+  return Constructor(aGlobal.GetAsSupports(), aURL, base.GetURI(), aRv);
 }
 
-/* static */ already_AddRefed<URL>
-URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
-                 URL& aBase, ErrorResult& aRv)
-{
-  return Constructor(aGlobal.GetAsSupports(), aUrl, aBase.GetURI(), aRv);
-}
-
-/* static */ already_AddRefed<URL>
-URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
-                 const Optional<nsAString>& aBase, ErrorResult& aRv)
+/* static */ already_AddRefed<URLMainThread>
+URLMainThread::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+                           const Optional<nsAString>& aBase, ErrorResult& aRv)
 {
   if (aBase.WasPassed()) {
-    return Constructor(aGlobal.GetAsSupports(), aUrl, aBase.Value(), aRv);
+    return Constructor(aGlobal.GetAsSupports(), aURL, aBase.Value(), aRv);
   }
 
-  return Constructor(aGlobal.GetAsSupports(), aUrl, nullptr, aRv);
+  return Constructor(aGlobal.GetAsSupports(), aURL, nullptr, aRv);
 }
 
-/* static */ already_AddRefed<URL>
-URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
-                 const nsAString& aBase, ErrorResult& aRv)
+/* static */ already_AddRefed<URLMainThread>
+URLMainThread::Constructor(nsISupports* aParent, const nsAString& aURL,
+                           const nsAString& aBase, ErrorResult& aRv)
 {
-  return Constructor(aGlobal.GetAsSupports(), aUrl, aBase, aRv);
-}
+  MOZ_ASSERT(NS_IsMainThread());
 
-/* static */ already_AddRefed<URL>
-URL::Constructor(nsISupports* aParent, const nsAString& aUrl,
-                 const nsAString& aBase, ErrorResult& aRv)
-{
   nsCOMPtr<nsIURI> baseUri;
   nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr,
                           nsContentUtils::GetIOService());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aBase);
     return nullptr;
   }
 
-  return Constructor(aParent, aUrl, baseUri, aRv);
+  return Constructor(aParent, aURL, baseUri, aRv);
 }
 
-/* static */
-already_AddRefed<URL>
-URL::Constructor(nsISupports* aParent, const nsAString& aUrl, nsIURI* aBase,
-                 ErrorResult& aRv)
+/* static */ already_AddRefed<URLMainThread>
+URLMainThread::Constructor(nsISupports* aParent, const nsAString& aURL,
+                           nsIURI* aBase, ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBase,
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBase,
                           nsContentUtils::GetIOService());
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aURL);
     return nullptr;
   }
 
-  RefPtr<URL> url = new URL(aParent, uri.forget());
+  RefPtr<URLMainThread> url = new URLMainThread(aParent, uri.forget());
   return url.forget();
 }
 
-void
-URL::CreateObjectURL(const GlobalObject& aGlobal,
-                     Blob& aBlob,
-                     const objectURLOptions& aOptions,
-                     nsAString& aResult,
-                     ErrorResult& aError)
+/* static */ void
+URLMainThread::CreateObjectURL(const GlobalObject& aGlobal,
+                               MediaSource& aSource,
+                               const objectURLOptions& aOptions,
+                               nsAString& aResult, ErrorResult& aRv)
 {
-  CreateObjectURLInternal(aGlobal, aBlob.Impl(),
-                          NS_LITERAL_CSTRING(BLOBURI_SCHEME), aOptions, aResult,
-                          aError);
-}
+  MOZ_ASSERT(NS_IsMainThread());
 
-void
-URL::CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
-                     const mozilla::dom::objectURLOptions& aOptions,
-                     nsAString& aResult,
-                     ErrorResult& aError)
-{
-  CreateObjectURLInternal(aGlobal, &aStream,
-                          NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME), aOptions,
-                          aResult, aError);
-}
+  nsCOMPtr<nsIPrincipal> principal =
+    nsContentUtils::ObjectPrincipal(aGlobal.Get());
 
-void
-URL::CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
-                     const objectURLOptions& aOptions,
-                     nsAString& aResult,
-                     ErrorResult& aError)
-{
-  nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
-
-  nsCString url;
-  nsresult rv = nsHostObjectProtocolHandler::
+  nsAutoCString url;
+  aRv = nsHostObjectProtocolHandler::
     AddDataEntry(NS_LITERAL_CSTRING(MEDIASOURCEURI_SCHEME),
                  &aSource, principal, url);
-  if (NS_FAILED(rv)) {
-    aError.Throw(rv);
+  if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> revocation = NS_NewRunnableFunction(
     [url] {
       nsHostObjectProtocolHandler::RemoveDataEntry(url);
     });
 
   nsContentUtils::RunInStableState(revocation.forget());
 
   CopyASCIItoUTF16(url, aResult);
 }
 
-void
-URL::CreateObjectURLInternal(const GlobalObject& aGlobal, nsISupports* aObject,
-                             const nsACString& aScheme,
-                             const objectURLOptions& aOptions,
-                             nsAString& aResult, ErrorResult& aRv)
+/* static */ void
+URLMainThread::CreateObjectURLInternal(const GlobalObject& aGlobal,
+                                       nsISupports* aObject,
+                                       const nsACString& aScheme,
+                                       const objectURLOptions& aOptions,
+                                       nsAString& aResult, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
+  nsCOMPtr<nsIPrincipal> principal =
+    nsContentUtils::ObjectPrincipal(aGlobal.Get());
 
   nsAutoCString url;
   nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(aScheme, aObject,
                                                           principal, url);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   global->RegisterHostObjectURI(url);
   CopyASCIItoUTF16(url, aResult);
 }
 
-void
-URL::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
-                     ErrorResult& aRv)
+/* static */ void
+URLMainThread::RevokeObjectURL(const GlobalObject& aGlobal,
+                               const nsAString& aURL, ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
 
@@ -198,29 +312,29 @@ URL::RevokeObjectURL(const GlobalObject&
   if (urlPrincipal && principal->Subsumes(urlPrincipal)) {
     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
     global->UnregisterHostObjectURI(asciiurl);
     nsHostObjectProtocolHandler::RemoveDataEntry(asciiurl);
   }
 }
 
 void
-URL::GetHref(nsAString& aHref) const
+URLMainThread::GetHref(nsAString& aHref, ErrorResult& aRv) const
 {
   aHref.Truncate();
 
   nsAutoCString href;
   nsresult rv = mURI->GetSpec(href);
   if (NS_SUCCEEDED(rv)) {
     CopyUTF8toUTF16(href, aHref);
   }
 }
 
 void
-URL::SetHref(const nsAString& aHref, ErrorResult& aRv)
+URLMainThread::SetHref(const nsAString& aHref, ErrorResult& aRv)
 {
   NS_ConvertUTF16toUTF8 href(aHref);
 
   nsresult rv;
   nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
@@ -233,35 +347,35 @@ URL::SetHref(const nsAString& aHref, Err
     return;
   }
 
   mURI = uri;
   UpdateURLSearchParams();
 }
 
 void
-URL::GetOrigin(nsAString& aOrigin) const
+URLMainThread::GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const
 {
   nsContentUtils::GetUTFOrigin(mURI, aOrigin);
 }
 
 void
-URL::GetProtocol(nsAString& aProtocol) const
+URLMainThread::GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const
 {
   nsAutoCString protocol;
   if (NS_SUCCEEDED(mURI->GetScheme(protocol))) {
     aProtocol.Truncate();
   }
 
   CopyASCIItoUTF16(protocol, aProtocol);
   aProtocol.Append(char16_t(':'));
 }
 
 void
-URL::SetProtocol(const nsAString& aProtocol)
+URLMainThread::SetProtocol(const nsAString& aProtocol, ErrorResult& aRv)
 {
   nsAString::const_iterator start, end;
   aProtocol.BeginReading(start);
   aProtocol.EndReading(end);
   nsAString::const_iterator iter(start);
 
   FindCharInReadable(':', iter, end);
 
@@ -298,112 +412,101 @@ URL::SetProtocol(const nsAString& aProto
   value.Truncate();               \
   nsAutoCString tmp;              \
   nsresult rv = mURI->func(tmp);  \
   if (NS_SUCCEEDED(rv)) {         \
     CopyUTF8toUTF16(tmp, value);  \
   }
 
 void
-URL::GetUsername(nsAString& aUsername) const
+URLMainThread::GetUsername(nsAString& aUsername, ErrorResult& aRv) const
 {
   URL_GETTER(aUsername, GetUsername);
 }
 
 void
-URL::SetUsername(const nsAString& aUsername)
+URLMainThread::SetUsername(const nsAString& aUsername, ErrorResult& aRv)
 {
   mURI->SetUsername(NS_ConvertUTF16toUTF8(aUsername));
 }
 
 void
-URL::GetPassword(nsAString& aPassword) const
+URLMainThread::GetPassword(nsAString& aPassword, ErrorResult& aRv) const
 {
   URL_GETTER(aPassword, GetPassword);
 }
 
 void
-URL::SetPassword(const nsAString& aPassword)
+URLMainThread::SetPassword(const nsAString& aPassword, ErrorResult& aRv)
 {
   mURI->SetPassword(NS_ConvertUTF16toUTF8(aPassword));
 }
 
 void
-URL::GetHost(nsAString& aHost) const
+URLMainThread::GetHost(nsAString& aHost, ErrorResult& aRv) const
 {
   URL_GETTER(aHost, GetHostPort);
 }
 
 void
-URL::SetHost(const nsAString& aHost)
+URLMainThread::SetHost(const nsAString& aHost, ErrorResult& aRv)
 {
   mURI->SetHostPort(NS_ConvertUTF16toUTF8(aHost));
 }
 
 void
-URL::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
-{
-  MOZ_ASSERT(mSearchParams);
-  MOZ_ASSERT(mSearchParams == aSearchParams);
-
-  nsAutoString search;
-  mSearchParams->Serialize(search);
-  SetSearchInternal(search);
-}
-
-void
-URL::UpdateURLSearchParams()
+URLMainThread::UpdateURLSearchParams()
 {
   if (!mSearchParams) {
     return;
   }
 
   nsAutoCString search;
   nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
   if (url) {
     nsresult rv = url->GetQuery(search);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Failed to get the query from a nsIURL.");
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      search.Truncate();
     }
   }
 
   mSearchParams->ParseInput(search);
 }
 
 void
-URL::GetHostname(nsAString& aHostname) const
+URLMainThread::GetHostname(nsAString& aHostname, ErrorResult& aRv) const
 {
   aHostname.Truncate();
   nsContentUtils::GetHostOrIPv6WithBrackets(mURI, aHostname);
 }
 
 void
-URL::SetHostname(const nsAString& aHostname)
+URLMainThread::SetHostname(const nsAString& aHostname, ErrorResult& aRv)
 {
   // nsStandardURL returns NS_ERROR_UNEXPECTED for an empty hostname
   // The return code is silently ignored
   mURI->SetHost(NS_ConvertUTF16toUTF8(aHostname));
 }
 
 void
-URL::GetPort(nsAString& aPort) const
+URLMainThread::GetPort(nsAString& aPort, ErrorResult& aRv) const
 {
   aPort.Truncate();
 
   int32_t port;
   nsresult rv = mURI->GetPort(&port);
   if (NS_SUCCEEDED(rv) && port != -1) {
     nsAutoString portStr;
     portStr.AppendInt(port, 10);
     aPort.Assign(portStr);
   }
 }
 
 void
-URL::SetPort(const nsAString& aPort)
+URLMainThread::SetPort(const nsAString& aPort, ErrorResult& aRv)
 {
   nsresult rv;
   nsAutoString portStr(aPort);
   int32_t port = -1;
 
   // nsIURI uses -1 as default value.
   if (!portStr.IsEmpty()) {
     port = portStr.ToInteger(&rv);
@@ -411,17 +514,17 @@ URL::SetPort(const nsAString& aPort)
       return;
     }
   }
 
   mURI->SetPort(port);
 }
 
 void
-URL::GetPathname(nsAString& aPathname) const
+URLMainThread::GetPathname(nsAString& aPathname, ErrorResult& aRv) const
 {
   aPathname.Truncate();
 
   nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
   if (!url) {
     nsAutoCString path;
     nsresult rv = mURI->GetPath(path);
     if (NS_FAILED(rv)){
@@ -437,29 +540,29 @@ URL::GetPathname(nsAString& aPathname) c
   nsAutoCString file;
   nsresult rv = url->GetFilePath(file);
   if (NS_SUCCEEDED(rv)) {
     CopyUTF8toUTF16(file, aPathname);
   }
 }
 
 void
-URL::SetPathname(const nsAString& aPathname)
+URLMainThread::SetPathname(const nsAString& aPathname, ErrorResult& aRv)
 {
   nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
   if (!url) {
     // Ignore failures to be compatible with NS4.
     return;
   }
 
   url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
 }
 
 void
-URL::GetSearch(nsAString& aSearch) const
+URLMainThread::GetSearch(nsAString& aSearch, ErrorResult& aRv) const
 {
   aSearch.Truncate();
 
   nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
   if (!url) {
     // Do not throw!  Not having a valid URI or URL should result in an empty
     // string.
     return;
@@ -468,63 +571,1160 @@ URL::GetSearch(nsAString& aSearch) const
   nsAutoCString search;
   nsresult rv = url->GetQuery(search);
   if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
     CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch);
   }
 }
 
 void
-URL::SetSearch(const nsAString& aSearch)
-{
-  SetSearchInternal(aSearch);
-  UpdateURLSearchParams();
-}
-
-void
-URL::SetSearchInternal(const nsAString& aSearch)
-{
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
-  if (!url) {
-    // Ignore failures to be compatible with NS4.
-    return;
-  }
-
-  url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
-}
-
-URLSearchParams*
-URL::SearchParams()
-{
-  CreateSearchParamsIfNeeded();
-  return mSearchParams;
-}
-
-void
-URL::GetHash(nsAString& aHash) const
+URLMainThread::GetHash(nsAString& aHash, ErrorResult& aRv) const
 {
   aHash.Truncate();
 
   nsAutoCString ref;
   nsresult rv = mURI->GetRef(ref);
   if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
     aHash.Assign(char16_t('#'));
     if (nsContentUtils::GettersDecodeURLHash()) {
       NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
     }
     AppendUTF8toUTF16(ref, aHash);
   }
 }
 
 void
-URL::SetHash(const nsAString& aHash)
+URLMainThread::SetHash(const nsAString& aHash, ErrorResult& aRv)
 {
   mURI->SetRef(NS_ConvertUTF16toUTF8(aHash));
 }
 
+void
+URLMainThread::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
+  if (!url) {
+    // Ignore failures to be compatible with NS4.
+    return;
+  }
+
+  url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+}
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// URL for Workers
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using namespace workers;
+
+// Proxy class to forward all the requests to a URLMainThread object.
+class URLProxy final
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
+
+  explicit URLProxy(already_AddRefed<URLMainThread> aURL)
+    : mURL(aURL)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  URLMainThread* URL()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mURL;
+  }
+
+  nsIURI* URI()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mURL->GetURI();
+  }
+
+  void ReleaseURI()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mURL = nullptr;
+  }
+
+private:
+  // Private destructor, to discourage deletion outside of Release():
+  ~URLProxy()
+  {
+     MOZ_ASSERT(!mURL);
+  }
+
+  RefPtr<URLMainThread> mURL;
+};
+
+// URLWorker implements the URL object in workers.
+class URLWorker final : public URL
+{
+public:
+  static already_AddRefed<URLWorker>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+              URL& aBase, ErrorResult& aRv);
+
+  static already_AddRefed<URLWorker>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+              const Optional<nsAString>& aBase, ErrorResult& aRv);
+
+  static already_AddRefed<URLWorker>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+              const nsAString& aBase, ErrorResult& aRv);
+
+  static void
+  CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+                  const mozilla::dom::objectURLOptions& aOptions,
+                  nsAString& aResult, mozilla::ErrorResult& aRv);
+
+  static void
+  RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aUrl,
+                  ErrorResult& aRv);
+
+  URLWorker(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy);
+
+  virtual void
+  GetHref(nsAString& aHref, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHref(const nsAString& aHref, ErrorResult& aRv) override;
+
+  virtual void
+  GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const override;
+
+  virtual void
+  GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const override;
+
+  virtual void
+  SetProtocol(const nsAString& aProtocol, ErrorResult& aRv) override;
+
+  virtual void
+  GetUsername(nsAString& aUsername, ErrorResult& aRv) const override;
+
+  virtual void
+  SetUsername(const nsAString& aUsername, ErrorResult& aRv) override;
+
+  virtual void
+  GetPassword(nsAString& aPassword, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPassword(const nsAString& aPassword, ErrorResult& aRv) override;
+
+  virtual void
+  GetHost(nsAString& aHost, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHost(const nsAString& aHost, ErrorResult& aRv) override;
+
+  virtual void
+  GetHostname(nsAString& aHostname, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHostname(const nsAString& aHostname, ErrorResult& aRv) override;
+
+  virtual void
+  GetPort(nsAString& aPort, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPort(const nsAString& aPort, ErrorResult& aRv) override;
+
+  virtual void
+  GetPathname(nsAString& aPathname, ErrorResult& aRv) const override;
+
+  virtual void
+  SetPathname(const nsAString& aPathname, ErrorResult& aRv) override;
+
+  virtual void
+  GetSearch(nsAString& aSearch, ErrorResult& aRv) const override;
+
+  virtual void
+  GetHash(nsAString& aHost, ErrorResult& aRv) const override;
+
+  virtual void
+  SetHash(const nsAString& aHash, ErrorResult& aRv) override;
+
+  virtual void UpdateURLSearchParams() override;
+
+  virtual void
+  SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) override;
+
+  URLProxy*
+  GetURLProxy() const
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    return mURLProxy;
+  }
+
+private:
+  ~URLWorker();
+
+  workers::WorkerPrivate* mWorkerPrivate;
+  RefPtr<URLProxy> mURLProxy;
+};
+
+// This class creates an URL from a DOM Blob on the main thread.
+class CreateURLRunnable : public WorkerMainThreadRunnable
+{
+private:
+  BlobImpl* mBlobImpl;
+  nsAString& mURL;
+
+public:
+  CreateURLRunnable(WorkerPrivate* aWorkerPrivate, BlobImpl* aBlobImpl,
+                    const objectURLOptions& aOptions,
+                    nsAString& aURL)
+  : WorkerMainThreadRunnable(aWorkerPrivate,
+                             NS_LITERAL_CSTRING("URL :: CreateURL"))
+  , mBlobImpl(aBlobImpl)
+  , mURL(aURL)
+  {
+    MOZ_ASSERT(aBlobImpl);
+
+    DebugOnly<bool> isMutable;
+    MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
+    MOZ_ASSERT(!isMutable);
+  }
+
+  bool
+  MainThreadRun()
+  {
+    using namespace mozilla::ipc;
+
+    AssertIsOnMainThread();
+
+    RefPtr<BlobImpl> newBlobImplHolder;
+
+    if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(mBlobImpl)) {
+      if (BlobChild* blobChild = remoteBlob->GetBlobChild()) {
+        if (PBackgroundChild* blobManager = blobChild->GetBackgroundManager()) {
+          PBackgroundChild* backgroundManager =
+            BackgroundChild::GetForCurrentThread();
+          MOZ_ASSERT(backgroundManager);
+
+          if (blobManager != backgroundManager) {
+            // Always make sure we have a blob from an actor we can use on this
+            // thread.
+            blobChild = BlobChild::GetOrCreate(backgroundManager, mBlobImpl);
+            MOZ_ASSERT(blobChild);
+
+            newBlobImplHolder = blobChild->GetBlobImpl();
+            MOZ_ASSERT(newBlobImplHolder);
+
+            mBlobImpl = newBlobImplHolder;
+          }
+        }
+      }
+    }
+
+    DebugOnly<bool> isMutable;
+    MOZ_ASSERT(NS_SUCCEEDED(mBlobImpl->GetMutable(&isMutable)));
+    MOZ_ASSERT(!isMutable);
+
+    nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
+
+    nsAutoCString url;
+    nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(
+        NS_LITERAL_CSTRING(BLOBURI_SCHEME),
+        mBlobImpl, principal, url);
+
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to add data entry for the blob!");
+      SetDOMStringToNull(mURL);
+      return false;
+    }
+
+    if (!mWorkerPrivate->IsSharedWorker() &&
+        !mWorkerPrivate->IsServiceWorker()) {
+      // Walk up to top worker object.
+      WorkerPrivate* wp = mWorkerPrivate;
+      while (WorkerPrivate* parent = wp->GetParent()) {
+        wp = parent;
+      }
+
+      nsCOMPtr<nsIScriptContext> sc = wp->GetScriptContext();
+      // We could not have a ScriptContext in JSM code. In this case, we leak.
+      if (sc) {
+        nsCOMPtr<nsIGlobalObject> global = sc->GetGlobalObject();
+        MOZ_ASSERT(global);
+
+        global->RegisterHostObjectURI(url);
+      }
+    }
+
+    mURL = NS_ConvertUTF8toUTF16(url);
+    return true;
+  }
+};
+
+// This class revokes an URL on the main thread.
+class RevokeURLRunnable : public WorkerMainThreadRunnable
+{
+private:
+  const nsString mURL;
+
+public:
+  RevokeURLRunnable(WorkerPrivate* aWorkerPrivate,
+                    const nsAString& aURL)
+  : WorkerMainThreadRunnable(aWorkerPrivate,
+                             NS_LITERAL_CSTRING("URL :: RevokeURL"))
+  , mURL(aURL)
+  {}
+
+  bool
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    NS_ConvertUTF16toUTF8 url(mURL);
+
+    nsIPrincipal* urlPrincipal =
+      nsHostObjectProtocolHandler::GetDataEntryPrincipal(url);
+
+    nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
+
+    bool subsumes;
+    if (urlPrincipal &&
+        NS_SUCCEEDED(principal->Subsumes(urlPrincipal, &subsumes)) &&
+        subsumes) {
+      nsHostObjectProtocolHandler::RemoveDataEntry(url);
+    }
+
+    if (!mWorkerPrivate->IsSharedWorker() &&
+        !mWorkerPrivate->IsServiceWorker()) {
+      // Walk up to top worker object.
+      WorkerPrivate* wp = mWorkerPrivate;
+      while (WorkerPrivate* parent = wp->GetParent()) {
+        wp = parent;
+      }
+
+      nsCOMPtr<nsIScriptContext> sc = wp->GetScriptContext();
+      // We could not have a ScriptContext in JSM code. In this case, we leak.
+      if (sc) {
+        nsCOMPtr<nsIGlobalObject> global = sc->GetGlobalObject();
+        MOZ_ASSERT(global);
+
+        global->UnregisterHostObjectURI(url);
+      }
+    }
+
+    return true;
+  }
+};
+
+// This class creates a URL object on the main thread.
+class ConstructorRunnable : public WorkerMainThreadRunnable
+{
+private:
+  const nsString mURL;
+
+  nsString mBase; // IsVoid() if we have no base URI string.
+  RefPtr<URLProxy> mBaseProxy;
+  ErrorResult& mRv;
+
+  RefPtr<URLProxy> mRetval;
+
+public:
+  ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+                      const nsAString& aURL, const Optional<nsAString>& aBase,
+                      ErrorResult& aRv)
+  : WorkerMainThreadRunnable(aWorkerPrivate,
+                             NS_LITERAL_CSTRING("URL :: Constructor"))
+  , mURL(aURL)
+  , mRv(aRv)
+  {
+    if (aBase.WasPassed()) {
+      mBase = aBase.Value();
+    } else {
+      mBase.SetIsVoid(true);
+    }
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+                      const nsAString& aURL, URLProxy* aBaseProxy,
+                      ErrorResult& aRv)
+  : WorkerMainThreadRunnable(aWorkerPrivate,
+                             NS_LITERAL_CSTRING("URL :: Constructor with BaseURL"))
+  , mURL(aURL)
+  , mBaseProxy(aBaseProxy)
+  , mRv(aRv)
+  {
+    mBase.SetIsVoid(true);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  bool
+  MainThreadRun()
+  {
+    AssertIsOnMainThread();
+
+    RefPtr<URLMainThread> url;
+    if (mBaseProxy) {
+      url = URLMainThread::Constructor(nullptr, mURL, mBaseProxy->URI(), mRv);
+    } else if (!mBase.IsVoid()) {
+      url = URLMainThread::Constructor(nullptr, mURL, mBase, mRv);
+    } else {
+      url = URLMainThread::Constructor(nullptr, mURL, nullptr, mRv);
+    }
+