merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 16 Oct 2013 13:03:50 +0200
changeset 165696 c14ca6b27b3007a972bddc75fa42731b071a0466
parent 165651 f9f7051206836fac6813becdc9960de3e0f53e90 (current diff)
parent 165695 9988599193391944b60d3d2a600b2e3fc71d1b0f (diff)
child 165702 33991c16bdb367a1c24b3acb9963379ff9a3adc3
child 165745 762cd8723f8dc716f042f0f0af74bbac89340d51
child 165769 685997ddd0a04c822cbdd03823bfccd834fe2e49
child 171413 56848e96cdb1481c85c7672eaea31623ff8c0451
child 178470 218c10f70144c889ec458431ba5912abc210de69
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.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 fx-team to mozilla-central
mobile/android/base/tests/testBookmarksPage.java.in
services/common/log4moz.js
services/common/tests/unit/test_log4moz.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1077,19 +1077,20 @@ pref("services.sync.prefs.sync.xpinstall
 pref("devtools.errorconsole.enabled", false);
 
 // Developer toolbar and GCLI preferences
 pref("devtools.toolbar.enabled", true);
 pref("devtools.toolbar.visible", false);
 pref("devtools.gcli.allowSet", false);
 pref("devtools.commands.dir", "");
 
-// Disable the app manager
+// Enable the app manager
 pref("devtools.appmanager.enabled", true);
 pref("devtools.appmanager.firstrun", true);
+pref("devtools.appmanager.manifestEditor.enabled", false);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
 pref("devtools.toolbox.sideEnabled", true);
--- a/browser/base/content/browser-data-submission-info-bar.js
+++ b/browser/base/content/browser-data-submission-info-bar.js
@@ -14,19 +14,19 @@ let gDataNotificationInfoBar = {
   _DATA_REPORTING_NOTIFICATION: "data-reporting",
 
   get _notificationBox() {
     delete this._notificationBox;
     return this._notificationBox = document.getElementById("global-notificationbox");
   },
 
   get _log() {
-    let log4moz = Cu.import("resource://services-common/log4moz.js", {}).Log4Moz;
+    let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
     delete this._log;
-    return this._log = log4moz.repository.getLogger("Services.DataReporting.InfoBar");
+    return this._log = Log.repository.getLogger("Services.DataReporting.InfoBar");
   },
 
   init: function() {
     window.addEventListener("unload", function onUnload() {
       window.removeEventListener("unload", onUnload, false);
 
       for (let o of this._OBSERVERS) {
         Services.obs.removeObserver(this, o);
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -468,16 +468,19 @@ var PlacesCommandHook = {
       organizer.focus();
     }
   }
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// HistoryMenu
 
+XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
+  "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
+
 // View for the history menu.
 function HistoryMenu(aPopupShowingEvent) {
   // Workaround for Bug 610187.  The sidebar does not include all the Places
   // views definitions, and we don't need them there.
   // Defining the prototype inheritance in the prototype itself would cause
   // browser.js to halt on "PlacesMenu is not defined" error.
   this.__proto__.__proto__ = PlacesMenu.prototype;
   PlacesMenu.call(this, aPopupShowingEvent,
@@ -526,55 +529,18 @@ HistoryMenu.prototype = {
       undoMenu.setAttribute("disabled", true);
       return;
     }
 
     // enable menu
     undoMenu.removeAttribute("disabled");
 
     // populate menu
-    var undoItems = JSON.parse(SessionStore.getClosedTabData(window));
-    for (var i = 0; i < undoItems.length; i++) {
-      var m = document.createElement("menuitem");
-      m.setAttribute("label", undoItems[i].title);
-      if (undoItems[i].image) {
-        let iconURL = undoItems[i].image;
-        // don't initiate a connection just to fetch a favicon (see bug 467828)
-        if (/^https?:/.test(iconURL))
-          iconURL = "moz-anno:favicon:" + iconURL;
-        m.setAttribute("image", iconURL);
-      }
-      m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
-      m.setAttribute("value", i);
-      m.setAttribute("oncommand", "undoCloseTab(" + i + ");");
-
-      // Set the targetURI attribute so it will be shown in tooltip and trigger
-      // onLinkHovered. SessionStore uses one-based indexes, so we need to
-      // normalize them.
-      let tabData = undoItems[i].state;
-      let activeIndex = (tabData.index || tabData.entries.length) - 1;
-      if (activeIndex >= 0 && tabData.entries[activeIndex])
-        m.setAttribute("targetURI", tabData.entries[activeIndex].url);
-
-      m.addEventListener("click", this._undoCloseMiddleClick, false);
-      if (i == 0)
-        m.setAttribute("key", "key_undoCloseTab");
-      undoPopup.appendChild(m);
-    }
-
-    // "Restore All Tabs"
-    var strings = gNavigatorBundle;
-    undoPopup.appendChild(document.createElement("menuseparator"));
-    m = undoPopup.appendChild(document.createElement("menuitem"));
-    m.id = "menu_restoreAllTabs";
-    m.setAttribute("label", strings.getString("menuRestoreAllTabs.label"));
-    m.addEventListener("command", function() {
-      for (var i = 0; i < undoItems.length; i++)
-        undoCloseTab(0);
-    }, false);
+    let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(window, "menuitem");
+    undoPopup.appendChild(tabsFragment);
   },
 
   toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
     // enable/disable the Recently Closed Windows sub menu
     var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
 
     // no restorable windows, so disable menu
     if (SessionStore.getClosedWindowCount() == 0)
@@ -602,55 +568,18 @@ HistoryMenu.prototype = {
       undoMenu.setAttribute("disabled", true);
       return;
     }
 
     // enable menu
     undoMenu.removeAttribute("disabled");
 
     // populate menu
-    let undoItems = JSON.parse(SessionStore.getClosedWindowData());
-    for (let i = 0; i < undoItems.length; i++) {
-      let undoItem = undoItems[i];
-      let otherTabsCount = undoItem.tabs.length - 1;
-      let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
-                                        : PluralForm.get(otherTabsCount, menuLabelString);
-      let menuLabel = label.replace("#1", undoItem.title)
-                           .replace("#2", otherTabsCount);
-      let m = document.createElement("menuitem");
-      m.setAttribute("label", menuLabel);
-      let selectedTab = undoItem.tabs[undoItem.selected - 1];
-      if (selectedTab.image) {
-        let iconURL = selectedTab.image;
-        // don't initiate a connection just to fetch a favicon (see bug 467828)
-        if (/^https?:/.test(iconURL))
-          iconURL = "moz-anno:favicon:" + iconURL;
-        m.setAttribute("image", iconURL);
-      }
-      m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
-      m.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
-
-      // Set the targetURI attribute so it will be shown in tooltip.
-      // SessionStore uses one-based indexes, so we need to normalize them.
-      let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
-      if (activeIndex >= 0 && selectedTab.entries[activeIndex])
-        m.setAttribute("targetURI", selectedTab.entries[activeIndex].url);
-
-      if (i == 0)
-        m.setAttribute("key", "key_undoCloseWindow");
-      undoPopup.appendChild(m);
-    }
-
-    // "Open All in Windows"
-    undoPopup.appendChild(document.createElement("menuseparator"));
-    let m = undoPopup.appendChild(document.createElement("menuitem"));
-    m.id = "menu_restoreAllWindows";
-    m.setAttribute("label", gNavigatorBundle.getString("menuRestoreAllWindows.label"));
-    m.setAttribute("oncommand",
-      "for (var i = 0; i < " + undoItems.length + "; i++) undoCloseWindow();");
+    let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(window, "menuitem");
+    undoPopup.appendChild(windowsFragment);
   },
 
   toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
     // This is a no-op if MOZ_SERVICES_SYNC isn't defined
 #ifdef MOZ_SERVICES_SYNC
     // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
     // by HistoryMenu do not have this menuitem.
     let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7122,17 +7122,24 @@ function safeModeRestart()
                      Services.prompt.BUTTON_TITLE_IS_STRING) +
                     (Services.prompt.BUTTON_POS_1 *
                      Services.prompt.BUTTON_TITLE_CANCEL) +
                     Services.prompt.BUTTON_POS_0_DEFAULT;
 
   let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
                                      buttonFlags, restartText, null, null,
                                      null, {});
-  if (rv == 0) {
+  if (rv != 0)
+    return;
+
+  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+                     .createInstance(Ci.nsISupportsPRBool);
+  Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+  if (!cancelQuit.data) {
     Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
   }
 }
 
 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
  *
  * |where| can be:
  *  "tab"         new tab
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1666,19 +1666,36 @@
                 throw new Error("Required argument missing: aTab");
 
               tabsToClose = this.getTabsToTheEndFrom(aTab).length;
               break;
             default:
               throw new Error("Invalid argument: " + aCloseTabs);
           }
 
-          if (tabsToClose <= 1 ||
-              aCloseTabs != this.closingTabsEnum.ALL ||
-              !Services.prefs.getBoolPref("browser.tabs.warnOnClose"))
+          let maxUndo =
+            Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
+          let warnOnCloseOtherTabs =
+            Services.prefs.getBoolPref("browser.tabs.warnOnCloseOtherTabs");
+          let warnOnCloseWindow =
+            Services.prefs.getBoolPref("browser.tabs.warnOnClose");
+          let isWindowClosing = aCloseTabs == this.closingTabsEnum.ALL;
+
+          let skipWarning =
+            // 1) If there is only one tab to close, we'll never warn the user.
+            tabsToClose <= 1 ||
+            // 2) If the whole window is going to be closed, don't warn the
+            //    user if the user has browser.tabs.warnOnClose set to false.
+            (isWindowClosing && !warnOnCloseWindow) ||
+            // 3) If the number of tabs are less than the undo threshold
+            //    or if the user has specifically opted-in to ignoring
+            //    this warning via the warnOnCloseOtherTabs pref.
+            (!isWindowClosing && (!warnOnCloseOtherTabs ||
+                                            tabsToClose <= maxUndo));
+          if (skipWarning)
             return true;
 
           var ps = Services.prompt;
 
           // default to true: if it were false, we wouldn't get this far
           var warnOnClose = { value: true };
           var bundle = this.mStringBundle;
 
@@ -1697,18 +1714,21 @@
                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
                          bundle.getString("tabs.closeButtonMultiple"),
                          null, null,
                          bundle.getString("tabs.closeWarningPromptMe"),
                          warnOnClose);
           var reallyClose = (buttonPressed == 0);
 
           // don't set the pref unless they press OK and it's false
-          if (reallyClose && !warnOnClose.value)
-            Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+          if (reallyClose && !warnOnClose.value) {
+            let pref = isWindowClosing ? "browser.tabs.warnOnClose" :
+                                         "browser.tabs.warnOnCloseOtherTabs";
+            Services.prefs.setBoolPref(pref, false);
+          }
 
           return reallyClose;
         ]]>
       </body>
       </method>
 
       <method name="getTabsToTheEndFrom">
         <parameter name="aTab"/>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -194,16 +194,17 @@ support-files =
 [browser_bug818118.js]
 [browser_bug820497.js]
 [browser_bug822367.js]
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug887515.js]
+[browser_bug896291_closeMaxSessionStoreTabs.js]
 [browser_bug902156.js]
 [browser_canonizeURL.js]
 [browser_clearplugindata.html]
 [browser_clearplugindata.js]
 [browser_clearplugindata_noage.html]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 [browser_ctrlTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug896291_closeMaxSessionStoreTabs.js
@@ -0,0 +1,108 @@
+function numClosedTabs()
+  Cc["@mozilla.org/browser/sessionstore;1"].
+    getService(Ci.nsISessionStore).
+    getNumberOfTabsClosedLast(window);
+
+let originalTab;
+let maxTabsUndo;
+let maxTabsUndoPlusOne;
+let acceptRemoveAllTabsDialogListener;
+let cancelRemoveAllTabsDialogListener;
+
+function test() {
+  waitForExplicitFinish();
+  Services.prefs.setBoolPref("browser.tabs.animate", false);
+
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("browser.tabs.animate");
+
+    originalTab.linkedBrowser.loadURI("about:blank");
+    originalTab = null;
+  });
+
+  // Creating and throwing away this tab guarantees that the
+  // number of tabs closed in the previous tab-close operation is 1.
+  let throwaway_tab = gBrowser.addTab("http://mochi.test:8888/");
+  gBrowser.removeTab(throwaway_tab);
+
+  let undoCloseTabElement = document.getElementById("context_undoCloseTab");
+  updateTabContextMenu();
+  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
+     "The label should be showing that the command will restore a single tab");
+
+  originalTab = gBrowser.selectedTab;
+  gBrowser.selectedBrowser.loadURI("http://mochi.test:8888/");
+
+  maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
+  maxTabsUndoPlusOne = maxTabsUndo + 1;
+  let numberOfTabsLoaded = 0;
+  for (let i = 0; i < maxTabsUndoPlusOne; i++) {
+    let tab = gBrowser.addTab("http://mochi.test:8888/");
+    let browser = gBrowser.getBrowserForTab(tab);
+    browser.addEventListener("load", function onLoad() {
+      browser.removeEventListener("load", onLoad, true);
+
+      if (++numberOfTabsLoaded == maxTabsUndoPlusOne)
+        verifyUndoMultipleClose();
+    }, true);
+  }
+}
+
+function verifyUndoMultipleClose() {
+  info("all tabs opened and loaded");
+  cancelRemoveAllTabsDialogListener = new WindowListener("chrome://global/content/commonDialog.xul", cancelRemoveAllTabsDialog);
+  Services.wm.addListener(cancelRemoveAllTabsDialogListener);
+  gBrowser.removeAllTabsBut(originalTab);
+}
+
+function cancelRemoveAllTabsDialog(domWindow) {
+  ok(true, "dialog appeared in response to multiple tab close action");
+  domWindow.document.documentElement.cancelDialog();
+  Services.wm.removeListener(cancelRemoveAllTabsDialogListener);
+
+  acceptRemoveAllTabsDialogListener = new WindowListener("chrome://global/content/commonDialog.xul", acceptRemoveAllTabsDialog);
+  Services.wm.addListener(acceptRemoveAllTabsDialogListener);
+  waitForCondition(function () gBrowser.tabs.length == 1 + maxTabsUndoPlusOne, function verifyCancel() {
+    is(gBrowser.tabs.length, 1 + maxTabsUndoPlusOne, /* The '1 +' is for the original tab */
+       "All tabs should still be open after the 'Cancel' option on the prompt is chosen");
+    gBrowser.removeAllTabsBut(originalTab);
+  }, "Waited too long to find that no tabs were closed.");
+}
+
+function acceptRemoveAllTabsDialog(domWindow) {
+  ok(true, "dialog appeared in response to multiple tab close action");
+  domWindow.document.documentElement.acceptDialog();
+  Services.wm.removeListener(acceptRemoveAllTabsDialogListener);
+
+  waitForCondition(function () gBrowser.tabs.length == 1, function verifyAccept() {
+    is(gBrowser.tabs.length, 1,
+       "All other tabs should be closed after the 'OK' option on the prompt is chosen");
+    finish();
+  }, "Waited too long for the other tabs to be closed.");
+}
+
+function WindowListener(aURL, aCallback) {
+  this.callback = aCallback;
+  this.url = aURL;
+}
+WindowListener.prototype = {
+  onOpenWindow: function(aXULWindow) {
+    var domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindow);
+    var self = this;
+    domWindow.addEventListener("load", function() {
+      domWindow.removeEventListener("load", arguments.callee, false);
+
+      info("domWindow.document.location.href: " + domWindow.document.location.href);
+      if (domWindow.document.location.href != self.url)
+        return;
+
+      // Allow other window load listeners to execute before passing to callback
+      executeSoon(function() {
+        self.callback(domWindow);
+      });
+    }, false);
+  },
+  onCloseWindow: function(aXULWindow) {},
+  onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
--- a/browser/base/content/test/general/browser_datareporting_notification.js
+++ b/browser/base/content/test/general/browser_datareporting_notification.js
@@ -53,20 +53,20 @@ function waitForNotificationClose(notifi
 }
 
 let dumpAppender, rootLogger;
 
 function test() {
   waitForExplicitFinish();
 
   let ns = {};
-  Components.utils.import("resource://services-common/log4moz.js", ns);
-  rootLogger = ns.Log4Moz.repository.rootLogger;
-  dumpAppender = new ns.Log4Moz.DumpAppender();
-  dumpAppender.level = ns.Log4Moz.Level.All;
+  Components.utils.import("resource://gre/modules/Log.jsm", ns);
+  rootLogger = ns.Log.repository.rootLogger;
+  dumpAppender = new ns.Log.DumpAppender();
+  dumpAppender.level = ns.Log.Level.All;
   rootLogger.addAppender(dumpAppender);
 
   let notification = document.getElementById("global-notificationbox");
   let policy;
 
   notification.addEventListener("AlertActive", function active() {
     notification.removeEventListener("AlertActive", active, true);
 
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/RecentlyClosedTabsAndWindowsMenuUtils.jsm
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["RecentlyClosedTabsAndWindowsMenuUtils"];
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+                                  "resource://gre/modules/PluralForm.jsm");
+
+let navigatorBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+this.RecentlyClosedTabsAndWindowsMenuUtils = {
+
+  /**
+  * Builds up a document fragment of UI items for the recently closed tabs.
+  * @param   aWindow
+  *          The window that the tabs were closed in.
+  * @param   aTagName
+  *          The tag name that will be used when creating the UI items.
+  * @returns A document fragment with UI items for each recently closed tab.
+  */
+  getTabsFragment: function(aWindow, aTagName) {
+    let doc = aWindow.document;
+    let fragment = doc.createDocumentFragment();
+    if (ss.getClosedTabCount(aWindow) != 0) {
+      let closedTabs = JSON.parse(ss.getClosedTabData(aWindow));
+      for (let i = 0; i < closedTabs.length; i++) {
+        let element = doc.createElementNS(kNSXUL, aTagName);
+        element.setAttribute("label", closedTabs[i].title);
+        if (closedTabs[i].image) {
+          setImage(closedTabs[i], element);
+        }
+        element.setAttribute("value", i);
+        if (aTagName == "menuitem") {
+          element.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+        }
+        element.setAttribute("oncommand", "undoCloseTab(" + i + ");");
+
+        // Set the targetURI attribute so it will be shown in tooltip and trigger
+        // onLinkHovered. SessionStore uses one-based indexes, so we need to
+        // normalize them.
+        let tabData = closedTabs[i].state;
+        let activeIndex = (tabData.index || tabData.entries.length) - 1;
+        if (activeIndex >= 0 && tabData.entries[activeIndex]) {
+          element.setAttribute("targetURI", tabData.entries[activeIndex].url);
+        }
+
+        element.addEventListener("click", this._undoCloseMiddleClick, false);
+        if (i == 0)
+          element.setAttribute("key", "key_undoCloseTab");
+        fragment.appendChild(element);
+      }
+
+      fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
+      let restoreAllTabs = fragment.appendChild(doc.createElementNS(kNSXUL, aTagName));
+      restoreAllTabs.setAttribute("label", navigatorBundle.GetStringFromName("menuRestoreAllTabs.label"));
+      restoreAllTabs.addEventListener("command", function() {
+        for (var i = 0; i < closedTabs.length; i++)
+          undoCloseTab(0);
+      }, false);
+    }
+    return fragment;
+  },
+
+  /**
+  * Builds up a document fragment of UI items for the recently closed windows.
+  * @param   aWindow
+  *          A window that can be used to create the elements and document fragment.
+  * @param   aTagName
+  *          The tag name that will be used when creating the UI items.
+  * @returns A document fragment with UI items for each recently closed window.
+  */
+  getWindowsFragment: function(aWindow, aTagName) {
+    let closedWindowData = JSON.parse(ss.getClosedWindowData());
+    let fragment = aWindow.document.createDocumentFragment();
+    if (closedWindowData.length != 0) {
+      let menuLabelString = navigatorBundle.GetStringFromName("menuUndoCloseWindowLabel");
+      let menuLabelStringSingleTab =
+        navigatorBundle.GetStringFromName("menuUndoCloseWindowSingleTabLabel");
+
+      let doc = aWindow.document;
+      for (let i = 0; i < closedWindowData.length; i++) {
+        let undoItem = closedWindowData[i];
+        let otherTabsCount = undoItem.tabs.length - 1;
+        let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
+                                          : PluralForm.get(otherTabsCount, menuLabelString);
+        let menuLabel = label.replace("#1", undoItem.title)
+                             .replace("#2", otherTabsCount);
+        let item = doc.createElementNS(kNSXUL, aTagName);
+        item.setAttribute("label", menuLabel);
+        let selectedTab = undoItem.tabs[undoItem.selected - 1];
+        if (selectedTab.image) {
+          setImage(selectedTab, item);
+        }
+        if (aTagName == "menuitem") {
+          item.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+        }
+        item.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
+
+        // Set the targetURI attribute so it will be shown in tooltip.
+        // SessionStore uses one-based indexes, so we need to normalize them.
+        let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
+        if (activeIndex >= 0 && selectedTab.entries[activeIndex])
+          item.setAttribute("targetURI", selectedTab.entries[activeIndex].url);
+
+        if (i == 0)
+          item.setAttribute("key", "key_undoCloseWindow");
+        fragment.appendChild(item);
+      }
+
+      // "Open All in Windows"
+      fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
+      let restoreAllWindows = fragment.appendChild(doc.createElementNS(kNSXUL, aTagName));
+      restoreAllWindows.setAttribute("label", navigatorBundle.GetStringFromName("menuRestoreAllWindows.label"));
+      restoreAllWindows.setAttribute("oncommand",
+        "for (var i = 0; i < " + closedWindowData.length + "; i++) undoCloseWindow();");
+    }
+    return fragment;
+  },
+};
+
+function setImage(aItem, aElement) {
+  let iconURL = aItem.image;
+  // don't initiate a connection just to fetch a favicon (see bug 467828)
+  if (/^https?:/.test(iconURL))
+    iconURL = "moz-anno:favicon:" + iconURL;
+  aElement.setAttribute("image", iconURL);
+}
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -197,32 +197,30 @@ let SessionSaverInternal = {
       if (state.windows[i].isPrivate) {
         state.windows.splice(i, 1);
         if (state.selectedWindow >= i) {
           state.selectedWindow--;
         }
       }
     }
 
-#ifndef XP_MACOSX
-    // Don't save invalid states.
-    // Looks like we currently have private windows, only.
-    if (state.windows.length == 0) {
-      stopWatchCancel("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
-      return;
-    }
-#endif
-
     // Remove private windows from the list of closed windows.
     for (let i = state._closedWindows.length - 1; i >= 0; i--) {
       if (state._closedWindows[i].isPrivate) {
         state._closedWindows.splice(i, 1);
       }
     }
 
+    // Make sure that we keep the previous session if we started with a single
+    // private window and no non-private windows have been opened, yet.
+    if (state.deferredInitialState) {
+      state.windows = state.deferredInitialState.windows || [];
+      delete state.deferredInitialState;
+    }
+
 #ifndef XP_MACOSX
     // We want to restore closed windows that are marked with _shouldRestore.
     // We're doing this here because we want to control this only when saving
     // the file.
     while (state._closedWindows.length) {
       let i = state._closedWindows.length - 1;
 
       if (!state._closedWindows[i]._shouldRestore) {
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -347,17 +347,17 @@ let SessionStoreInternal = {
    * A promise fulfilled once initialization is complete.
    */
   get promiseInitialized() {
     return this._deferredInitialized.promise;
   },
 
   /* ........ Public Getters .............. */
   get canRestoreLastSession() {
-    return this._lastSessionState;
+    return !!this._lastSessionState;
   },
 
   set canRestoreLastSession(val) {
     // Cheat a bit; only allow false.
     if (val)
       return;
     this._lastSessionState = null;
   },
@@ -723,17 +723,17 @@ let SessionStoreInternal = {
       SessionSaver.updateLastSaveTime();
 
       // restore a crashed session resp. resume the last session if requested
       if (aInitialState) {
         if (isPrivateWindow) {
           // We're starting with a single private window. Save the state we
           // actually wanted to restore so that we can do it later in case
           // the user opens another, non-private window.
-          this._deferredInitialState = aInitialState;
+          this._deferredInitialState = gSessionStartup.state;
 
           // Nothing to restore now, notify observers things are complete.
           Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
         } else {
           TelemetryTimestamps.add("sessionRestoreRestoring");
           this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
 
           let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
@@ -1341,16 +1341,19 @@ let SessionStoreInternal = {
   /* ........ nsISessionStore API .............. */
 
   getBrowserState: function ssi_getBrowserState() {
     let state = this.getCurrentState();
 
     // Don't include the last session state in getBrowserState().
     delete state.lastSessionState;
 
+    // Don't include any deferred initial state.
+    delete state.deferredInitialState;
+
     return this._toJSONString(state);
   },
 
   setBrowserState: function ssi_setBrowserState(aState) {
     this._handleClosedWindows();
 
     try {
       var state = JSON.parse(aState);
@@ -2069,16 +2072,23 @@ let SessionStoreInternal = {
       scratchpads: scratchpads
     };
 
     // Persist the last session if we deferred restoring it
     if (this._lastSessionState) {
       state.lastSessionState = this._lastSessionState;
     }
 
+    // If we were called by the SessionSaver and started with only a private
+    // window we want to pass the deferred initial state to not lose the
+    // previous session.
+    if (this._deferredInitialState) {
+      state.deferredInitialState = this._deferredInitialState;
+    }
+
     return state;
   },
 
   /**
    * serialize session data for a window
    * @param aWindow
    *        Window reference
    * @returns string
@@ -3519,16 +3529,24 @@ let SessionStoreInternal = {
    * this._lastSessionState and will be kept in case the user explicitly wants
    * to restore the previous session (publicly exposed as restoreLastSession).
    *
    * @param state
    *        The state, presumably from nsISessionStartup.state
    * @returns [defaultState, state]
    */
   _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
+    // Make sure that we don't modify the global state as provided by
+    // nsSessionStartup.state. Converting the object to a JSON string and
+    // parsing it again is the easiest way to do that, although not the most
+    // efficient one. Deferred sessions that don't have automatic session
+    // restore enabled tend to be a lot smaller though so that this shouldn't
+    // be a big perf hit.
+    state = JSON.parse(JSON.stringify(state));
+
     let defaultState = { windows: [], selectedWindow: 1 };
 
     state.selectedWindow = state.selectedWindow || 1;
 
     // Look at each window, remove pinned tabs, adjust selectedindex,
     // remove window if necessary.
     for (let wIndex = 0; wIndex < state.windows.length;) {
       let window = state.windows[wIndex];
@@ -3710,19 +3728,19 @@ let SessionStoreInternal = {
     aWindow.dispatchEvent(event);
   },
 
   /**
    * Dispatch the SSTabRestored event for the given tab.
    * @param aTab the which has been restored
    */
   _sendTabRestoredNotification: function ssi_sendTabRestoredNotification(aTab) {
-      let event = aTab.ownerDocument.createEvent("Events");
-      event.initEvent("SSTabRestored", true, false);
-      aTab.dispatchEvent(event);
+    let event = aTab.ownerDocument.createEvent("Events");
+    event.initEvent("SSTabRestored", true, false);
+    aTab.dispatchEvent(event);
   },
 
   /**
    * @param aWindow
    *        Window reference
    * @returns whether this window's data is still cached in _statesToRestore
    *          because it's not fully loaded yet
    */
@@ -4243,17 +4261,17 @@ let TabState = {
       tabData.entries = history.entries;
       tabData.index = history.index;
 
       if (Object.keys(storage).length) {
         tabData.storage = storage;
       }
 
       if (disallow.length > 0) {
-	tabData.disallow = disallow.join(",");
+        tabData.disallow = disallow.join(",");
       }
 
       // Save text and scroll data.
       this._updateTextAndScrollDataForTab(tab, tabData);
 
       // If we're still the latest async collection for the given tab and
       // the cache hasn't been filled by collect() in the meantime, let's
       // fill the cache with the data we received.
@@ -4390,19 +4408,19 @@ let TabState = {
       tabData.pinned = true;
     else
       delete tabData.pinned;
     tabData.hidden = tab.hidden;
 
     if (!options || !options.omitDocShellCapabilities) {
       let disallow = DocShellCapabilities.collect(browser.docShell);
       if (disallow.length > 0)
-	tabData.disallow = disallow.join(",");
+        tabData.disallow = disallow.join(",");
       else if (tabData.disallow)
-	delete tabData.disallow;
+        delete tabData.disallow;
     }
 
     // Save tab attributes.
     tabData.attributes = TabAttributes.get(tab);
 
     // Store the tab icon.
     let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
     tabData.image = tabbrowser.getIcon(tab);
@@ -4599,10 +4617,8 @@ let TabState = {
     for (let i = 0; i < content.frames.length; i++) {
       let selectedPageStyle = this._getSelectedPageStyle(content.frames[i]);
       if (selectedPageStyle)
         return selectedPageStyle;
     }
     return "";
   }
 };
-
-
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -12,16 +12,17 @@ EXTRA_COMPONENTS += [
 
 JS_MODULES_PATH = 'modules/sessionstore'
 
 EXTRA_JS_MODULES = [
     'DocShellCapabilities.jsm',
     'DocumentUtils.jsm',
     'Messenger.jsm',
     'PrivacyLevel.jsm',
+    'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'SessionCookies.jsm',
     'SessionHistory.jsm',
     'SessionMigration.jsm',
     'SessionStorage.jsm',
     'SessionWorker.js',
     'TabStateCache.jsm',
     'XPathGenerator.jsm',
     '_SessionFile.jsm',
--- a/browser/devtools/app-manager/app-validator.js
+++ b/browser/devtools/app-manager/app-validator.js
@@ -19,47 +19,54 @@ function AppValidator(project) {
 AppValidator.prototype.error = function (message) {
   this.errors.push(message);
 }
 
 AppValidator.prototype.warning = function (message) {
   this.warnings.push(message);
 }
 
-AppValidator.prototype._getPackagedManifestURL = function () {
+AppValidator.prototype._getPackagedManifestFile = function () {
   let manifestFile = FileUtils.File(this.project.location);
   if (!manifestFile.exists()) {
     this.error(strings.GetStringFromName("validator.nonExistingFolder"));
-    return;
+    return null;
   }
   if (!manifestFile.isDirectory()) {
     this.error(strings.GetStringFromName("validator.expectProjectFolder"));
-    return;
+    return null;
   }
   manifestFile.append("manifest.webapp");
   if (!manifestFile.exists() || !manifestFile.isFile()) {
     this.error(strings.GetStringFromName("validator.wrongManifestFileName"));
-    return;
+    return null;
   }
+  return manifestFile;
+};
 
+AppValidator.prototype._getPackagedManifestURL = function () {
+  let manifestFile = this._getPackagedManifestFile();
+  if (!manifestFile) {
+    return null;
+  }
   return Services.io.newFileURI(manifestFile).spec;
-}
+};
 
 AppValidator.prototype._fetchManifest = function (manifestURL) {
   let deferred = promise.defer();
 
   let req = new XMLHttpRequest();
   try {
     req.open("GET", manifestURL, true);
   } catch(e) {
     this.error(strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1));
     deferred.resolve(null);
     return deferred.promise;
   }
-  req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+  req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
   req.onload = (function () {
     let manifest = null;
     try {
       manifest = JSON.parse(req.responseText);
     } catch(e) {
       this.error(strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2));
     }
     deferred.resolve(manifest);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/content/manifest-editor.js
@@ -0,0 +1,106 @@
+/* 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";
+
+Cu.import("resource://gre/modules/osfile.jsm");
+const {VariablesView} =
+  Cu.import("resource:///modules/devtools/VariablesView.jsm", {});
+
+const VARIABLES_VIEW_URL =
+  "chrome://browser/content/devtools/widgets/VariablesView.xul";
+
+function ManifestEditor(project) {
+  this.project = project;
+  this._onContainerReady = this._onContainerReady.bind(this);
+  this._onEval = this._onEval.bind(this);
+  this._onSwitch = this._onSwitch.bind(this);
+  this._onDelete = this._onDelete.bind(this);
+}
+
+ManifestEditor.prototype = {
+  get manifest() { return this.project.manifest; },
+
+  show: function(containerElement) {
+    let deferred = promise.defer();
+    let iframe = document.createElement("iframe");
+
+    iframe.addEventListener("load", function onIframeLoad() {
+      iframe.removeEventListener("load", onIframeLoad, true);
+      deferred.resolve(iframe.contentWindow);
+    }, true);
+
+    iframe.setAttribute("src", VARIABLES_VIEW_URL);
+    iframe.classList.add("variables-view");
+    containerElement.appendChild(iframe);
+
+    return deferred.promise.then(this._onContainerReady);
+  },
+
+  _onContainerReady: function(varWindow) {
+    let variablesContainer = varWindow.document.querySelector("#variables");
+
+    let editor = this.editor = new VariablesView(variablesContainer);
+
+    editor.onlyEnumVisible = true;
+    editor.eval = this._onEval;
+    editor.switch = this._onSwitch;
+    editor.delete = this._onDelete;
+
+    return this.update();
+  },
+
+  _onEval: function(evalString) {
+    let manifest = this.manifest;
+    eval("manifest" + evalString);
+    this.update();
+  },
+
+  _onSwitch: function(variable, newName) {
+    let manifest = this.manifest;
+    let newSymbolicName = variable.ownerView.symbolicName +
+                          "['" + newName + "']";
+    if (newSymbolicName == variable.symbolicName) {
+      return;
+    }
+
+    let evalString = "manifest" + newSymbolicName + " = " +
+                     "manifest" + variable.symbolicName + ";" +
+                     "delete manifest" + variable.symbolicName;
+
+    eval(evalString);
+    this.update();
+  },
+
+  _onDelete: function(variable) {
+    let manifest = this.manifest;
+    let evalString = "delete manifest" + variable.symbolicName;
+    eval(evalString);
+  },
+
+  update: function() {
+    this.editor.createHierarchy();
+    this.editor.rawObject = this.manifest;
+    this.editor.commitHierarchy();
+
+    // Wait until the animation from commitHierarchy has completed
+    let deferred = promise.defer();
+    setTimeout(deferred.resolve, this.editor.lazyEmptyDelay + 1);
+    return deferred.promise;
+  },
+
+  save: function() {
+    if (this.project.type == "packaged") {
+      let validator = new AppValidator(this.project);
+      let manifestFile = validator._getPackagedManifestFile();
+      let path = manifestFile.path;
+
+      let encoder = new TextEncoder();
+      let data = encoder.encode(JSON.stringify(this.manifest, null, 2));
+
+      return OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp" });
+    }
+
+    return promise.resolve();
+  }
+};
--- a/browser/devtools/app-manager/content/projects.js
+++ b/browser/devtools/app-manager/content/projects.js
@@ -10,19 +10,22 @@ Cu.import("resource:///modules/devtools/
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
 const {AppProjects} = require("devtools/app-manager/app-projects");
 const {AppValidator} = require("devtools/app-manager/app-validator");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {installHosted, installPackaged, getTargetForApp} = require("devtools/app-actor-front");
+const {EventEmitter} = Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 
 const promise = require("sdk/core/promise");
 
+const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
+
 window.addEventListener("message", function(event) {
   try {
     let json = JSON.parse(event.data);
     if (json.name == "connection") {
       let cid = parseInt(json.cid);
       for (let c of ConnectionManager.connections) {
         if (c.uid == cid) {
           UI.connection = c;
@@ -30,22 +33,30 @@ window.addEventListener("message", funct
           break;
         }
       }
     }
   } catch(e) {}
 }, false);
 
 let UI = {
+  isReady: false,
+
   onload: function() {
+    if (Services.prefs.getBoolPref(MANIFEST_EDITOR_ENABLED)) {
+      document.querySelector("#lense").setAttribute("manifest-editable", "");
+    }
+
     this.template = new Template(document.body, AppProjects.store, Utils.l10n);
     this.template.start();
 
     AppProjects.load().then(() => {
       AppProjects.store.object.projects.forEach(UI.validate);
+      this.isReady = true;
+      this.emit("ready");
     });
   },
 
   onNewConnection: function() {
     this.connection.on(Connection.Events.STATUS_CHANGED, () => this._onConnectionStatusChange());
     this._onConnectionStatusChange();
   },
 
@@ -83,21 +94,21 @@ let UI = {
 
   addHosted: function() {
     let form = document.querySelector("#new-hosted-project-wrapper")
     if (!form.checkValidity())
       return;
 
     let urlInput = document.querySelector("#url-input");
     let manifestURL = urlInput.value;
-    AppProjects.addHosted(manifestURL)
-               .then(function (project) {
-                 UI.validate(project);
-                 UI.selectProject(project.location);
-               });
+    return AppProjects.addHosted(manifestURL)
+                      .then(function (project) {
+                        UI.validate(project);
+                        UI.selectProject(project.location);
+                      });
   },
 
   _getLocalIconURL: function(project, manifest) {
     let icon;
     if (manifest.icons) {
       let size = Object.keys(manifest.icons).sort(function(a, b) b - a)[0];
       if (size) {
         icon = manifest.icons[size];
@@ -116,17 +127,16 @@ let UI = {
     }
   },
 
   validate: function(project) {
     let validation = new AppValidator(project);
     return validation.validate()
       .then(function () {
         if (validation.manifest) {
-          project.name = validation.manifest.name;
           project.icon = UI._getLocalIconURL(project, validation.manifest);
           project.manifest = validation.manifest;
         }
 
         project.validationStatus = "valid";
 
         if (validation.warnings.length > 0) {
           project.warningsCount = validation.warnings.length;
@@ -148,17 +158,20 @@ let UI = {
 
       });
 
   },
 
   update: function(button, location) {
     button.disabled = true;
     let project = AppProjects.get(location);
-    this.validate(project)
+    this.manifestEditor.save()
+        .then(() => {
+          return this.validate(project);
+        })
         .then(() => {
            // Install the app to the device if we are connected,
            // and there is no error
            if (project.errorsCount == 0 && this.listTabsResponse) {
              return this.install(project);
            }
          })
         .then(
@@ -381,10 +394,21 @@ let UI = {
     let button = document.getElementById(location);
     button.classList.add("selected");
 
     let template = '{"path":"projects.' + idx + '","childSelector":"#lense-template"}';
 
     let lense = document.querySelector("#lense");
     lense.setAttribute("template-for", template);
     this.template._processFor(lense);
+
+    let project = projects[idx];
+    this._showManifestEditor(project).then(() => this.emit("project-selected"));
   },
-}
+
+  _showManifestEditor: function(project) {
+    let editorContainer = document.querySelector("#lense .manifest-editor");
+    this.manifestEditor = new ManifestEditor(project);
+    return this.manifestEditor.show(editorContainer);
+  }
+};
+
+EventEmitter.decorate(UI);
--- a/browser/devtools/app-manager/content/projects.xhtml
+++ b/browser/devtools/app-manager/content/projects.xhtml
@@ -9,61 +9,65 @@
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 
   <head>
     <meta charset="utf8"/>
     <base href="chrome://browser/content/devtools/app-manager/"></base>
     <title>&projects.title;</title>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/app-manager/projects.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="utils.js"></script>
+    <script type="application/javascript;version=1.8" src="projects.js"></script>
+    <script type="application/javascript;version=1.8" src="template.js"></script>
+    <script type="application/javascript;version=1.8" src="manifest-editor.js"></script>
   </head>
 
   <body onload="UI.onload()">
     <aside id="sidebar">
       <div id="project-list" template='{"type":"attribute","path":"projects.length","name":"projects-count"}'>
         <div template-loop='{"arrayPath":"projects","childSelector":"#project-item-template"}'></div>
         <div id="no-project">&projects.noProjects;</div>
       </div>
       <div id="new-packaged-project" onclick="UI.addPackaged()" title="&projects.addPackagedTooltip;">&projects.addPackaged;</div>
       <div id="new-hosted-project">&projects.addHosted;
         <form onsubmit="UI.addHosted(); return false;" id="new-hosted-project-wrapper">
-          <input value="" id="url-input" type="url" required="true" pattern="https?://.+" placeholder="&projects.hostedManifestPlaceHolder2;" size="50" />
+          <input value="" id="url-input" type="url" required="true" pattern="(https?|chrome)://.+" placeholder="&projects.hostedManifestPlaceHolder2;" size="50" />
           <div onclick="UI.addHosted()" id="new-hosted-project-click" title="&projects.addHostedTooltip;"></div>
           <input type="submit" hidden="true"></input>
         </form>
       </div>
     </aside>
     <section id="lense"></section>
   </body>
 
   <template id="project-item-template">
   <div class="project-item" template='{"type":"attribute","path":"location","name":"id"}' onclick="UI.selectProject(this.id)">
     <div class="project-item-status" template='{"type":"attribute","path":"validationStatus","name":"status"}'></div>
     <img class="project-item-icon" template='{"type":"attribute","path":"icon","name":"src"}' />
     <div class="project-item-meta">
       <div class="button-remove" onclick="UI.remove(this.dataset.location, event)" template='{"type":"attribute","path":"location","name":"data-location"}' title="&projects.removeAppFromList;"></div>
-      <strong template='{"type":"textContent","path":"name"}'></strong>
+      <strong template='{"type":"textContent","path":"manifest.name"}'></strong>
       <span class="project-item-type" template='{"type":"textContent","path":"type"}'></span>
       <p class="project-item-description" template='{"type":"textContent","path":"manifest.description"}'></p>
       <div template='{"type":"attribute","path":"validationStatus","name":"status"}'>
         <div class="project-item-errors"><span template='{"type":"textContent","path":"errorsCount"}'></span></div>
         <div class="project-item-warnings"><span template='{"type":"textContent","path":"warningsCount"}'></span></div>
       </div>
     </div>
   </div>
   </template>
 
   <template id="lense-template">
   <div>
     <div class="project-details" template='{"type":"attribute","path":"validationStatus","name":"status"}'>
       <div class="project-header">
         <img class="project-icon" template='{"type":"attribute","path":"icon","name":"src"}'/>
-        <div class="project-details">
+        <div class="project-metadata">
           <div class="project-title">
-            <h1 template='{"type":"textContent","path":"name"}'></h1>
+            <h1 template='{"type":"textContent","path":"manifest.name"}'></h1>
             <div class="project-status" template='{"type":"attribute","path":"validationStatus","name":"status"}'>
               <p class="project-validation" template='{"type":"textContent","path":"validationStatus"}'></p>
               <p class="project-type" template='{"type":"textContent","path":"type"}'></p>
             </div>
           </div>
           <span template='{"type":"textContent","path":"manifest.developer.name"}'></span>
           <p class="project-location" template='{"type":"textContent","path":"location"}' onclick="UI.reveal(this.textContent)"></p>
           <p class="project-description" template='{"type":"textContent","path":"manifest.description"}'></p>
@@ -71,16 +75,14 @@
       </div>
       <div class="project-buttons">
         <button class="project-button-update" onclick="UI.update(this, this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}' title="&projects.updateAppTooltip;">&projects.updateApp;</button>
         <button class="device-action project-button-debug" onclick="UI.debug(this, this.dataset.location)" template='{"type":"attribute","path":"location","name":"data-location"}' title="&projects.debugAppTooltip;">&projects.debugApp;</button>
       </div>
       <div class="project-errors" template='{"type":"textContent","path":"errors"}'></div>
       <div class="project-warnings" template='{"type":"textContent","path":"warnings"}'></div>
     </div>
+    <div class="manifest-editor">
+      <h2>&projects.manifestEditor;</h2>
+    </div>
   </div>
   </template>
-
-
-  <script type="application/javascript;version=1.8" src="utils.js"></script>
-  <script type="application/javascript;version=1.8" src="projects.js"></script>
-  <script type="application/javascript;version=1.8" src="template.js"></script>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  head.js
+  hosted_app.manifest
+
+[browser_manifest_editor.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/browser_manifest_editor.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+
+const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
+
+function test() {
+  waitForExplicitFinish();
+
+  Task.spawn(function() {
+    Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, true);
+    let tab = yield openAppManager();
+    yield selectProjectsPanel();
+    yield addSampleHostedApp();
+    yield showSampleProjectDetails();
+    yield changeManifestValue("name", "the best app");
+    yield removeSampleHostedApp();
+    yield removeTab(tab);
+    Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, false);
+    finish();
+  });
+}
+
+function changeManifestValue(key, value) {
+  return Task.spawn(function() {
+    let manifestWindow = getManifestWindow();
+    let manifestEditor = getProjectsWindow().UI.manifestEditor;
+
+    let propElem = manifestWindow.document
+                   .querySelector("[id ^= '" + key + "']");
+    is(propElem.querySelector(".name").value, key,
+       "Key doesn't match expected value");
+
+    let valueElem = propElem.querySelector(".value");
+    EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, manifestWindow);
+
+    let valueInput = propElem.querySelector(".element-value-input");
+    valueInput.value = '"' + value + '"';
+    EventUtils.sendKey("RETURN", manifestWindow);
+
+    // Wait until the animation from commitHierarchy has completed
+    yield waitForTime(manifestEditor.editor.lazyEmptyDelay + 1);
+    // Elements have all been replaced, re-select them
+    propElem = manifestWindow.document.querySelector("[id ^= '" + key + "']");
+    valueElem = propElem.querySelector(".value");
+    is(valueElem.value, '"' + value + '"',
+       "Value doesn't match expected value");
+
+    is(manifestEditor.manifest[key], value,
+       "Manifest doesn't contain expected value");
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/head.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {utils: Cu} = Components;
+
+const {Promise: promise} =
+  Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+const {devtools} =
+  Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {require} = devtools;
+
+const {AppProjects} = require("devtools/app-manager/app-projects");
+
+const APP_MANAGER_URL = "about:app-manager";
+const TEST_BASE =
+  "chrome://mochitests/content/browser/browser/devtools/app-manager/test/";
+const HOSTED_APP_MANIFEST = TEST_BASE + "hosted_app.manifest";
+
+function addTab(url, targetWindow = window) {
+  info("Adding tab: " + url);
+
+  let deferred = promise.defer();
+  let targetBrowser = targetWindow.gBrowser;
+
+  targetWindow.focus();
+  let tab = targetBrowser.selectedTab = targetBrowser.addTab(url);
+  let linkedBrowser = tab.linkedBrowser;
+
+  linkedBrowser.addEventListener("load", function onLoad() {
+    linkedBrowser.removeEventListener("load", onLoad, true);
+    info("Tab added and finished loading: " + url);
+    deferred.resolve(tab);
+  }, true);
+
+  return deferred.promise;
+}
+
+function removeTab(tab, targetWindow = window) {
+  info("Removing tab.");
+
+  let deferred = promise.defer();
+  let targetBrowser = targetWindow.gBrowser;
+  let tabContainer = targetBrowser.tabContainer;
+
+  tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+    tabContainer.removeEventListener("TabClose", onClose, false);
+    info("Tab removed and finished closing.");
+    deferred.resolve();
+  }, false);
+
+  targetBrowser.removeTab(tab);
+
+  return deferred.promise;
+}
+
+function openAppManager() {
+  return addTab(APP_MANAGER_URL);
+}
+
+function addSampleHostedApp() {
+  info("Adding sample hosted app");
+  let projectsWindow = getProjectsWindow();
+  let projectsDocument = projectsWindow.document;
+  let url = projectsDocument.querySelector("#url-input");
+  url.value = HOSTED_APP_MANIFEST;
+  return projectsWindow.UI.addHosted();
+}
+
+function removeSampleHostedApp() {
+  info("Removing sample hosted app");
+  return AppProjects.remove(HOSTED_APP_MANIFEST);
+}
+
+function getProjectsWindow() {
+  return content.document.querySelector(".projects-panel").contentWindow;
+}
+
+function getManifestWindow() {
+  return getProjectsWindow().document.querySelector(".variables-view")
+         .contentWindow;
+}
+
+function waitForProjectsPanel(deferred = promise.defer()) {
+  info("Wait for projects panel");
+
+  let projectsWindow = getProjectsWindow();
+  let projectsUI = projectsWindow.UI;
+  if (!projectsUI) {
+    projectsWindow.addEventListener("load", function onLoad() {
+      projectsWindow.removeEventListener("load", onLoad);
+      waitForProjectsPanel(deferred);
+    });
+    return deferred.promise;
+  }
+
+  if (projectsUI.isReady) {
+    deferred.resolve();
+    return deferred.promise;
+  }
+
+  projectsUI.once("ready", deferred.resolve);
+  return deferred.promise;
+}
+
+function selectProjectsPanel() {
+  return Task.spawn(function() {
+    let projectsButton = content.document.querySelector(".projects-button");
+    EventUtils.sendMouseEvent({ type: "click" }, projectsButton, content);
+
+    yield waitForProjectsPanel();
+  });
+}
+
+function waitForProjectSelection() {
+  info("Wait for project selection");
+
+  let deferred = promise.defer();
+  getProjectsWindow().UI.once("project-selected", deferred.resolve);
+  return deferred.promise;
+}
+
+function selectFirstProject() {
+  return Task.spawn(function() {
+    let projectsFrame = content.document.querySelector(".projects-panel");
+    let projectsWindow = projectsFrame.contentWindow;
+    let projectsDoc = projectsWindow.document;
+    let projectItem = projectsDoc.querySelector(".project-item");
+    EventUtils.sendMouseEvent({ type: "click" }, projectItem, projectsWindow);
+
+    yield waitForProjectSelection();
+  });
+}
+
+function showSampleProjectDetails() {
+  return Task.spawn(function() {
+    yield selectProjectsPanel();
+    yield selectFirstProject();
+  });
+}
+
+function waitForTick() {
+  let deferred = promise.defer();
+  executeSoon(deferred.resolve);
+  return deferred.promise;
+}
+
+function waitForTime(aDelay) {
+  let deferred = promise.defer();
+  setTimeout(deferred.resolve, aDelay);
+  return deferred.promise;
+}
--- a/browser/devtools/app-manager/test/moz.build
+++ b/browser/devtools/app-manager/test/moz.build
@@ -1,8 +1,8 @@
 # -*- 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/.
 
+BROWSER_CHROME_MANIFESTS += ['browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
-
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-01.js
@@ -95,25 +95,32 @@ function test() {
       "The testScope should remember it is collapsed even if it is hidden.");
 
     testScope.visible = true;
     ok(testScope.visible,
       "The testScope should be visible after reshowing.");
     ok(!testScope.expanded,
       "The testScope should remember it is collapsed after it is reshown.");
 
+    EventUtils.sendMouseEvent({ type: "mousedown", button: 1 },
+      testScope.target.querySelector(".title"),
+      aPanel.panelWin);
+
+    ok(!testScope.expanded,
+      "Clicking the testScope title with the right mouse button should't expand it.");
+
     EventUtils.sendMouseEvent({ type: "mousedown" },
       testScope.target.querySelector(".title"),
       aPanel.panelWin);
 
     ok(testScope.expanded,
-      "Clicking the testScope tilte should expand it.");
+      "Clicking the testScope title should expand it.");
 
     EventUtils.sendMouseEvent({ type: "mousedown" },
       testScope.target.querySelector(".title"),
       aPanel.panelWin);
 
     ok(!testScope.expanded,
-      "Clicking again the testScope tilte should collapse it.");
+      "Clicking again the testScope title should collapse it.");
 
     closeDebuggerAndFinish(aPanel);
   });
 }
--- a/browser/devtools/framework/connect/connect.xhtml
+++ b/browser/devtools/framework/connect/connect.xhtml
@@ -40,11 +40,11 @@
       <p>&availableTabs;</p>
       <ul class="actors" id="tabActors"></ul>
       <p>&availableProcesses;</p>
       <ul class="actors" id="globalActors"></ul>
     </section>
     <section id="connecting">
       <p><img src="chrome://browser/skin/tabbrowser/loading.png"></img> &connecting;</p>
     </section>
-    <footer>&help;</footer>
+    <footer>&help2;</footer>
   </body>
 </html>
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -739,16 +739,25 @@ InspectorPanel.prototype = {
       this.markup.deleteNode(this.selection.nodeFront);
     } else {
       // remove the node from content
       this.walker.removeNode(this.selection.nodeFront);
     }
   },
 
   /**
+  * Trigger a high-priority layout change for things that need to be
+  * updated immediately
+  */
+  immediateLayoutChange: function Inspector_immediateLayoutChange()
+  {
+    this.emit("layout-change");
+  },
+
+  /**
    * Schedule a low-priority change event for things like paint
    * and resize.
    */
   scheduleLayoutChange: function Inspector_scheduleLayoutChange(event)
   {
     // Filter out non browser window resize events (i.e. triggered by iframes)
     if (this.browser.contentWindow === event.target) {
       if (this._timer) {
--- a/browser/devtools/inspector/test/browser_inspector_changes.js
+++ b/browser/devtools/inspector/test/browser_inspector_changes.js
@@ -12,92 +12,143 @@ function test() {
   function createDocument()
   {
     doc.body.innerHTML = '<div id="testdiv">Test div!</div>';
     doc.title = "Inspector Change Test";
     openInspector(runInspectorTests);
   }
 
 
-  function getInspectorProp(aName)
+  function getInspectorComputedProp(aName)
   {
     let computedview = inspector.sidebar.getWindowForTab("computedview").computedview.view;
     for each (let view in computedview.propertyViews) {
       if (view.name == aName) {
         return view;
       }
     }
     return null;
   }
 
+  function getInspectorRuleProp(aName)
+  {
+    let ruleview = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
+    let inlineStyles = ruleview._elementStyle.rules[0];
+
+    for each (let prop in inlineStyles.textProps) {
+      if (prop.name == aName) {
+        return prop;
+      }
+    }
+    return null;
+  }
+
   function runInspectorTests(aInspector)
   {
     inspector = aInspector;
     inspector.sidebar.once("computedview-ready", function() {
       info("Computed View ready");
       inspector.sidebar.select("computedview");
 
       testDiv = doc.getElementById("testdiv");
 
       testDiv.style.fontSize = "10px";
 
       // Start up the style inspector panel...
-      inspector.once("computed-view-refreshed", stylePanelTests);
+      inspector.once("computed-view-refreshed", computedStylePanelTests);
 
       inspector.selection.setNode(testDiv);
     });
   }
 
-  function stylePanelTests()
+  function computedStylePanelTests()
   {
     let computedview = inspector.sidebar.getWindowForTab("computedview").computedview;
     ok(computedview, "Style Panel has a cssHtmlTree");
 
-    let propView = getInspectorProp("font-size");
+    let propView = getInspectorComputedProp("font-size");
     is(propView.value, "10px", "Style inspector should be showing the correct font size.");
 
-    inspector.once("computed-view-refreshed", stylePanelAfterChange);
-
-    testDiv.style.fontSize = "15px";
+    testDiv.style.cssText = "font-size: 15px; color: red;";
 
-    // FIXME: This shouldn't be needed but as long as we don't fix the bug
-    // where the rule/computed views are not updated when the selected node's
-    // styles change, it has to stay here
-    inspector.emit("layout-change");
+    // Wait until layout-change fires from mutation to skip earlier refresh event
+    inspector.once("layout-change", () => {
+      inspector.once("computed-view-refreshed", computedStylePanelAfterChange);
+    });
   }
 
-  function stylePanelAfterChange()
+  function computedStylePanelAfterChange()
   {
-    let propView = getInspectorProp("font-size");
+    let propView = getInspectorComputedProp("font-size");
     is(propView.value, "15px", "Style inspector should be showing the new font size.");
 
-    stylePanelNotActive();
+    let propView = getInspectorComputedProp("color");
+    is(propView.value, "#F00", "Style inspector should be showing the new color.");
+
+    computedStylePanelNotActive();
   }
 
-  function stylePanelNotActive()
+  function computedStylePanelNotActive()
   {
     // Tests changes made while the style panel is not active.
     inspector.sidebar.select("ruleview");
 
-    executeSoon(function() {
-      inspector.once("computed-view-refreshed", stylePanelAfterSwitch);
-      testDiv.style.fontSize = "20px";
-      inspector.sidebar.select("computedview");
+    testDiv.style.fontSize = "20px";
+    testDiv.style.color = "blue";
+    testDiv.style.textAlign = "center";
+
+    inspector.once("computed-view-refreshed", computedStylePanelAfterSwitch);
+    inspector.sidebar.select("computedview");
+  }
 
-      // FIXME: This shouldn't be needed but as long as we don't fix the bug
-      // where the rule/computed views are not updated when the selected node's
-      // styles change, it has to stay here
-      inspector.emit("layout-change");
-    });
+  function computedStylePanelAfterSwitch()
+  {
+    let propView = getInspectorComputedProp("font-size");
+    is(propView.value, "20px", "Style inspector should be showing the new font size.");
+
+    let propView = getInspectorComputedProp("color");
+    is(propView.value, "#00F", "Style inspector should be showing the new color.");
+
+    let propView = getInspectorComputedProp("text-align");
+    is(propView.value, "center", "Style inspector should be showing the new text align.");
+
+    rulePanelTests();
   }
 
-  function stylePanelAfterSwitch()
+  function rulePanelTests()
   {
-    let propView = getInspectorProp("font-size");
-    is(propView.value, "20px", "Style inspector should be showing the newest font size.");
+    inspector.sidebar.select("ruleview");
+    let ruleview = inspector.sidebar.getWindowForTab("ruleview").ruleview;
+    ok(ruleview, "Style Panel has a ruleview");
+
+    let propView = getInspectorRuleProp("text-align");
+    is(propView.value, "center", "Style inspector should be showing the new text align.");
+
+    testDiv.style.textAlign = "right";
+    testDiv.style.color = "lightgoldenrodyellow";
+    testDiv.style.fontSize = "3em";
+    testDiv.style.textTransform = "uppercase";
+
+
+    inspector.once("rule-view-refreshed", rulePanelAfterChange);
+  }
+
+  function rulePanelAfterChange()
+  {
+    let propView = getInspectorRuleProp("text-align");
+    is(propView.value, "right", "Style inspector should be showing the new text align.");
+
+    let propView = getInspectorRuleProp("color");
+    is(propView.value, "#FAFAD2", "Style inspector should be showing the new color.")
+
+    let propView = getInspectorRuleProp("font-size");
+    is(propView.value, "3em", "Style inspector should be showing the new font size.");
+
+    let propView = getInspectorRuleProp("text-transform");
+    is(propView.value, "uppercase", "Style inspector should be showing the new text transform.");
 
     finishTest();
   }
 
   function finishTest()
   {
     gBrowser.removeCurrentTab();
     finish();
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -80,8 +80,9 @@ browser.jar:
     content/browser/devtools/app-manager/connection-footer.xhtml       (app-manager/content/connection-footer.xhtml)
     content/browser/devtools/app-manager/device.js                     (app-manager/content/device.js)
     content/browser/devtools/app-manager/device.xhtml                  (app-manager/content/device.xhtml)
     content/browser/devtools/app-manager/projects.js                   (app-manager/content/projects.js)
     content/browser/devtools/app-manager/projects.xhtml                (app-manager/content/projects.xhtml)
     content/browser/devtools/app-manager/index.xul                     (app-manager/content/index.xul)
     content/browser/devtools/app-manager/index.js                      (app-manager/content/index.js)
     content/browser/devtools/app-manager/help.xhtml                    (app-manager/content/help.xhtml)
+    content/browser/devtools/app-manager/manifest-editor.js            (app-manager/content/manifest-editor.js)
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -382,16 +382,17 @@ MarkupView.prototype = {
 
     return container;
   },
 
   /**
    * Mutation observer used for included nodes.
    */
   _mutationObserver: function(aMutations) {
+    let requiresLayoutChange = false;
     for (let mutation of aMutations) {
       let type = mutation.type;
       let target = mutation.target;
 
       if (mutation.type === "documentUnload") {
         // Treat this as a childList change of the child (maybe the protocol
         // should do this).
         type = "childList";
@@ -404,24 +405,33 @@ MarkupView.prototype = {
       let container = this._containers.get(target);
       if (!container) {
         // Container might not exist if this came from a load event for a node
         // we're not viewing.
         continue;
       }
       if (type === "attributes" || type === "characterData") {
         container.update();
+
+        // Auto refresh style properties on selected node when they change.
+        if (type === "attributes" && container.selected) {
+          requiresLayoutChange = true;
+        }
       } else if (type === "childList") {
         container.childrenDirty = true;
         // Update the children to take care of changes in the DOM
         // Passing true as the last parameter asks for mutation flashing of the
         // new nodes
         this._updateChildren(container, {flash: true});
       }
     }
+
+    if (requiresLayoutChange) {
+      this._inspector.immediateLayoutChange();
+    }
     this._waitForChildren().then(() => {
       this._flashMutatedNodes(aMutations);
       this._inspector.emit("markupmutation");
     });
   },
 
   /**
    * Given a list of mutations returned by the mutation observer, flash the
--- a/browser/devtools/shared/Parser.jsm
+++ b/browser/devtools/shared/Parser.jsm
@@ -27,17 +27,17 @@ Parser.prototype = {
    * Gets a collection of parser methods for a specified source.
    *
    * @param string aUrl [optional]
    *        The source url. The AST nodes will be cached, so you can use this
    *        identifier to avoid parsing the whole source again.
    * @param string aSource
    *        The source text content.
    */
-  get: function P_get(aUrl, aSource) {
+  get: function(aUrl, aSource) {
     // Try to use the cached AST nodes, to avoid useless parsing operations.
     if (this._cache.has(aUrl)) {
       return this._cache.get(aUrl);
     }
 
     // The source may not necessarily be JS, in which case we need to extract
     // all the scripts. Fastest/easiest way is with a regular expression.
     // Don't worry, the rules of using a <script> tag are really strict,
@@ -84,27 +84,27 @@ Parser.prototype = {
     let pool = new SyntaxTreesPool(syntaxTrees);
     this._cache.set(aUrl, pool);
     return pool;
   },
 
   /**
    * Clears all the parsed sources from cache.
    */
-  clearCache: function P_clearCache() {
+  clearCache: function() {
     this._cache.clear();
   },
 
   /**
    * Clears the AST for a particular source.
    *
    * @param String aUrl
    *        The URL of the source that is being cleared.
    */
-  clearSource: function P_clearSource(aUrl) {
+  clearSource: function(aUrl) {
     this._cache.delete(aUrl);
   },
 
   _cache: null
 };
 
 /**
  * A pool handling a collection of AST nodes generated by the reflection API.
@@ -116,37 +116,30 @@ function SyntaxTreesPool(aSyntaxTrees) {
   this._trees = aSyntaxTrees;
   this._cache = new Map();
 }
 
 SyntaxTreesPool.prototype = {
   /**
    * @see SyntaxTree.prototype.getNamedFunctionDefinitions
    */
-  getNamedFunctionDefinitions: function STP_getNamedFunctionDefinitions(aSubstring) {
+  getNamedFunctionDefinitions: function(aSubstring) {
     return this._call("getNamedFunctionDefinitions", aSubstring);
   },
 
   /**
-   * @see SyntaxTree.prototype.getFunctionAtLocation
-   */
-  getFunctionAtLocation: function STP_getFunctionAtLocation(aLine, aColumn) {
-    return this._call("getFunctionAtLocation", [aLine, aColumn]);
-  },
-
-  /**
    * Finds the offset and length of the script containing the specified offset
    * relative to its parent source.
    *
    * @param number aOffset
    *        The offset relative to the parent source.
    * @return array
    *         The offset and length relative to the enclosing script.
    */
-  getScriptInfo: function STP_getScriptInfo(aOffset) {
+  getScriptInfo: function(aOffset) {
     for (let { offset, length } of this._trees) {
       if (offset <= aOffset &&  offset + length >= aOffset) {
         return [offset, length];
       }
     }
     return [-1, -1];
   },
 
@@ -155,17 +148,17 @@ SyntaxTreesPool.prototype = {
    *
    * @param string aFunction
    *        The function name to call on the SyntaxTree instances.
    * @param any aParams
    *        Any kind params to pass to the request function.
    * @return array
    *         The results given by all known syntax trees.
    */
-  _call: function STP__call(aFunction, aParams) {
+  _call: function(aFunction, aParams) {
     let results = [];
     let requestId = aFunction + aParams; // Cache all the things!
 
     if (this._cache.has(requestId)) {
       return this._cache.get(requestId);
     }
     for (let syntaxTree of this._trees) {
       try {
@@ -216,197 +209,96 @@ SyntaxTree.prototype = {
    *
    * @param string aSubstring
    *        The string to be contained in the function name (or inferred name).
    *        Can be an empty string to match all functions.
    * @return array
    *         All the matching function declarations and expressions, as
    *         { functionName, functionLocation ... } object hashes.
    */
-  getNamedFunctionDefinitions: function ST_getNamedFunctionDefinitions(aSubstring) {
+  getNamedFunctionDefinitions: function(aSubstring) {
     let lowerCaseToken = aSubstring.toLowerCase();
     let store = [];
 
     SyntaxTreeVisitor.walk(this.AST, {
       /**
        * Callback invoked for each function declaration node.
        * @param Node aNode
        */
-      onFunctionDeclaration: function STW_onFunctionDeclaration(aNode) {
+      onFunctionDeclaration: function(aNode) {
         let functionName = aNode.id.name;
         if (functionName.toLowerCase().contains(lowerCaseToken)) {
           store.push({
             functionName: functionName,
             functionLocation: aNode.loc
           });
         }
       },
 
       /**
        * Callback invoked for each function expression node.
        * @param Node aNode
        */
-      onFunctionExpression: function STW_onFunctionExpression(aNode) {
-        let parent = aNode._parent;
-        let functionName, inferredName, inferredChain, inferredLocation;
+      onFunctionExpression: function(aNode) {
+        // Function expressions don't necessarily have a name.
+        let functionName = aNode.id ? aNode.id.name : "";
+        let functionLocation = aNode.loc || null;
 
-        // Function expressions don't necessarily have a name.
-        if (aNode.id) {
-          functionName = aNode.id.name;
-        }
         // Infer the function's name from an enclosing syntax tree node.
-        if (parent) {
-          let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
-          inferredName = inferredInfo.name;
-          inferredChain = inferredInfo.chain;
-          inferredLocation = inferredInfo.loc;
-        }
+        let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
+        let inferredName = inferredInfo.name;
+        let inferredChain = inferredInfo.chain;
+        let inferredLocation = inferredInfo.loc;
+
         // Current node may be part of a larger assignment expression stack.
-        if (parent.type == "AssignmentExpression") {
-          this.onFunctionExpression(parent);
+        if (aNode._parent.type == "AssignmentExpression") {
+          this.onFunctionExpression(aNode._parent);
         }
 
         if ((functionName && functionName.toLowerCase().contains(lowerCaseToken)) ||
             (inferredName && inferredName.toLowerCase().contains(lowerCaseToken))) {
           store.push({
             functionName: functionName,
-            functionLocation: aNode.loc,
+            functionLocation: functionLocation,
             inferredName: inferredName,
             inferredChain: inferredChain,
             inferredLocation: inferredLocation
           });
         }
       },
 
       /**
        * Callback invoked for each arrow expression node.
        * @param Node aNode
        */
-      onArrowExpression: function STW_onArrowExpression(aNode) {
-        let parent = aNode._parent;
-        let inferredName, inferredChain, inferredLocation;
-
+      onArrowExpression: function(aNode) {
         // Infer the function's name from an enclosing syntax tree node.
         let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
-        inferredName = inferredInfo.name;
-        inferredChain = inferredInfo.chain;
-        inferredLocation = inferredInfo.loc;
+        let inferredName = inferredInfo.name;
+        let inferredChain = inferredInfo.chain;
+        let inferredLocation = inferredInfo.loc;
 
         // Current node may be part of a larger assignment expression stack.
-        if (parent.type == "AssignmentExpression") {
-          this.onFunctionExpression(parent);
+        if (aNode._parent.type == "AssignmentExpression") {
+          this.onFunctionExpression(aNode._parent);
         }
 
         if (inferredName && inferredName.toLowerCase().contains(lowerCaseToken)) {
           store.push({
             inferredName: inferredName,
             inferredChain: inferredChain,
             inferredLocation: inferredLocation
           });
         }
       }
     });
 
     return store;
   },
 
-  /**
-   * Gets the "new" or "call" expression at the specified location.
-   *
-   * @param number aLine
-   *        The line in the source.
-   * @param number aColumn
-   *        The column in the source.
-   * @return object
-   *         An { functionName, functionLocation } object hash,
-   *         or null if nothing is found at the specified location.
-   */
-  getFunctionAtLocation: function STW_getFunctionAtLocation([aLine, aColumn]) {
-    let self = this;
-    let func = null;
-
-    SyntaxTreeVisitor.walk(this.AST, {
-      /**
-       * Callback invoked for each node.
-       * @param Node aNode
-       */
-      onNode: function STW_onNode(aNode) {
-        // Make sure the node is part of a branch that's guaranteed to be
-        // hovered. Otherwise, return true to abruptly halt walking this
-        // syntax tree branch. This is a really efficient optimization.
-        return ParserHelpers.isWithinLines(aNode, aLine);
-      },
-
-      /**
-       * Callback invoked for each identifier node.
-       * @param Node aNode
-       */
-      onIdentifier: function STW_onIdentifier(aNode) {
-        // Make sure the identifier itself is hovered.
-        let hovered = ParserHelpers.isWithinBounds(aNode, aLine, aColumn);
-        if (!hovered) {
-          return;
-        }
-
-        // Make sure the identifier is part of a "new" expression or
-        // "call" expression node.
-        let expression = ParserHelpers.getEnclosingFunctionExpression(aNode);
-        if (!expression) {
-          return;
-        }
-
-        // Found an identifier node that is part of a "new" expression or
-        // "call" expression node. However, it may be an argument, not a callee.
-        if (ParserHelpers.isFunctionCalleeArgument(aNode)) {
-          // It's an argument.
-          if (self.functionIdentifiersCache.has(aNode.name)) {
-            // It's a function as an argument.
-            func = {
-              functionName: aNode.name,
-              functionLocation: aNode.loc || aNode._parent.loc
-            };
-          }
-          return;
-        }
-
-        // Found a valid "new" expression or "call" expression node.
-        func = {
-          functionName: aNode.name,
-          functionLocation: ParserHelpers.getFunctionCalleeInfo(expression).loc
-        };
-
-        // Abruptly halt walking the syntax tree.
-        this.break = true;
-      }
-    });
-
-    return func;
-  },
-
-  /**
-   * Gets all the function identifiers in this syntax tree (both the
-   * function names and their inferred names).
-   *
-   * @return array
-   *         An array of strings.
-   */
-  get functionIdentifiersCache() {
-    if (this._functionIdentifiersCache) {
-      return this._functionIdentifiersCache;
-    }
-    let functionDefinitions = this.getNamedFunctionDefinitions("");
-    let functionIdentifiers = new Set();
-
-    for (let { functionName, inferredName } of functionDefinitions) {
-      functionIdentifiers.add(functionName);
-      functionIdentifiers.add(inferredName);
-    }
-    return this._functionIdentifiersCache = functionIdentifiers;
-  },
-
   AST: null,
   url: "",
   length: 0,
   offset: 0
 };
 
 /**
  * Parser utility methods.
@@ -417,17 +309,17 @@ let ParserHelpers = {
    *
    * @param Node aNode
    *        The node's bounds used as reference.
    * @param number aLine
    *        The line number to check.
    * @return boolean
    *         True if the line and column is contained in the node's bounds.
    */
-  isWithinLines: function PH_isWithinLines(aNode, aLine) {
+  isWithinLines: function(aNode, aLine) {
     // Not all nodes have location information attached.
     if (!aNode.loc) {
       return this.isWithinLines(aNode._parent, aLine);
     }
     return aNode.loc.start.line <= aLine && aNode.loc.end.line >= aLine;
   },
 
   /**
@@ -437,37 +329,37 @@ let ParserHelpers = {
    *        The node's bounds used as reference.
    * @param number aLine
    *        The line number to check.
    * @param number aColumn
    *        The column number to check.
    * @return boolean
    *         True if the line and column is contained in the node's bounds.
    */
-  isWithinBounds: function PH_isWithinBounds(aNode, aLine, aColumn) {
+  isWithinBounds: function(aNode, aLine, aColumn) {
     // Not all nodes have location information attached.
     if (!aNode.loc) {
       return this.isWithinBounds(aNode._parent, aLine, aColumn);
     }
     return aNode.loc.start.line == aLine && aNode.loc.end.line == aLine &&
            aNode.loc.start.column <= aColumn && aNode.loc.end.column >= aColumn;
   },
 
   /**
    * Try to infer a function expression's name & other details based on the
-   * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression node.
+   * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression.
    *
    * @param Node aNode
    *        The function expression node to get the name for.
    * @return object
-   *         The inferred function name, or empty string can't infer name,
+   *         The inferred function name, or empty string can't infer the name,
    *         along with the chain (a generic "context", like a prototype chain)
    *         and location if available.
    */
-  inferFunctionExpressionInfo: function PH_inferFunctionExpressionInfo(aNode) {
+  inferFunctionExpressionInfo: function(aNode) {
     let parent = aNode._parent;
 
     // A function expression may be defined in a variable declarator,
     // e.g. var foo = function(){}, in which case it is possible to infer
     // the variable name.
     if (parent.type == "VariableDeclarator") {
       return {
         name: parent.id.name,
@@ -475,220 +367,147 @@ let ParserHelpers = {
         loc: parent.loc
       };
     }
 
     // Function expressions can also be defined in assignment expressions,
     // e.g. foo = function(){} or foo.bar = function(){}, in which case it is
     // possible to infer the assignee name ("foo" and "bar" respectively).
     if (parent.type == "AssignmentExpression") {
-      let assigneeChain = this.getAssignmentExpressionAssigneeChain(parent);
-      let assigneeLeaf = assigneeChain.pop();
+      let propertyChain = this.getMemberExpressionPropertyChain(parent.left);
+      let propertyLeaf = propertyChain.pop();
       return {
-        name: assigneeLeaf,
-        chain: assigneeChain,
+        name: propertyLeaf,
+        chain: propertyChain,
         loc: parent.left.loc
       };
     }
 
     // If a function expression is defined in an object expression,
     // e.g. { foo: function(){} }, then it is possible to infer the name
     // from the corresponding property.
     if (parent.type == "ObjectExpression") {
-      let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
+      let propertyKey = this.getObjectExpressionPropertyKeyForValue(aNode);
       let propertyChain = this.getObjectExpressionPropertyChain(parent);
-      let propertyLeaf = propertyDetails.name;
+      let propertyLeaf = propertyKey.name;
       return {
         name: propertyLeaf,
         chain: propertyChain,
-        loc: propertyDetails.loc
+        loc: propertyKey.loc
       };
     }
 
     // Can't infer the function expression's name.
     return {
       name: "",
       chain: null,
       loc: null
     };
   },
 
   /**
-   * Gets details about an object expression's property to which a specified
-   * value is assigned. For example, the node returned for the value 42 in
-   * "{ foo: { bar: 42 } }" is "bar".
+   * Gets the name of an object expression's property to which a specified
+   * value is assigned.
+   *
+   * For example, if aNode represents the "bar" identifier in a hypothetical
+   * "{ foo: bar }" object expression, the returned node is the "foo" identifier.
    *
    * @param Node aNode
-   *        The value node assigned to a property in an object expression.
+   *        The value node in an object expression.
    * @return object
-   *         The details about the assignee property node.
+   *         The key identifier node in the object expression.
    */
-  getObjectExpressionPropertyKeyForValue:
-  function PH_getObjectExpressionPropertyKeyForValue(aNode) {
+  getObjectExpressionPropertyKeyForValue: function(aNode) {
     let parent = aNode._parent;
     if (parent.type != "ObjectExpression") {
       return null;
     }
     for (let property of parent.properties) {
       if (property.value == aNode) {
         return property.key;
       }
     }
   },
 
   /**
-   * Gets an object expression property chain to its parent variable declarator.
-   * For example, the chain to "baz" in "foo = { bar: { baz: { } } }" is
-   * ["foo", "bar", "baz"].
+   * Gets an object expression's property chain to its parent
+   * variable declarator or assignment expression, if available.
+   *
+   * For example, if aNode represents the "baz: {}" object expression in a
+   * hypothetical "foo = { bar: { baz: {} } }" assignment expression, the
+   * returned chain is ["foo", "bar", "baz"].
    *
    * @param Node aNode
    *        The object expression node to begin the scan from.
    * @param array aStore [optional]
    *        The chain to store the nodes into.
    * @return array
    *         The chain to the parent variable declarator, as strings.
    */
-  getObjectExpressionPropertyChain:
-  function PH_getObjectExpressionPropertyChain(aNode, aStore = []) {
+  getObjectExpressionPropertyChain: function(aNode, aStore = []) {
     switch (aNode.type) {
       case "ObjectExpression":
         this.getObjectExpressionPropertyChain(aNode._parent, aStore);
-
-        let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
-        if (propertyDetails) {
-          aStore.push(this.getObjectExpressionPropertyKeyForValue(aNode).name);
+        let propertyKey = this.getObjectExpressionPropertyKeyForValue(aNode);
+        if (propertyKey) {
+          aStore.push(propertyKey.name);
         }
         break;
-      // Handle "foo.bar = { ... }" since it's commonly used when defining an
-      // object's prototype methods; for example: "Foo.prototype = { ... }".
+      // Handle "var foo = { ... }" variable declarators.
+      case "VariableDeclarator":
+        aStore.push(aNode.id.name);
+        break;
+      // Handle "foo.bar = { ... }" assignment expressions, since they're
+      // commonly used when defining an object's prototype methods; e.g:
+      // "Foo.prototype = { ... }".
       case "AssignmentExpression":
-        this.getAssignmentExpressionAssigneeChain(aNode, aStore);
+        this.getMemberExpressionPropertyChain(aNode.left, aStore);
         break;
       // Additionally handle stuff like "foo = bar.baz({ ... })", because it's
-      // commonly used in prototype-based inheritance in many libraries;
-      // for example: "Foo.Bar = Baz.extend({ ... })".
+      // commonly used in prototype-based inheritance in many libraries; e.g:
+      // "Foo = Bar.extend({ ... })".
       case "NewExpression":
       case "CallExpression":
         this.getObjectExpressionPropertyChain(aNode._parent, aStore);
         break;
-      // End of the chain.
-      case "VariableDeclarator":
-        aStore.push(aNode.id.name);
-        break;
     }
     return aStore;
   },
 
   /**
-   * Gets the assignee property chain in an assignment expression.
-   * For example, the chain in "foo.bar.baz = 42" is ["foo", "bar", "baz"].
+   * Gets a member expression's property chain.
+   *
+   * For example, if aNode represents a hypothetical "foo.bar.baz"
+   * member expression, the returned chain ["foo", "bar", "baz"].
+   *
+   * More complex expressions like foo.bar().baz are intentionally not handled.
    *
    * @param Node aNode
-   *        The assignment expression node to begin the scan from.
-   * @param array aStore
-   *        The chain to store the nodes into.
+   *        The member expression node to begin the scan from.
    * @param array aStore [optional]
    *        The chain to store the nodes into.
    * @return array
-   *         The full assignee chain, as strings.
+   *         The full member chain, as strings.
    */
-  getAssignmentExpressionAssigneeChain:
-  function PH_getAssignmentExpressionAssigneeChain(aNode, aStore = []) {
+  getMemberExpressionPropertyChain: function(aNode, aStore = []) {
     switch (aNode.type) {
-      case "AssignmentExpression":
-        this.getAssignmentExpressionAssigneeChain(aNode.left, aStore);
-        break;
       case "MemberExpression":
-        this.getAssignmentExpressionAssigneeChain(aNode.object, aStore);
-        this.getAssignmentExpressionAssigneeChain(aNode.property, aStore);
+        this.getMemberExpressionPropertyChain(aNode.object, aStore);
+        this.getMemberExpressionPropertyChain(aNode.property, aStore);
         break;
       case "ThisExpression":
         // Such expressions may appear in an assignee chain, for example
         // "this.foo.bar = baz", however it seems better to ignore such nodes
         // and limit the chain to ["foo", "bar"].
         break;
       case "Identifier":
         aStore.push(aNode.name);
         break;
     }
     return aStore;
-  },
-
-  /**
-   * Gets the "new" expression or "call" expression containing the specified
-   * node. If the node is not enclosed in either of these expression types,
-   * null is returned.
-   *
-   * @param Node aNode
-   *        The child node of an enclosing "new" expression or "call" expression.
-   * @return Node
-   *         The enclosing "new" expression or "call" expression node, or
-   *         null if nothing is found.
-   */
-  getEnclosingFunctionExpression:
-  function PH_getEnclosingFunctionExpression(aNode) {
-    switch (aNode.type) {
-      case "NewExpression":
-      case "CallExpression":
-        return aNode;
-      case "MemberExpression":
-      case "Identifier":
-        return this.getEnclosingFunctionExpression(aNode._parent);
-      default:
-        return null;
-    }
-  },
-
-  /**
-   * Gets the name and { line, column } location of a "new" expression or
-   * "call" expression's callee node.
-   *
-   * @param Node aNode
-   *        The "new" expression or "call" expression to get the callee info for.
-   * @return object
-   *         An object containing the name and location as properties, or
-   *         null if nothing is found.
-   */
-  getFunctionCalleeInfo: function PH_getFunctionCalleeInfo(aNode) {
-    switch (aNode.type) {
-      case "NewExpression":
-      case "CallExpression":
-        return this.getFunctionCalleeInfo(aNode.callee);
-      case "MemberExpression":
-        return this.getFunctionCalleeInfo(aNode.property);
-      case "Identifier":
-        return {
-          name: aNode.name,
-          loc: aNode.loc || (aNode._parent || {}).loc
-        };
-      default:
-        return null;
-    }
-  },
-
-  /**
-   * Determines if an identifier node is part of a "new" expression or
-   * "call" expression's callee arguments.
-   *
-   * @param Node aNode
-   *        The node to determine if part of a function's arguments.
-   * @return boolean
-   *         True if the identifier is an argument, false otherwise.
-   */
-  isFunctionCalleeArgument: function PH_isFunctionCalleeArgument(aNode) {
-    if (!aNode._parent) {
-      return false;
-    }
-    switch (aNode._parent.type) {
-      case "NewExpression":
-      case "CallExpression":
-        return aNode._parent.arguments.indexOf(aNode) != -1;
-      default:
-        return this.isFunctionCalleeArgument(aNode._parent);
-    }
   }
 };
 
 /**
  * A visitor for a syntax tree generated by the reflection API.
  * See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API.
  *
  * All node types implement the following interface:
@@ -702,17 +521,17 @@ let SyntaxTreeVisitor = {
    * Walks a syntax tree.
    *
    * @param object aTree
    *        The AST nodes generated by the reflection API
    * @param object aCallbacks
    *        A map of all the callbacks to invoke when passing through certain
    *        types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
    */
-  walk: function STV_walk(aTree, aCallbacks) {
+  walk: function(aTree, aCallbacks) {
     this[aTree.type](aTree, aCallbacks);
   },
 
   /**
    * A flag checked on each node in the syntax tree. If true, walking is
    * abruptly halted.
    */
   break: false,
@@ -720,31 +539,31 @@ let SyntaxTreeVisitor = {
   /**
    * A complete program source tree.
    *
    * interface Program <: Node {
    *   type: "Program";
    *   body: [ Statement ];
    * }
    */
-  Program: function STV_Program(aNode, aCallbacks) {
+  Program: function(aNode, aCallbacks) {
     if (aCallbacks.onProgram) {
       aCallbacks.onProgram(aNode);
     }
     for (let statement of aNode.body) {
       this[statement.type](statement, aNode, aCallbacks);
     }
   },
 
   /**
    * Any statement.
    *
    * interface Statement <: Node { }
    */
-  Statement: function STV_Statement(aNode, aParent, aCallbacks) {
+  Statement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -757,17 +576,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * An empty statement, i.e., a solitary semicolon.
    *
    * interface EmptyStatement <: Statement {
    *   type: "EmptyStatement";
    * }
    */
-  EmptyStatement: function STV_EmptyStatement(aNode, aParent, aCallbacks) {
+  EmptyStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -781,17 +600,17 @@ let SyntaxTreeVisitor = {
   /**
    * A block statement, i.e., a sequence of statements surrounded by braces.
    *
    * interface BlockStatement <: Statement {
    *   type: "BlockStatement";
    *   body: [ Statement ];
    * }
    */
-  BlockStatement: function STV_BlockStatement(aNode, aParent, aCallbacks) {
+  BlockStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -808,17 +627,17 @@ let SyntaxTreeVisitor = {
   /**
    * An expression statement, i.e., a statement consisting of a single expression.
    *
    * interface ExpressionStatement <: Statement {
    *   type: "ExpressionStatement";
    *   expression: Expression;
    * }
    */
-  ExpressionStatement: function STV_ExpressionStatement(aNode, aParent, aCallbacks) {
+  ExpressionStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -835,17 +654,17 @@ let SyntaxTreeVisitor = {
    *
    * interface IfStatement <: Statement {
    *   type: "IfStatement";
    *   test: Expression;
    *   consequent: Statement;
    *   alternate: Statement | null;
    * }
    */
-  IfStatement: function STV_IfStatement(aNode, aParent, aCallbacks) {
+  IfStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -865,17 +684,17 @@ let SyntaxTreeVisitor = {
    * A labeled statement, i.e., a statement prefixed by a break/continue label.
    *
    * interface LabeledStatement <: Statement {
    *   type: "LabeledStatement";
    *   label: Identifier;
    *   body: Statement;
    * }
    */
-  LabeledStatement: function STV_LabeledStatement(aNode, aParent, aCallbacks) {
+  LabeledStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -891,17 +710,17 @@ let SyntaxTreeVisitor = {
   /**
    * A break statement.
    *
    * interface BreakStatement <: Statement {
    *   type: "BreakStatement";
    *   label: Identifier | null;
    * }
    */
-  BreakStatement: function STV_BreakStatement(aNode, aParent, aCallbacks) {
+  BreakStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -918,17 +737,17 @@ let SyntaxTreeVisitor = {
   /**
    * A continue statement.
    *
    * interface ContinueStatement <: Statement {
    *   type: "ContinueStatement";
    *   label: Identifier | null;
    * }
    */
-  ContinueStatement: function STV_ContinueStatement(aNode, aParent, aCallbacks) {
+  ContinueStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -946,17 +765,17 @@ let SyntaxTreeVisitor = {
    * A with statement.
    *
    * interface WithStatement <: Statement {
    *   type: "WithStatement";
    *   object: Expression;
    *   body: Statement;
    * }
    */
-  WithStatement: function STV_WithStatement(aNode, aParent, aCallbacks) {
+  WithStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -976,17 +795,17 @@ let SyntaxTreeVisitor = {
    *
    * interface SwitchStatement <: Statement {
    *   type: "SwitchStatement";
    *   discriminant: Expression;
    *   cases: [ SwitchCase ];
    *   lexical: boolean;
    * }
    */
-  SwitchStatement: function STV_SwitchStatement(aNode, aParent, aCallbacks) {
+  SwitchStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1004,17 +823,17 @@ let SyntaxTreeVisitor = {
   /**
    * A return statement.
    *
    * interface ReturnStatement <: Statement {
    *   type: "ReturnStatement";
    *   argument: Expression | null;
    * }
    */
-  ReturnStatement: function STV_ReturnStatement(aNode, aParent, aCallbacks) {
+  ReturnStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1031,17 +850,17 @@ let SyntaxTreeVisitor = {
   /**
    * A throw statement.
    *
    * interface ThrowStatement <: Statement {
    *   type: "ThrowStatement";
    *   argument: Expression;
    * }
    */
-  ThrowStatement: function STV_ThrowStatement(aNode, aParent, aCallbacks) {
+  ThrowStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1059,17 +878,17 @@ let SyntaxTreeVisitor = {
    * interface TryStatement <: Statement {
    *   type: "TryStatement";
    *   block: BlockStatement;
    *   handler: CatchClause | null;
    *   guardedHandlers: [ CatchClause ];
    *   finalizer: BlockStatement | null;
    * }
    */
-  TryStatement: function STV_TryStatement(aNode, aParent, aCallbacks) {
+  TryStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1094,17 +913,17 @@ let SyntaxTreeVisitor = {
    * A while statement.
    *
    * interface WhileStatement <: Statement {
    *   type: "WhileStatement";
    *   test: Expression;
    *   body: Statement;
    * }
    */
-  WhileStatement: function STV_WhileStatement(aNode, aParent, aCallbacks) {
+  WhileStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1121,17 +940,17 @@ let SyntaxTreeVisitor = {
    * A do/while statement.
    *
    * interface DoWhileStatement <: Statement {
    *   type: "DoWhileStatement";
    *   body: Statement;
    *   test: Expression;
    * }
    */
-  DoWhileStatement: function STV_DoWhileStatement(aNode, aParent, aCallbacks) {
+  DoWhileStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1150,17 +969,17 @@ let SyntaxTreeVisitor = {
    * interface ForStatement <: Statement {
    *   type: "ForStatement";
    *   init: VariableDeclaration | Expression | null;
    *   test: Expression | null;
    *   update: Expression | null;
    *   body: Statement;
    * }
    */
-  ForStatement: function STV_ForStatement(aNode, aParent, aCallbacks) {
+  ForStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1187,17 +1006,17 @@ let SyntaxTreeVisitor = {
    * interface ForInStatement <: Statement {
    *   type: "ForInStatement";
    *   left: VariableDeclaration | Expression;
    *   right: Expression;
    *   body: Statement;
    *   each: boolean;
    * }
    */
-  ForInStatement: function STV_ForInStatement(aNode, aParent, aCallbacks) {
+  ForInStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1216,17 +1035,17 @@ let SyntaxTreeVisitor = {
    *
    * interface ForOfStatement <: Statement {
    *   type: "ForOfStatement";
    *   left: VariableDeclaration | Expression;
    *   right: Expression;
    *   body: Statement;
    * }
    */
-  ForOfStatement: function STV_ForOfStatement(aNode, aParent, aCallbacks) {
+  ForOfStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1244,17 +1063,17 @@ let SyntaxTreeVisitor = {
    * A let statement.
    *
    * interface LetStatement <: Statement {
    *   type: "LetStatement";
    *   head: [ { id: Pattern, init: Expression | null } ];
    *   body: Statement;
    * }
    */
-  LetStatement: function STV_LetStatement(aNode, aParent, aCallbacks) {
+  LetStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1274,17 +1093,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * A debugger statement.
    *
    * interface DebuggerStatement <: Statement {
    *   type: "DebuggerStatement";
    * }
    */
-  DebuggerStatement: function STV_DebuggerStatement(aNode, aParent, aCallbacks) {
+  DebuggerStatement: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1297,17 +1116,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * Any declaration node. Note that declarations are considered statements;
    * this is because declarations can appear in any statement context in the
    * language recognized by the SpiderMonkey parser.
    *
    * interface Declaration <: Statement { }
    */
-  Declaration: function STV_Declaration(aNode, aParent, aCallbacks) {
+  Declaration: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1327,17 +1146,17 @@ let SyntaxTreeVisitor = {
    *   params: [ Pattern ];
    *   defaults: [ Expression ];
    *   rest: Identifier | null;
    *   body: BlockStatement | Expression;
    *   generator: boolean;
    *   expression: boolean;
    * }
    */
-  FunctionDeclaration: function STV_FunctionDeclaration(aNode, aParent, aCallbacks) {
+  FunctionDeclaration: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1363,17 +1182,17 @@ let SyntaxTreeVisitor = {
    * A variable declaration, via one of var, let, or const.
    *
    * interface VariableDeclaration <: Declaration {
    *   type: "VariableDeclaration";
    *   declarations: [ VariableDeclarator ];
    *   kind: "var" | "let" | "const";
    * }
    */
-  VariableDeclaration: function STV_VariableDeclaration(aNode, aParent, aCallbacks) {
+  VariableDeclaration: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1391,17 +1210,17 @@ let SyntaxTreeVisitor = {
    * A variable declarator.
    *
    * interface VariableDeclarator <: Node {
    *   type: "VariableDeclarator";
    *   id: Pattern;
    *   init: Expression | null;
    * }
    */
-  VariableDeclarator: function STV_VariableDeclarator(aNode, aParent, aCallbacks) {
+  VariableDeclarator: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1417,17 +1236,17 @@ let SyntaxTreeVisitor = {
   },
 
   /**
    * Any expression node. Since the left-hand side of an assignment may be any
    * expression in general, an expression can also be a pattern.
    *
    * interface Expression <: Node, Pattern { }
    */
-  Expression: function STV_Expression(aNode, aParent, aCallbacks) {
+  Expression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1440,17 +1259,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * A this expression.
    *
    * interface ThisExpression <: Expression {
    *   type: "ThisExpression";
    * }
    */
-  ThisExpression: function STV_ThisExpression(aNode, aParent, aCallbacks) {
+  ThisExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1464,17 +1283,17 @@ let SyntaxTreeVisitor = {
   /**
    * An array expression.
    *
    * interface ArrayExpression <: Expression {
    *   type: "ArrayExpression";
    *   elements: [ Expression | null ];
    * }
    */
-  ArrayExpression: function STV_ArrayExpression(aNode, aParent, aCallbacks) {
+  ArrayExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1500,17 +1319,17 @@ let SyntaxTreeVisitor = {
    *
    * interface ObjectExpression <: Expression {
    *   type: "ObjectExpression";
    *   properties: [ { key: Literal | Identifier,
    *                   value: Expression,
    *                   kind: "init" | "get" | "set" } ];
    * }
    */
-  ObjectExpression: function STV_ObjectExpression(aNode, aParent, aCallbacks) {
+  ObjectExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1534,17 +1353,17 @@ let SyntaxTreeVisitor = {
    *   params: [ Pattern ];
    *   defaults: [ Expression ];
    *   rest: Identifier | null;
    *   body: BlockStatement | Expression;
    *   generator: boolean;
    *   expression: boolean;
    * }
    */
-  FunctionExpression: function STV_FunctionExpression(aNode, aParent, aCallbacks) {
+  FunctionExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1576,17 +1395,17 @@ let SyntaxTreeVisitor = {
    *   params: [ Pattern ];
    *   defaults: [ Expression ];
    *   rest: Identifier | null;
    *   body: BlockStatement | Expression;
    *   generator: boolean;
    *   expression: boolean;
    * }
    */
-  ArrowExpression: function STV_ArrowExpression(aNode, aParent, aCallbacks) {
+  ArrowExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1610,17 +1429,17 @@ let SyntaxTreeVisitor = {
   /**
    * A sequence expression, i.e., a comma-separated sequence of expressions.
    *
    * interface SequenceExpression <: Expression {
    *   type: "SequenceExpression";
    *   expressions: [ Expression ];
    * }
    */
-  SequenceExpression: function STV_SequenceExpression(aNode, aParent, aCallbacks) {
+  SequenceExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1639,17 +1458,17 @@ let SyntaxTreeVisitor = {
    *
    * interface UnaryExpression <: Expression {
    *   type: "UnaryExpression";
    *   operator: UnaryOperator;
    *   prefix: boolean;
    *   argument: Expression;
    * }
    */
-  UnaryExpression: function STV_UnaryExpression(aNode, aParent, aCallbacks) {
+  UnaryExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1666,17 +1485,17 @@ let SyntaxTreeVisitor = {
    *
    * interface BinaryExpression <: Expression {
    *   type: "BinaryExpression";
    *   operator: BinaryOperator;
    *   left: Expression;
    *   right: Expression;
    * }
    */
-  BinaryExpression: function STV_BinaryExpression(aNode, aParent, aCallbacks) {
+  BinaryExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1694,17 +1513,17 @@ let SyntaxTreeVisitor = {
    *
    * interface AssignmentExpression <: Expression {
    *   type: "AssignmentExpression";
    *   operator: AssignmentOperator;
    *   left: Expression;
    *   right: Expression;
    * }
    */
-  AssignmentExpression: function STV_AssignmentExpression(aNode, aParent, aCallbacks) {
+  AssignmentExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1722,17 +1541,17 @@ let SyntaxTreeVisitor = {
    *
    * interface UpdateExpression <: Expression {
    *   type: "UpdateExpression";
    *   operator: UpdateOperator;
    *   argument: Expression;
    *   prefix: boolean;
    * }
    */
-  UpdateExpression: function STV_UpdateExpression(aNode, aParent, aCallbacks) {
+  UpdateExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1749,17 +1568,17 @@ let SyntaxTreeVisitor = {
    *
    * interface LogicalExpression <: Expression {
    *   type: "LogicalExpression";
    *   operator: LogicalOperator;
    *   left: Expression;
    *   right: Expression;
    * }
    */
-  LogicalExpression: function STV_LogicalExpression(aNode, aParent, aCallbacks) {
+  LogicalExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1777,17 +1596,17 @@ let SyntaxTreeVisitor = {
    *
    * interface ConditionalExpression <: Expression {
    *   type: "ConditionalExpression";
    *   test: Expression;
    *   alternate: Expression;
    *   consequent: Expression;
    * }
    */
-  ConditionalExpression: function STV_ConditionalExpression(aNode, aParent, aCallbacks) {
+  ConditionalExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1805,17 +1624,17 @@ let SyntaxTreeVisitor = {
    * A new expression.
    *
    * interface NewExpression <: Expression {
    *   type: "NewExpression";
    *   callee: Expression;
    *   arguments: [ Expression | null ];
    * }
    */
-  NewExpression: function STV_NewExpression(aNode, aParent, aCallbacks) {
+  NewExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1836,17 +1655,17 @@ let SyntaxTreeVisitor = {
    * A function or method call expression.
    *
    * interface CallExpression <: Expression {
    *   type: "CallExpression";
    *   callee: Expression;
    *   arguments: [ Expression | null ];
    * }
    */
-  CallExpression: function STV_CallExpression(aNode, aParent, aCallbacks) {
+  CallExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1871,17 +1690,17 @@ let SyntaxTreeVisitor = {
    *
    * interface MemberExpression <: Expression {
    *   type: "MemberExpression";
    *   object: Expression;
    *   property: Identifier | Expression;
    *   computed: boolean;
    * }
    */
-  MemberExpression: function STV_MemberExpression(aNode, aParent, aCallbacks) {
+  MemberExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1896,17 +1715,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * A yield expression.
    *
    * interface YieldExpression <: Expression {
    *   argument: Expression | null;
    * }
    */
-  YieldExpression: function STV_YieldExpression(aNode, aParent, aCallbacks) {
+  YieldExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1926,17 +1745,17 @@ let SyntaxTreeVisitor = {
    * final if clause, if present.
    *
    * interface ComprehensionExpression <: Expression {
    *   body: Expression;
    *   blocks: [ ComprehensionBlock ];
    *   filter: Expression | null;
    * }
    */
-  ComprehensionExpression: function STV_ComprehensionExpression(aNode, aParent, aCallbacks) {
+  ComprehensionExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1960,17 +1779,17 @@ let SyntaxTreeVisitor = {
    * filter expression corresponds to the final if clause, if present.
    *
    * interface GeneratorExpression <: Expression {
    *   body: Expression;
    *   blocks: [ ComprehensionBlock ];
    *   filter: Expression | null;
    * }
    */
-  GeneratorExpression: function STV_GeneratorExpression(aNode, aParent, aCallbacks) {
+  GeneratorExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -1991,17 +1810,17 @@ let SyntaxTreeVisitor = {
   /**
    * A graph expression, aka "sharp literal," such as #1={ self: #1# }.
    *
    * interface GraphExpression <: Expression {
    *   index: uint32;
    *   expression: Literal;
    * }
    */
-  GraphExpression: function STV_GraphExpression(aNode, aParent, aCallbacks) {
+  GraphExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2015,17 +1834,17 @@ let SyntaxTreeVisitor = {
 
   /**
    * A graph index expression, aka "sharp variable," such as #1#.
    *
    * interface GraphIndexExpression <: Expression {
    *   index: uint32;
    * }
    */
-  GraphIndexExpression: function STV_GraphIndexExpression(aNode, aParent, aCallbacks) {
+  GraphIndexExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2040,17 +1859,17 @@ let SyntaxTreeVisitor = {
    * A let expression.
    *
    * interface LetExpression <: Expression {
    *   type: "LetExpression";
    *   head: [ { id: Pattern, init: Expression | null } ];
    *   body: Expression;
    * }
    */
-  LetExpression: function STV_LetExpression(aNode, aParent, aCallbacks) {
+  LetExpression: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2068,17 +1887,17 @@ let SyntaxTreeVisitor = {
     this[aNode.body.type](aNode.body, aNode, aCallbacks);
   },
 
   /**
    * Any pattern.
    *
    * interface Pattern <: Node { }
    */
-  Pattern: function STV_Pattern(aNode, aParent, aCallbacks) {
+  Pattern: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2093,17 +1912,17 @@ let SyntaxTreeVisitor = {
    * An object-destructuring pattern. A literal property in an object pattern
    * can have either a string or number as its value.
    *
    * interface ObjectPattern <: Pattern {
    *   type: "ObjectPattern";
    *   properties: [ { key: Literal | Identifier, value: Pattern } ];
    * }
    */
-  ObjectPattern: function STV_ObjectPattern(aNode, aParent, aCallbacks) {
+  ObjectPattern: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2121,17 +1940,17 @@ let SyntaxTreeVisitor = {
   /**
    * An array-destructuring pattern.
    *
    * interface ArrayPattern <: Pattern {
    *   type: "ArrayPattern";
    *   elements: [ Pattern | null ];
    * }
    */
-  ArrayPattern: function STV_ArrayPattern(aNode, aParent, aCallbacks) {
+  ArrayPattern: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2152,17 +1971,17 @@ let SyntaxTreeVisitor = {
    * the body of a switch statement.
    *
    * interface SwitchCase <: Node {
    *   type: "SwitchCase";
    *   test: Expression | null;
    *   consequent: [ Statement ];
    * }
    */
-  SwitchCase: function STV_SwitchCase(aNode, aParent, aCallbacks) {
+  SwitchCase: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2185,17 +2004,17 @@ let SyntaxTreeVisitor = {
    *
    * interface CatchClause <: Node {
    *   type: "CatchClause";
    *   param: Pattern;
    *   guard: Expression | null;
    *   body: BlockStatement;
    * }
    */
-  CatchClause: function STV_CatchClause(aNode, aParent, aCallbacks) {
+  CatchClause: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2215,17 +2034,17 @@ let SyntaxTreeVisitor = {
    * A for or for each block in an array comprehension or generator expression.
    *
    * interface ComprehensionBlock <: Node {
    *   left: Pattern;
    *   right: Expression;
    *   each: boolean;
    * }
    */
-  ComprehensionBlock: function STV_ComprehensionBlock(aNode, aParent, aCallbacks) {
+  ComprehensionBlock: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2242,17 +2061,17 @@ let SyntaxTreeVisitor = {
    * An identifier. Note that an identifier may be an expression or a
    * destructuring pattern.
    *
    * interface Identifier <: Node, Expression, Pattern {
    *   type: "Identifier";
    *   name: string;
    * }
    */
-  Identifier: function STV_Identifier(aNode, aParent, aCallbacks) {
+  Identifier: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
@@ -2266,17 +2085,17 @@ let SyntaxTreeVisitor = {
   /**
    * A literal token. Note that a literal can be an expression.
    *
    * interface Literal <: Node, Expression {
    *   type: "Literal";
    *   value: string | boolean | null | number | RegExp;
    * }
    */
-  Literal: function STV_Literal(aNode, aParent, aCallbacks) {
+  Literal: function(aNode, aParent, aCallbacks) {
     aNode._parent = aParent;
 
     if (this.break) {
       return;
     }
     if (aCallbacks.onNode) {
       if (aCallbacks.onNode(aNode, aParent) === false) {
         return;
--- a/browser/devtools/shared/theme-switching.js
+++ b/browser/devtools/shared/theme-switching.js
@@ -30,17 +30,20 @@
       } catch(ex) {}
     }
 
     let newThemeUrl = Services.io.newURI(
       DEVTOOLS_SKIN_URL + newTheme + "-theme.css", null, null);
     winUtils.loadSheet(newThemeUrl, winUtils.AUTHOR_SHEET);
 
     // Floating scrollbars à la osx
-    if (!window.matchMedia("(-moz-overlay-scrollbars)").matches) {
+    let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
+                 .getService(Ci.nsIAppShellService)
+                 .hiddenDOMWindow;
+    if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
       let scrollbarsUrl = Services.io.newURI(
         DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
 
       if (newTheme == "dark") {
         winUtils.loadSheet(scrollbarsUrl, winUtils.AGENT_SHEET);
       } else if (oldTheme == "dark") {
         try {
           winUtils.removeSheet(scrollbarsUrl, winUtils.AGENT_SHEET);
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -162,17 +162,17 @@ SideMenuWidget.prototype = {
 
   /**
    * Removes the specified child node from this container.
    *
    * @param nsIDOMNode aChild
    *        The element associated with the displayed item.
    */
   removeChild: function(aChild) {
-    if (aChild.className == "side-menu-widget-item-contents") {
+    if (aChild.classList.contains("side-menu-widget-item-contents")) {
       // Remove the item itself, not the contents.
       aChild.parentNode.remove();
     } else {
       // Groups with no title don't have any special internal structure.
       aChild.remove();
     }
 
     this._orderedMenuElementsArray.splice(
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -16,16 +16,17 @@ const LAZY_APPEND_BATCH = 100; // nodes
 const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
 const PAGE_SIZE_MAX_JUMPS = 30;
 const SEARCH_ACTION_MAX_DELAY = 300; // ms
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
   "resource://gre/modules/devtools/Loader.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1",
   "nsIClipboardHelper");
 
@@ -103,17 +104,19 @@ VariablesView.prototype = {
    * Helper setter for populating this container with a raw object.
    *
    * @param object aObject
    *        The raw object to display. You can only provide this object
    *        if you want the variables view to work in sync mode.
    */
   set rawObject(aObject) {
     this.empty();
-    this.addScope().addItem().populate(aObject, { sorted: true });
+    this.addScope()
+        .addItem("", { enumerable: true })
+        .populate(aObject, { sorted: true });
   },
 
   /**
    * Adds a scope to contain any inspected variables.
    *
    * @param string aName
    *        The scope's name (e.g. "Local", "Global" etc.).
    * @return Scope
@@ -1115,24 +1118,16 @@ function Scope(aView, aName, aFlags = {}
   this.preventDescriptorModifiers = aView.preventDescriptorModifiers;
   this.editableNameTooltip = aView.editableNameTooltip;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
-  // Creating maps and arrays thousands of times for variables or properties
-  // with a large number of children fills up a lot of memory. Make sure
-  // these are instantiated only if needed.
-  XPCOMUtils.defineLazyGetter(this, "_store", () => new Map());
-  XPCOMUtils.defineLazyGetter(this, "_enumItems", () => []);
-  XPCOMUtils.defineLazyGetter(this, "_nonEnumItems", () => []);
-  XPCOMUtils.defineLazyGetter(this, "_batchItems", () => []);
-
   this._init(aName.trim(), aFlags);
 }
 
 Scope.prototype = {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   shouldPrefetch: true,
@@ -1644,17 +1639,18 @@ Scope.prototype = {
   _addEventListeners: function() {
     this._title.addEventListener("mousedown", this._onClick, false);
   },
 
   /**
    * The click listener for this scope's title.
    */
   _onClick: function(e) {
-    if (e.target == this._inputNode ||
+    if (e.button != 0 ||
+        e.target == this._inputNode ||
         e.target == this._editNode ||
         e.target == this._deleteNode) {
       return;
     }
     this.toggle();
     this.focus();
   },
 
@@ -2031,16 +2027,24 @@ Scope.prototype = {
   _arrow: null,
   _name: null,
   _title: null,
   _enum: null,
   _nonenum: null,
   _throbber: null
 };
 
+// Creating maps and arrays thousands of times for variables or properties
+// with a large number of children fills up a lot of memory. Make sure
+// these are instantiated only if needed.
+DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map);
+DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array);
+DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array);
+DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_batchItems", Array);
+
 /**
  * A Variable is a Scope holding Property instances.
  * Iterable via "for (let [name, property] in instance) { }".
  *
  * @param Scope aScope
  *        The scope to contain this variable.
  * @param string aName
  *        The variable's name.
@@ -2771,25 +2775,33 @@ Variable.prototype = Heritage.extend(Sco
         return;
     }
   },
 
   /**
    * The click listener for the edit button.
    */
   _onEdit: function(e) {
+    if (e.button != 0) {
+      return;
+    }
+
     e.preventDefault();
     e.stopPropagation();
     this._activateValueInput();
   },
 
   /**
    * The click listener for the delete button.
    */
   _onDelete: function(e) {
+    if ("button" in e && e.button != 0) {
+      return;
+    }
+
     e.preventDefault();
     e.stopPropagation();
 
     if (this.ownerView.delete) {
       if (!this.ownerView.delete(this)) {
         this.hide();
       }
     }
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1278,23 +1278,23 @@ CssRuleView.prototype = {
 
   /**
    * Update the rules for the currently highlighted element.
    */
   nodeChanged: function CssRuleView_nodeChanged()
   {
     // Ignore refreshes during editing or when no element is selected.
     if (this.isEditing || !this._elementStyle) {
-      return promise.resolve(null);
+      return;
     }
 
     this._clearRules();
 
     // Repopulate the element style.
-    return this._populate();
+    this._populate();
   },
 
   _populate: function() {
     let elementStyle = this._elementStyle;
     return this._elementStyle.populate().then(() => {
       if (this._elementStyle != elementStyle) {
         return promise.reject("element changed");
       }
--- a/browser/devtools/styleinspector/test/browser_ruleview_update.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_update.js
@@ -2,16 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let doc;
 
 let inspector;
 let ruleView;
 let testElement;
+let rule;
 
 function startTest(aInspector, aRuleView)
 {
   inspector = aInspector;
   ruleView = aRuleView;
   let style = '' +
     '#testid {' +
     '  background-color: blue;' +
@@ -24,123 +25,146 @@ function startTest(aInspector, aRuleView
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
 
   testElement = doc.getElementById("testid");
 
   let elementStyle = 'margin-top: 1px; padding-top: 5px;'
   testElement.setAttribute("style", elementStyle);
 
   inspector.selection.setNode(testElement);
-  inspector.once("inspector-updated", () => {
-    testRuleChanges();
-  }, true);
+  inspector.once("rule-view-refreshed", testRuleChanges);
 }
 
 function testRuleChanges()
 {
   let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
   is(selectors.length, 3, "Three rules visible.");
   is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
   is(selectors[1].textContent.indexOf("#testid"), 0, "Second item is id rule.");
   is(selectors[2].textContent.indexOf(".testclass"), 0, "Third item is class rule.");
 
   // Change the id and refresh.
+  inspector.once("rule-view-refreshed", testRuleChange1);
   testElement.setAttribute("id", "differentid");
-  promiseDone(ruleView.nodeChanged().then(() => {
-    let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
-    is(selectors.length, 2, "Two rules visible.");
-    is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
-    is(selectors[1].textContent.indexOf(".testclass"), 0, "Second item is class rule.");
+}
+
+function testRuleChange1()
+{
+  let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
+  is(selectors.length, 2, "Two rules visible.");
+  is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
+  is(selectors[1].textContent.indexOf(".testclass"), 0, "Second item is class rule.");
 
-    testElement.setAttribute("id", "testid");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    // Put the id back.
-    let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
-    is(selectors.length, 3, "Three rules visible.");
-    is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
-    is(selectors[1].textContent.indexOf("#testid"), 0, "Second item is id rule.");
-    is(selectors[2].textContent.indexOf(".testclass"), 0, "Third item is class rule.");
+  inspector.once("rule-view-refreshed", testRuleChange2);
+  testElement.setAttribute("id", "testid");
+}
+function testRuleChange2()
+{
+  let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
+  is(selectors.length, 3, "Three rules visible.");
+  is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
+  is(selectors[1].textContent.indexOf("#testid"), 0, "Second item is id rule.");
+  is(selectors[2].textContent.indexOf(".testclass"), 0, "Third item is class rule.");
 
-    testPropertyChanges();
-  }));
+  testPropertyChanges();
 }
 
 function validateTextProp(aProp, aEnabled, aName, aValue, aDesc)
 {
   is(aProp.enabled, aEnabled, aDesc + ": enabled.");
   is(aProp.name, aName, aDesc + ": name.");
   is(aProp.value, aValue, aDesc + ": value.");
 
   is(aProp.editor.enable.hasAttribute("checked"), aEnabled, aDesc + ": enabled checkbox.");
   is(aProp.editor.nameSpan.textContent, aName, aDesc + ": name span.");
   is(aProp.editor.valueSpan.textContent, aValue, aDesc + ": value span.");
 }
 
 function testPropertyChanges()
 {
+  rule = ruleView._elementStyle.rules[0];
+  let ruleEditor = ruleView._elementStyle.rules[0].editor;
+  inspector.once("rule-view-refreshed", testPropertyChange0);
+
   // Add a second margin-top value, just to make things interesting.
-  let rule = ruleView._elementStyle.rules[0];
-  let ruleEditor = ruleView._elementStyle.rules[0].editor;
   ruleEditor.addProperty("margin-top", "5px", "");
-  promiseDone(expectRuleChange(rule).then(() => {
-    // Set the element style back to a 1px margin-top.
-    testElement.setAttribute("style", "margin-top: 1px; padding-top: 5px");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
-    validateTextProp(rule.textProps[0], true, "margin-top", "1px", "First margin property re-enabled");
-    validateTextProp(rule.textProps[2], false, "margin-top", "5px", "Second margin property disabled");
+}
+
+function testPropertyChange0()
+{
+  validateTextProp(rule.textProps[0], false, "margin-top", "1px", "Original margin property active");
 
-    // Now set it back to 5px, the 5px value should be re-enabled.
-    testElement.setAttribute("style", "margin-top: 5px; padding-top: 5px;");
-    return ruleView.nodeChanged();
+  inspector.once("rule-view-refreshed", testPropertyChange1);
+  testElement.setAttribute("style", "margin-top: 1px; padding-top: 5px");
+}
+function testPropertyChange1()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
+  validateTextProp(rule.textProps[0], true, "margin-top", "1px", "First margin property re-enabled");
+  validateTextProp(rule.textProps[2], false, "margin-top", "5px", "Second margin property disabled");
+
+  inspector.once("rule-view-refreshed", testPropertyChange2);
 
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
-    validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
-    validateTextProp(rule.textProps[2], true, "margin-top", "5px", "Second margin property disabled");
+  // Now set it back to 5px, the 5px value should be re-enabled.
+  testElement.setAttribute("style", "margin-top: 5px; padding-top: 5px;");
+}
+function testPropertyChange2()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
+  validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
+  validateTextProp(rule.textProps[2], true, "margin-top", "5px", "Second margin property disabled");
+
+  inspector.once("rule-view-refreshed", testPropertyChange3);
 
-    // Set the margin property to a value that doesn't exist in the editor.
-    // Should reuse the currently-enabled element (the second one.)
-    testElement.setAttribute("style", "margin-top: 15px; padding-top: 5px;");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
-    validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
-    validateTextProp(rule.textProps[2], true, "margin-top", "15px", "Second margin property disabled");
+  // Set the margin property to a value that doesn't exist in the editor.
+  // Should reuse the currently-enabled element (the second one.)
+  testElement.setAttribute("style", "margin-top: 15px; padding-top: 5px;");
+}
+function testPropertyChange3()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
+  validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
+  validateTextProp(rule.textProps[2], true, "margin-top", "15px", "Second margin property disabled");
 
-    // Remove the padding-top attribute.  Should disable the padding property but not remove it.
-    testElement.setAttribute("style", "margin-top: 5px;");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
-    validateTextProp(rule.textProps[1], false, "padding-top", "5px", "Padding property disabled");
+  inspector.once("rule-view-refreshed", testPropertyChange4);
+
+  // Remove the padding-top attribute.  Should disable the padding property but not remove it.
+  testElement.setAttribute("style", "margin-top: 5px;");
+}
+function testPropertyChange4()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
+  validateTextProp(rule.textProps[1], false, "padding-top", "5px", "Padding property disabled");
 
-    // Put the padding-top attribute back in, should re-enable the padding property.
-    testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
-    validateTextProp(rule.textProps[1], true, "padding-top", "25px", "Padding property enabled");
+  inspector.once("rule-view-refreshed", testPropertyChange5);
+
+  // Put the padding-top attribute back in, should re-enable the padding property.
+  testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px");
+}
+function testPropertyChange5()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
+  validateTextProp(rule.textProps[1], true, "padding-top", "25px", "Padding property enabled");
 
-    // Add an entirely new property.
-    testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px; padding-left: 20px;");
-    return ruleView.nodeChanged();
-  }).then(() => {
-    is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4, "Added a property");
-    validateTextProp(rule.textProps[3], true, "padding-left", "20px", "Padding property enabled");
+  inspector.once("rule-view-refreshed", testPropertyChange6);
 
-    finishTest();
-  }));
+  // Add an entirely new property
+  testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px; padding-left: 20px;");
+}
+function testPropertyChange6()
+{
+  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4, "Added a property");
+  validateTextProp(rule.textProps[3], true, "padding-left", "20px", "Padding property enabled");
+
+  finishTest();
 }
 
 function finishTest()
 {
-  inspector = ruleView = null;
+  inspector = ruleView = rule = null;
   doc = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
@@ -68,16 +68,17 @@
 <!ENTITY projects.appDetails "App Details">
 <!ENTITY projects.removeAppFromList "Remove this app from the list of apps you are working on. This will not remove it from a device or a simulator.">
 <!ENTITY projects.updateApp "Update">
 <!ENTITY projects.updateAppTooltip "Execute validation checks and update the app to the connected device">
 <!ENTITY projects.debugApp "Debug">
 <!ENTITY projects.debugAppTooltip "Open Developer Tools connected to this app">
 <!ENTITY projects.hostedManifestPlaceHolder2 "http://example.com/app/manifest.webapp">
 <!ENTITY projects.noProjects "No projects. Add a new packaged app below (local directory) or a hosted app (link to a manifest file).">
+<!ENTITY projects.manifestEditor "Manifest Editor">
 
 <!ENTITY help.title "App Manager">
 <!ENTITY help.close "Close">
 <!ENTITY help.intro "This tool will help you build and install web apps on compatible devices (i.e. Firefox OS). The <strong>Apps</strong> tab will assist you in the validation and installation process of your app. The <strong>Device</strong> tab will give you information about the connected device. Use the bottom toolbar to connect to a device or start the simulator.">
 <!ENTITY help.usefullLinks "Useful links:">
 <!ENTITY help.appMgrDoc "Documentation: Using the App Manager">
 <!ENTITY help.configuringDevice "How to setup your Firefox OS device">
 <!ENTITY help.troubleShooting "Troubleshooting">
--- a/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
@@ -14,9 +14,9 @@
 <!ENTITY connect    "Connect">
 <!ENTITY connecting "Connecting…">
 <!ENTITY availableTabs "Available remote tabs:">
 <!ENTITY availableProcesses "Available remote processes:">
 <!ENTITY connectionError "Error:">
 <!ENTITY errorTimeout "Error: connection timeout.">
 <!ENTITY errorRefused "Error: connection refused.">
 <!ENTITY errorUnexpected "Unexpected error.">
-<!ENTITY help "Firefox Developer Tools can debug remote devices (Firefox for Android and Firefox OS, for example). Make sure that you have turned on the 'Remote debugging' option in the remote device. See the <a target='_' href='https://developer.mozilla.org/docs/Tools/Debugger#Remote_Debugging'>documentation</a> for more.">
+<!ENTITY help2 "Firefox Developer Tools can debug remote devices (Firefox for Android and Firefox OS, for example). Make sure that you have turned on the 'Remote debugging' option in the remote device. See the <a target='_' href='https://developer.mozilla.org/docs/Tools/Remote_Debugging'>documentation</a> for more.">
--- a/browser/metro/base/content/bindings/browser.js
+++ b/browser/metro/base/content/bindings/browser.js
@@ -543,16 +543,17 @@ let DOMEvents =  {
         break;
     }
   }
 };
 
 DOMEvents.init();
 
 let ContentScroll =  {
+  // The most recent offset set by APZC for the root scroll frame
   _scrollOffset: { x: 0, y: 0 },
 
   init: function() {
     addMessageListener("Content:SetCacheViewport", this);
     addMessageListener("Content:SetWindowSize", this);
 
     if (Services.prefs.getBoolPref("layers.async-pan-zoom.enabled")) {
       addEventListener("scroll", this, false);
@@ -612,19 +613,23 @@ let ContentScroll =  {
           break;
 
         let binding = element.ownerDocument.getBindingParent(element);
         if (binding instanceof Ci.nsIDOMHTMLInputElement && binding.mozIsTextField(false))
           break;
 
         // Set the scroll offset for this element if specified
         if (json.scrollX >= 0 || json.scrollY >= 0) {
-          this.setScrollOffsetForElement(element, json.scrollX, json.scrollY)
-          if (json.id == 1)
+          this.setScrollOffsetForElement(element, json.scrollX, json.scrollY);
+          if (element == content.document.documentElement) {
+            // scrollTo can make some small adjustments to the offset before
+            // actually scrolling the document.  To ensure that _scrollOffset
+            // actually matches the offset stored in the window, re-query it.
             this._scrollOffset = this.getScrollOffset(content);
+          }
         }
 
         // Set displayport. We want to set this after setting the scroll offset, because
         // it is calculated based on the scroll offset.
         let scrollOffset = this.getScrollOffsetForElement(element);
         let x = displayport.x - scrollOffset.x;
         let y = displayport.y - scrollOffset.y;
 
@@ -685,16 +690,19 @@ let ContentScroll =  {
     let isRoot = false;
     if (target instanceof Ci.nsIDOMDocument) {
       var window = target.defaultView;
       var scrollOffset = this.getScrollOffset(window);
       var element = target.documentElement;
 
       if (target == content.document) {
         if (this._scrollOffset.x == scrollOffset.x && this._scrollOffset.y == scrollOffset.y) {
+          // Don't send a scroll message back to APZC if it's the same as the
+          // last one set by APZC.  We use this to avoid sending APZC back an
+          // event that it originally triggered.
           return;
         }
         this._scrollOffset = scrollOffset;
         isRoot = true;
       }
     } else {
       var window = target.currentDoc.defaultView;
       var scrollOffset = this.getScrollOffsetForElement(target);
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -61,16 +61,20 @@
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
   color: #888;
 }
 
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
   display: none;
 }
 
+#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow {
+  background-image: none;
+}
+
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
   background: url(background-noise-toolbar.png) rgb(61,69,76);
   /* Prevent the container deck from aquiring the height from this message. */
   min-height: 1px;
   padding: 25vh 0;
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -59,16 +59,20 @@
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
   color: #888;
 }
 
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
   display: none;
 }
 
+#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow {
+  background-image: none;
+}
+
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
   background: url(background-noise-toolbar.png) rgb(61,69,76);
   /* Prevent the container deck from aquiring the height from this message. */
   min-height: 1px;
   padding: 25vh 0;
--- a/browser/themes/shared/devtools/app-manager/projects.css
+++ b/browser/themes/shared/devtools/app-manager/projects.css
@@ -226,27 +226,33 @@ strong {
   background-repeat: no-repeat, repeat;
   background-size: 35%, auto;
   background-position: center center, top left;
 }
 
 #lense > div {
   display: flex;
   flex-grow: 1;
+  flex-direction: column;
 }
 
 
 /********* PROJECT ***********/
 
 
 .project-details {
   background-color: rgb(225, 225, 225);
   padding: 10px;
+  line-height: 160%;
+  display: flex;
+  flex-direction: column;
+}
+
+.project-metadata {
   flex-grow: 1;
-  line-height: 160%;
 }
 
 .project-status {
   display: flex;
 }
 
 .project-title {
   flex-direction: row;
@@ -433,8 +439,49 @@ strong {
 }
 
 .project-item-warnings > span,
 .project-item-errors > span {
   font-size: 11px;
   padding-left: 16px;
   font-weight: bold;
 }
+
+
+/********* MANIFEST EDITOR ***********/
+
+.manifest-editor {
+  display: flex;
+  flex-direction: column;
+  flex-grow: 1;
+  background-color: #E1E1E1;
+}
+
+.manifest-editor > h2 {
+  font-size: 18px;
+  margin: 1em 30px;
+}
+
+.variables-view {
+  flex-grow: 1;
+  border: 0;
+  border-top: 5px solid #C9C9C9;
+}
+
+/* Bug 925921: Remove when the manifest editor is always on */
+
+.manifest-editor {
+  display: none;
+}
+
+.project-details {
+  flex-grow: 1;
+}
+
+#lense[manifest-editable] .manifest-editor {
+  display: flex;
+}
+
+#lense[manifest-editable] .project-details {
+  flex-grow: 0;
+}
+
+/* End blocks to remove */
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -59,16 +59,20 @@
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
   color: #888;
 }
 
 #sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
   display: none;
 }
 
+#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow {
+  background-image: none;
+}
+
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
   background: url(background-noise-toolbar.png) rgb(61,69,76);
   /* Prevent the container deck from aquiring the height from this message. */
   min-height: 1px;
   padding: 25vh 0;
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -72,17 +72,17 @@ class nsGeolocationRequest
                        const GeoPositionCallback& aCallback,
                        const GeoPositionErrorCallback& aErrorCallback,
                        PositionOptions* aOptions,
                        bool aWatchPositionRequest = false,
                        int32_t aWatchId = 0);
   void Shutdown();
 
   void SendLocation(nsIDOMGeoPosition* location);
-  bool WantsHighAccuracy() {return mOptions && mOptions->mEnableHighAccuracy;}
+  bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
   void SetTimeoutTimer();
   nsIPrincipal* GetPrincipal();
 
   ~nsGeolocationRequest();
 
   virtual bool Recv__delete__(const bool& allow) MOZ_OVERRIDE;
   virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
 
@@ -440,17 +440,17 @@ nsGeolocationRequest::Allow()
   // if the user has specified a maximumAge, return a cached value.
 
   uint32_t maximumAge = 0;
   if (mOptions) {
     if (mOptions->mMaximumAge > 0) {
       maximumAge = mOptions->mMaximumAge;
     }
   }
-  gs->SetHigherAccuracy(mOptions && mOptions->mEnableHighAccuracy);
+  gs->UpdateAccuracy(WantsHighAccuracy());
 
   bool canUseCache = lastPosition && maximumAge > 0 &&
     (PRTime(PR_Now() / PR_USEC_PER_MSEC) - maximumAge <=
     PRTime(cachedPositionTime));
 
   if (canUseCache) {
     // okay, we can return a cached position
     // getCurrentPosition requests serviced by the cache
@@ -580,22 +580,22 @@ nsGeolocationRequest::Shutdown()
   MOZ_ASSERT(!mShutdown, "request shutdown twice");
   mShutdown = true;
 
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
     mTimeoutTimer = nullptr;
   }
 
-  // This should happen last, to ensure that this request isn't taken into consideration
-  // when deciding whether existing requests still require high accuracy.
+  // If there are no other high accuracy requests, the geolocation service will
+  // notify the provider to switch to the default accuracy.
   if (mOptions && mOptions->mEnableHighAccuracy) {
     nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
     if (gs) {
-      gs->SetHigherAccuracy(false);
+      gs->UpdateAccuracy();
     }
   }
 }
 
 bool nsGeolocationRequest::Recv__delete__(const bool& allow)
 {
   if (allow) {
     (void) Allow();
@@ -896,19 +896,19 @@ nsGeolocationService::HighAccuracyReques
     if (mGeolocators[i]->HighAccuracyRequested()) {
       return true;
     }
   }
   return false;
 }
 
 void
-nsGeolocationService::SetHigherAccuracy(bool aEnable)
+nsGeolocationService::UpdateAccuracy(bool aForceHigh)
 {
-  bool highRequired = aEnable || HighAccuracyRequested();
+  bool highRequired = aForceHigh || HighAccuracyRequested();
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     ContentChild* cpc = ContentChild::GetSingleton();
     cpc->SendSetGeolocationHigherAccuracy(highRequired);
     return;
   }
 
   if (!mHigherAccuracy && highRequired) {
@@ -1056,16 +1056,17 @@ void
 Geolocation::Shutdown()
 {
   // Release all callbacks
   mPendingCallbacks.Clear();
   mWatchingCallbacks.Clear();
 
   if (mService) {
     mService->RemoveLocator(this);
+    mService->UpdateAccuracy();
   }
 
   mService = nullptr;
   mPrincipal = nullptr;
 }
 
 nsIDOMWindow*
 Geolocation::GetParentObject() const {
--- a/dom/src/geolocation/nsGeolocation.h
+++ b/dom/src/geolocation/nsGeolocation.h
@@ -80,18 +80,18 @@ public:
   nsresult StartDevice(nsIPrincipal* aPrincipal);
 
   // Stop the started geolocation device (gps, nmea, etc.)
   void     StopDevice();
 
   // create, or reinitalize the callback timer
   void     SetDisconnectTimer();
 
-  // request higher accuracy, if possible
-  void     SetHigherAccuracy(bool aEnable);
+  // Update the accuracy and notify the provider if changed
+  void     UpdateAccuracy(bool aForceHigh = false);
   bool     HighAccuracyRequested();
 
 private:
 
   ~nsGeolocationService();
 
   // Disconnect timer.  When this timer expires, it clears all pending callbacks
   // and closes down the provider, unless we are watching a point, and in that
--- a/dom/system/NetworkGeolocationProvider.js
+++ b/dom/system/NetworkGeolocationProvider.js
@@ -68,16 +68,19 @@ function WifiGeoPositionProvider() {
   try {
     gUseScanning = Services.prefs.getBoolPref("geo.wifi.scan");
   } catch (e) {}
 
   this.wifiService = null;
   this.timer = null;
   this.hasSeenWiFi = false;
   this.started = false;
+  // this is only used when logging is enabled, to debug interactions with the
+  // geolocation service
+  this.highAccuracy = false;
 }
 
 WifiGeoPositionProvider.prototype = {
   classID:          Components.ID("{77DA64D3-7458-4920-9491-86CC9914F904}"),
   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIGeolocationProvider,
                                            Ci.nsIWifiListener,
                                            Ci.nsITimerCallback]),
   startup:  function() {
@@ -127,20 +130,22 @@ WifiGeoPositionProvider.prototype = {
       this.timer.cancel();
       this.timer = null;
     }
 
     this.started = false;
   },
 
   setHighAccuracy: function(enable) {
+    this.highAccuracy = enable;
+    LOG("setting highAccuracy to " + (this.highAccuracy?"TRUE":"FALSE"));
   },
 
   onChange: function(accessPoints) {
-    LOG("onChange called");
+    LOG("onChange called, highAccuracy = " + (this.highAccuracy?"TRUE":"FALSE"));
     this.hasSeenWiFi = true;
 
     let url = Services.urlFormatter.formatURLPref("geo.wifi.uri");
 
     function isPublic(ap) {
         let mask = "_nomap"
         let result = ap.ssid.indexOf(mask, ap.ssid.length - mask.length) == -1;
         if (result != -1) {
new file mode 100644
--- /dev/null
+++ b/dom/tests/unit/test_geolocation_reset_accuracy.js
@@ -0,0 +1,104 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const providerCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
+const providerContract = "@mozilla.org/geolocation/provider;1";
+
+const categoryName = "geolocation-provider";
+
+var provider = {
+  QueryInterface: function eventsink_qi(iid) {
+    if (iid.equals(Components.interfaces.nsISupports) ||
+        iid.equals(Components.interfaces.nsIFactory) ||
+        iid.equals(Components.interfaces.nsIGeolocationProvider))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+  createInstance: function eventsink_ci(outer, iid) {
+    if (outer)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this.QueryInterface(iid);
+  },
+  lockFactory: function eventsink_lockf(lock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  startup: function() {
+  },
+  watch: function() {
+  },
+  shutdown: function() {
+  },
+  setHighAccuracy: function(enable) {
+    this._isHigh = enable;
+    if (enable) {
+      this._seenHigh = true;
+    }
+  },
+  _isHigh: false,
+  _seenHigh: false
+};
+
+let runningInParent = true;
+try {
+  runningInParent = Components.classes["@mozilla.org/xre/runtime;1"].
+                    getService(Components.interfaces.nsIXULRuntime).processType
+                    == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+catch (e) { }
+
+function successCallback()
+{
+  do_check_true(false);
+  do_test_finished();
+}
+
+function errorCallback()
+{
+  do_check_true(false);
+  do_test_finished();
+}
+
+function run_test()
+{
+  if (runningInParent) {
+    // XPCShell does not get a profile by default. The geolocation service
+    // depends on the settings service which uses IndexedDB and IndexedDB
+    // needs a place where it can store databases.
+    do_get_profile();
+
+    Components.manager.nsIComponentRegistrar.registerFactory(providerCID,
+      "Unit test geo provider", providerContract, provider);
+    var catMan = Components.classes["@mozilla.org/categorymanager;1"]
+                           .getService(Components.interfaces.nsICategoryManager);
+    catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
+                                               providerContract, false, true);
+
+    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+    prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
+    prefs.setBoolPref("geo.wifi.scan", false);
+  }
+
+  let geolocation = Cc["@mozilla.org/geolocation;1"].createInstance(Ci.nsISupports);
+
+  do_test_pending();
+
+  let watchID1 = geolocation.watchPosition(successCallback, errorCallback);
+  let watchID2 = geolocation.watchPosition(successCallback, errorCallback,
+                                           {enableHighAccuracy: true});
+
+  do_timeout(1000, function() {
+    geolocation.clearWatch(watchID2);
+    do_timeout(1000, check_results);
+  });
+}
+
+function check_results()
+{
+  if (runningInParent) {
+    // check the provider was set to high accuracy during the test
+    do_check_true(provider._seenHigh);
+    // check the provider is not currently set to high accuracy
+    do_check_false(provider._isHigh);
+  }
+  do_test_finished();
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/unit/test_geolocation_reset_accuracy_wrap.js
@@ -0,0 +1,70 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const providerCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
+const providerContract = "@mozilla.org/geolocation/provider;1";
+
+const categoryName = "geolocation-provider";
+
+var provider = {
+  QueryInterface: function eventsink_qi(iid) {
+    if (iid.equals(Components.interfaces.nsISupports) ||
+        iid.equals(Components.interfaces.nsIFactory) ||
+        iid.equals(Components.interfaces.nsIGeolocationProvider))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+  createInstance: function eventsink_ci(outer, iid) {
+    if (outer)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this.QueryInterface(iid);
+  },
+  lockFactory: function eventsink_lockf(lock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  startup: function() {
+  },
+  watch: function() {
+  },
+  shutdown: function() {
+  },
+  setHighAccuracy: function(enable) {
+    this._isHigh = enable;
+    if (enable) {
+      this._seenHigh = true;
+    }
+  },
+  _isHigh: false,
+  _seenHigh: false
+};
+
+function run_test()
+{
+  // XPCShell does not get a profile by default. The geolocation service
+  // depends on the settings service which uses IndexedDB and IndexedDB
+  // needs a place where it can store databases.
+  do_get_profile();
+
+  Components.manager.nsIComponentRegistrar.registerFactory(providerCID,
+    "Unit test geo provider", providerContract, provider);
+  var catMan = Components.classes["@mozilla.org/categorymanager;1"]
+                         .getService(Components.interfaces.nsICategoryManager);
+  catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
+                                             providerContract, false, true);
+
+  var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+  prefs.setBoolPref("geo.testing.ignore_ipc_principal", true);
+  prefs.setBoolPref("geo.wifi.scan", false);
+
+  run_test_in_child("test_geolocation_reset_accuracy.js", check_results);
+}
+
+function check_results()
+{
+  // check the provider was set to high accuracy during the test
+  do_check_true(provider._seenHigh);
+  // check the provider is not currently set to high accuracy
+  do_check_false(provider._isHigh);
+
+  do_test_finished();
+}
--- a/dom/tests/unit/xpcshell.ini
+++ b/dom/tests/unit/xpcshell.ini
@@ -7,8 +7,13 @@ tail =
 [test_geolocation_provider.js]
 # Bug 684962: test hangs consistently on Android
 skip-if = os == "android"
 [test_geolocation_timeout.js]
 # Bug 919946: test hangs consistently on Android
 skip-if = os == "android"
 [test_geolocation_timeout_wrap.js]
 skip-if = os == "mac" || os == "android"
+[test_geolocation_reset_accuracy.js]
+# Bug 919946: test hangs consistently on Android
+skip-if = os == "android"
+[test_geolocation_reset_accuracy_wrap.js]
+skip-if = os == "mac" || os == "android"
--- a/mobile/android/base/gfx/BitmapUtils.java
+++ b/mobile/android/base/gfx/BitmapUtils.java
@@ -22,16 +22,17 @@ import android.util.Base64;
 import android.util.Log;
 import android.text.TextUtils;
 
 import org.mozilla.gecko.R;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
+import java.lang.NoSuchFieldException;
 import java.net.MalformedURLException;
 import java.net.URL;
 
 public final class BitmapUtils {
     private static final String LOGTAG = "GeckoBitmapUtils";
 
     private BitmapUtils() {}
 
@@ -76,16 +77,29 @@ public final class BitmapUtils {
                 @Override
                 public void onPostExecute(Drawable drawable) {
                     loader.onBitmapFound(drawable);
                 }
             }).execute();
             return;
         }
 
+        if(data.startsWith("-moz-icon://")) {
+            Uri imageUri = Uri.parse(data);
+            String resource = imageUri.getSchemeSpecificPart();
+            resource = resource.substring(resource.lastIndexOf('/') + 1);
+
+            try {
+                Drawable d = context.getPackageManager().getApplicationIcon(resource);
+                loader.onBitmapFound(d);
+            } catch(Exception ex) { }
+
+            return;
+        }
+
         if(data.startsWith("drawable://")) {
             Uri imageUri = Uri.parse(data);
             int id = getResource(imageUri, R.drawable.ic_status_logo);
             Drawable d = context.getResources().getDrawable(id);
 
             loader.onBitmapFound(d);
             return;
         }
@@ -277,19 +291,42 @@ public final class BitmapUtils {
 
     public static int getResource(Uri resourceUrl, int defaultIcon) {
         int icon = defaultIcon;
 
         final String scheme = resourceUrl.getScheme();
         if ("drawable".equals(scheme)) {
             String resource = resourceUrl.getSchemeSpecificPart();
             resource = resource.substring(resource.lastIndexOf('/') + 1);
+
+            try {
+                return Integer.parseInt(resource);
+            } catch(NumberFormatException ex) {
+                // This isn't a resource id, try looking for a string
+            }
+
             try {
                 final Class<R.drawable> drawableClass = R.drawable.class;
                 final Field f = drawableClass.getField(resource);
                 icon = f.getInt(null);
-            } catch (final Exception e) {} // just means the resource doesn't exist
+            } catch (final NoSuchFieldException e1) {
+
+                // just means the resource doesn't exist for fennec. Check in Android resources
+                try {
+                    final Class<android.R.drawable> drawableClass = android.R.drawable.class;
+                    final Field f = drawableClass.getField(resource);
+                    icon = f.getInt(null);
+                } catch (final NoSuchFieldException e2) {
+                    // This drawable doesn't seem to exist...
+                } catch(Exception e3) {
+                    Log.i(LOGTAG, "Exception getting drawable", e3);
+                }
+
+            } catch (Exception e4) {
+              Log.i(LOGTAG, "Exception getting drawable", e4);
+            }
+
             resourceUrl = null;
         }
         return icon;
     }
 }
 
--- a/mobile/android/base/tests/AboutHomeTest.java.in
+++ b/mobile/android/base/tests/AboutHomeTest.java.in
@@ -15,25 +15,24 @@ import android.widget.GridView;
 import android.widget.LinearLayout;
 import android.widget.TabWidget;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * This class is an extension of BaseTest that helps with interaction with about:home
  * This class contains methods that access the different tabs from about:home, methods that get information like history and bookmarks from the database, edit and remove bookmarks and history items
  * The purpose of this class is to collect all the logically connected methods that deal with about:home
  * To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
  */
-abstract class AboutHomeTest extends BaseTest {
+abstract class AboutHomeTest extends PixelTest {
     protected enum AboutHomeTabs {HISTORY, MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST};
     private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
                   add("TOP_SITES");
                   add("BOOKMARKS");
                   add("READING_LIST");
               }};
 
 
@@ -93,20 +92,16 @@ abstract class AboutHomeTest extends Bas
         } else {
             mAsserter.ok(false, url + " is not one of the displayed bookmarks", "Please make sure the url provided is bookmarked");
         }
     }
 
     // @return the View associated with bookmark for the provided url or null if the link is not bookmarked
     protected View getDisplayedBookmark(String url) {
         openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
-        if (!mDevice.type.equals("tablet")) {
-            toggleVKB(); // dismiss the keyboard to make sure this works on small screen devices
-        }
-        getInstrumentation().waitForIdleSync();
         ListView bookmarksTabList = findListViewWithTag("bookmarks");
         waitForNonEmptyListToLoad(bookmarksTabList);
         ListAdapter adapter = bookmarksTabList.getAdapter();
         if (adapter != null) {
             for (int i = 0; i < adapter.getCount(); i++ ) {
                 // I am unable to click the view taken with getView for some reason so getting the child at i
                 bookmarksTabList.smoothScrollToPosition(i);
                 View bookmarkView = bookmarksTabList.getChildAt(i);
@@ -177,40 +172,69 @@ abstract class AboutHomeTest extends Bas
                 return listView;
             }
         }
 
         return null;
     }
 
    /**
-    * @param1 a String with the original url
-    * @param2 a String array with the new values for all fields
+    * FIXME: rewrite this to work with fig when rewriting the testBookmarksTab test
+    * This method will edit the bookmark with index = bookmarkIndex from the list of bookmarks
+    * For the field index:
+    *     fieldIndex = 1 - the Bookmark name
+    *     fieldIndex = 2 - the Bookmark url
+    *     fieldIndex = 3 - the Bookmark keyword
     */
-    protected void editBookmark(String originalUrl, String[] newValues) {
-        openBookmarkContextMenu(originalUrl);
+    protected void editBookmark(int bookmarkIndex, int fieldIndex, String addedText, ListView list) {
+
+        // Open the Edit Bookmark context menu
+        View child;
+        mSolo.clickOnText("Bookmarks");
+        child = list.getChildAt(bookmarkIndex);
+        mAsserter.ok(child != null, "edit item can be retrieved", child != null ? child.toString() : "null!");
+        waitForText("Switch to tab");
+        mSolo.clickLongOnView(child);
+        waitForText("Share");
         mSolo.clickOnText("Edit");
         waitForText("Edit Bookmark");
-        for (String value:newValues) {
-            mSolo.clearEditText(Arrays.asList(newValues).indexOf(value));
-            mSolo.clickOnEditText(Arrays.asList(newValues).indexOf(value));
-            mActions.sendKeys(value);
-        }
-        mSolo.clickOnButton("OK");
+
+        // Clear the Field
+        mSolo.clearEditText(fieldIndex);
+
+        // Enter the new text
+        mSolo.clickOnEditText(fieldIndex);
+        mActions.sendKeys(addedText);
+        mSolo.clickOnText("OK");
         waitForText("Bookmark updated");
     }
 
-    protected void checkBookmarkEdit(String bookmarkUrl, String[] values) {
-        openBookmarkContextMenu(bookmarkUrl);
+    // FIXME: rewrite this to work with fig when rewriting the testBookmarksTab test
+    protected boolean checkBookmarkEdit(int bookmarkIndex, String addedText, ListView list) {
+        // Open the Edit Bookmark context menu
+        View child;
+        mSolo.clickOnText("Bookmarks");
+        child = list.getChildAt(bookmarkIndex);
+        mAsserter.ok(child != null, "check item can be retrieved", child != null ? child.toString() : "null!");
+        waitForText("Switch to tab");
+        mSolo.clickLongOnView(child);
+        waitForText("Share");
         mSolo.clickOnText("Edit");
-        for (String value:values) {
-            mAsserter.ok(mSolo.searchText(value), "Checking that the value is correct", "The value = " + value + " is correct");
+        waitForText("Edit Bookmark");
+
+        // Check if the new text was added
+        if (mSolo.searchText(addedText)) {
+            clickOnButton("Cancel");
+            waitForText("about:home");
+            return true;
+        } else {
+            clickOnButton("Cancel");
+            waitForText("about:home");
+            return false;
         }
-        clickOnButton("Cancel");
-        waitForText("BOOKMARKS");
     }
 
     // A wait in order for the about:home tab to be rendered after drag/tab selection
     private void waitForAboutHomeTab(final int tabIndex) {
         boolean correctTab = waitForCondition(new Condition() {
             @Override
             public boolean isSatisfied() {
                 ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
@@ -251,22 +275,25 @@ abstract class AboutHomeTest extends Bas
     /**
      * This method can be used to open the different tabs of about:home.
      *
      * @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
      */
     protected void openAboutHomeTab(AboutHomeTabs tab) {
         focusUrlBar();
         ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
+        final int currentTabIndex = pager.getCurrentItem();
+        int tabOffset;
 
         // Handle tablets by just clicking the visible tab title.
         if (mDevice.type.equals("tablet")) {
-            // Just click for tablets, since all the titles are visible.
             if (AboutHomeTabs.MOST_RECENT == tab || AboutHomeTabs.TABS_FROM_LAST_TIME == tab) {
-                mSolo.clickOnText(AboutHomeTabs.HISTORY.toString());
+                tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
+                swipeAboutHome(tabOffset);
+                waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
                 TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
 
                 switch (tab) {
                     case MOST_RECENT: {
                         mSolo.clickOnView(tabwidget.getChildAt(0));
                         // We can determine if we are on the MOST_RECENT tab only if pages were first visited during the test
                         mAsserter.ok(waitForText(StringHelper.TODAY_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
                         break;
@@ -279,18 +306,17 @@ abstract class AboutHomeTest extends Bas
                 }
             } else {
                 clickAboutHomeTab(tab);
             }
             return;
         }
 
         // Handle phones (non-tablets).
-        final int currentTabIndex = pager.getCurrentItem();
-        int tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
+        tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
         switch (tab) {
             case TOP_SITES : {
                 swipeAboutHome(tabOffset);
                 waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
                 break;
             }
             case BOOKMARKS : {
                 swipeAboutHome(tabOffset);
--- a/mobile/android/base/tests/StringHelper.java.in
+++ b/mobile/android/base/tests/StringHelper.java.in
@@ -40,18 +40,16 @@ class StringHelper {
         "Bookmark Link"
     };
 
     public static final String[] BOOKMARKS_OPTIONS_CONTEXTMENU_ITEMS = new String[] {
         "Edit",
         "Add to Home Screen"
     };
 
-    public static final String[] BOOKMARK_CONTEXT_MENU_ITEMS = {"Open in New Tab", "Open in Private Tab", "Share", "Edit", "Remove", "Add to Home Screen"};
-
     // Robocop page urls
     // Note: please use getAbsoluteUrl(String url) on each robocop url to get the correct url
     public static final String ROBOCOP_BIG_LINK_URL = "/robocop/robocop_big_link.html";
     public static final String ROBOCOP_BIG_MAILTO_URL = "/robocop/robocop_big_mailto.html";
     public static final String ROBOCOP_BLANK_PAGE_01_URL = "/robocop/robocop_blank_01.html";
     public static final String ROBOCOP_BLANK_PAGE_02_URL = "/robocop/robocop_blank_02.html";
     public static final String ROBOCOP_BLANK_PAGE_03_URL = "/robocop/robocop_blank_03.html";
     public static final String ROBOCOP_BOXES_URL = "/robocop/robocop_boxes.html";
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -1,12 +1,11 @@
 [testAwesomebar]
 # [testAwesomebarSwipes] # disabled on fig - bug 880060
 [testBookmark]
-[testBookmarksPage]
 # [testBookmarklets] # see bug 915350
 # [testBookmarkKeyword] # see bug 915350
 [testBrowserSearchVisibility]
 [testJNI]
 [testLoad]
 [testNewTab]
 [testOrderedBroadcast]
 [testPrefsObserver]
@@ -41,14 +40,15 @@
 [testDistribution]
 [testFindInPage]
 [testInputUrlBar]
 [testAddSearchEngine]
 [testImportFromAndroid]
 [testMasterPassword]
 [testDeviceSearchEngine]
 [testPrivateBrowsing]
+[testReaderMode]
 
 # Used for Talos, please don't use in mochitest
 #[testPan]
 #[testCheck]
 #[testCheck2]
 #[testBrowserProviderPerf]
deleted file mode 100644
--- a/mobile/android/base/tests/testBookmarksPage.java.in
+++ /dev/null
@@ -1,223 +0,0 @@
-#filter substitution
-package @ANDROID_PACKAGE_NAME@.tests;
-
-import @ANDROID_PACKAGE_NAME@.*;
-
-import com.jayway.android.robotium.solo.Condition;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.ContentUris;
-import android.database.Cursor;
-import android.net.Uri;
-import android.view.View;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class testBookmarksPage extends AboutHomeTest {
-    private static String BOOKMARK_URL;
-    private static String DESKTOP_BOOKMARK_URL;
-
-    @Override
-    protected int getTestType() {
-        return TEST_MOCHITEST;
-    }
-
-    public void testBookmarksPage() {
-        BOOKMARK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
-        DESKTOP_BOOKMARK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
-
-        setUpDesktopBookmarks();
-        checkBookmarkList();
-        checkBookmarkContextMenu();
-    }
-
-    private void checkBookmarkList() {
-        // Check that the default bookmarks are displayed
-        for (String url:StringHelper.DEFAULT_BOOKMARKS_URLS) {
-            mAsserter.ok(isBookmarkDisplayed(url), "Checking that default bookmark: " + url + " is displayed in the bookmarks list", url + " is displayed as a bookmark");
-        }
-        mAsserter.ok(isBookmarkDisplayed(BOOKMARK_URL), "Checking that added bookmark: " + BOOKMARK_URL + " is displayed in the bookmarks list", BOOKMARK_URL + " is displayed as a bookmark");
-
-        waitForText(StringHelper.DESKTOP_FOLDER_LABEL);
-        clickOnBookmarkFolder(StringHelper.DESKTOP_FOLDER_LABEL);
-        waitForText(StringHelper.TOOLBAR_FOLDER_LABEL);
-
-        // Verify the number of folders displayed in the Desktop Bookmarks folder is correct
-        ListView desktopFolderContent = findListViewWithTag("bookmarks");
-        ListAdapter adapter = desktopFolderContent.getAdapter();
-        if (mDevice.type.equals("tablet")) { // On tablets it's 4 folders and 1 view for top padding
-            mAsserter.is(adapter.getCount(), 5, "Checking that the correct number of folders is displayed in the Desktop Bookmarks folder");
-        } else { // On phones it's just the 4 folders
-            mAsserter.is(adapter.getCount(), 4, "Checking that the correct number of folders is displayed in the Desktop Bookmarks folder");
-        }
-
-        clickOnBookmarkFolder(StringHelper.TOOLBAR_FOLDER_LABEL);
-
-        // Go up in the bookmark folder hierarchy
-        clickOnBookmarkFolder(StringHelper.TOOLBAR_FOLDER_LABEL);
-        mAsserter.ok(waitForText(StringHelper.BOOKMARKS_MENU_FOLDER_LABEL), "Going up in the folder hierarchy", "We are back in the Desktop Bookmarks folder");
-
-        clickOnBookmarkFolder(StringHelper.DESKTOP_FOLDER_LABEL);
-        mAsserter.ok(waitForText(StringHelper.DESKTOP_FOLDER_LABEL), "Going up in the folder hierarchy", "We are back in the main Bookmarks List View");
-
-        clickOnBookmarkFolder(StringHelper.DESKTOP_FOLDER_LABEL);
-        clickOnBookmarkFolder(StringHelper.TOOLBAR_FOLDER_LABEL);
-        mAsserter.ok(isBookmarkDisplayed(DESKTOP_BOOKMARK_URL), "Checking that added bookmark: " + DESKTOP_BOOKMARK_URL + " is displayed in the bookmarks list", DESKTOP_BOOKMARK_URL + " is displayed as a bookmark");
-
-        // Open the bookmark from a bookmark folder hierarchy
-        loadBookmark(DESKTOP_BOOKMARK_URL);
-        waitForText(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
-        verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
-        openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
-
-        // Check that folders don't have a context menu
-        boolean success = waitForCondition(new Condition() {
-            @Override
-            public boolean isSatisfied() {
-                View desktopFolder = getBookmarkFolderView(StringHelper.DESKTOP_FOLDER_LABEL);
-                if (desktopFolder != null) {
-                     mSolo.clickLongOnView(desktopFolder);
-                    return true;
-                }
-                return false;
-            }
-        }, MAX_WAIT_MS);
-        mAsserter.ok(success, "Trying to long click on the Desktop Bookmarks","Desktop Bookmarks folder could not be long clicked");
-        mAsserter.ok(!waitForText("Share"), "Folders do not have context menus", "The context menu was not opened");
-
-        // Even if no context menu is opened long clicking a folder still opens it. We need to close it.
-        clickOnBookmarkFolder(StringHelper.DESKTOP_FOLDER_LABEL);
-    }
-
-    private void checkBookmarkContextMenu() {
-        // Open default bookmarks in a new tab and a new private tab since the url is substituted with "Switch to tab" after opening the link
-        openBookmarkContextMenu(StringHelper.DEFAULT_BOOKMARKS_URLS[1]);
-
-        // Test that the options are all displayed
-        for (String contextMenuOption:StringHelper.BOOKMARK_CONTEXT_MENU_ITEMS) {
-            mAsserter.ok(mSolo.searchText(contextMenuOption), "Checking that the context menu option is present", contextMenuOption + " is present");
-        }
-
-        // Test that "Open in New Tab" works
-        final Element tabCount = mDriver.findElement(getActivity(), "tabs_counter");
-        final int tabCountInt = Integer.parseInt(tabCount.getText());
-        Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
-        mSolo.clickOnText(StringHelper.BOOKMARK_CONTEXT_MENU_ITEMS[0]);
-        tabEventExpecter.blockForEvent();
-        tabEventExpecter.unregisterListener();
-
-        // Test that "Open in Private Tab" works
-        openBookmarkContextMenu(StringHelper.DEFAULT_BOOKMARKS_URLS[2]);
-        tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
-        mSolo.clickOnText(StringHelper.BOOKMARK_CONTEXT_MENU_ITEMS[1]);
-        tabEventExpecter.blockForEvent();
-        tabEventExpecter.unregisterListener();
-
-        // Test that "Share" works
-        openBookmarkContextMenu(BOOKMARK_URL);
-        mSolo.clickOnText(StringHelper.BOOKMARK_CONTEXT_MENU_ITEMS[2]);
-        mAsserter.ok(waitForText("Share via"), "Checking to see if the share menu has been opened","The share menu has been opened");
-        mActions.sendSpecialKey(Actions.SpecialKey.BACK);
-        waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
-
-        // Test that "Edit" works
-        String[] editedBookmarkValues = {"New bookmark title", "www.NewBookmark.url", "newBookmarkKeyword"};
-        editBookmark(BOOKMARK_URL,editedBookmarkValues);
-        checkBookmarkEdit(editedBookmarkValues[1],editedBookmarkValues);
-
-        // Test that "Remove" works
-        openBookmarkContextMenu(editedBookmarkValues[1]);
-        mSolo.clickOnText(StringHelper.BOOKMARK_CONTEXT_MENU_ITEMS[4]);
-        waitForText("Bookmark removed");
-        mAsserter.ok(!mDatabaseHelper.isBookmark(editedBookmarkValues[1]), "Checking that the bookmark was removed", "The bookmark was removed");
-    }
-
-    private void clickOnBookmarkFolder(final String folderName) {
-        boolean success = waitForCondition(new Condition() {
-            @Override
-            public boolean isSatisfied() {
-                View bookmarksFolder = getBookmarkFolderView(folderName);
-                if (bookmarksFolder != null) {
-                    mSolo.clickOnView(bookmarksFolder);
-                    return true;
-                }
-                return false;
-            }
-        }, MAX_WAIT_MS);
-        mAsserter.ok(success, "Trying to click on the " + folderName + " folder","The " + folderName + " folder was clicked");
-    }
-
-    private View getBookmarkFolderView(String folderName) {
-        ListView bookmarksTabList = findListViewWithTag("bookmarks");
-        ListAdapter adapter = bookmarksTabList.getAdapter();
-        if (adapter != null) {
-            for (int i = 0; i < adapter.getCount(); i++ ) {
-                View bookmarkView = bookmarksTabList.getChildAt(i);
-                if (bookmarkView instanceof TextView) {
-                    TextView folderTextView = (TextView) bookmarkView;
-                    if (folderTextView.getText().equals(folderName)) {
-                        return bookmarkView;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    // Add a bookmark in the Desktop folder so we can check the folder navigation in the bookmarks page
-    private void setUpDesktopBookmarks() {
-        // Get the folder id of the StringHelper.DESKTOP_FOLDER_LABEL folder
-        Long desktopFolderId = mDatabaseHelper.getFolderIdFromGuid("toolbar");
-
-        // Generate a Guid for the bookmark
-        String generatedGuid = null;
-        try {
-            ClassLoader classLoader = getActivity().getClassLoader();
-            Class syncUtilityClass = classLoader.loadClass("org.mozilla.gecko.sync.Utils");
-            Method generateGuid = syncUtilityClass.getMethod("generateGuid", (Class[]) null);
-            generatedGuid = (String)generateGuid.invoke(null);
-        } catch (Exception e) {
-            mAsserter.dumpLog("Exception in setUpDesktopBookmarks" + e);
-        }
-        mAsserter.ok((generatedGuid != null), "Generating a random Guid for the bookmark", "We could not generate a Guid for the bookmark");
-
-        // Insert the bookmark
-        ContentResolver resolver = getActivity().getContentResolver();
-        Uri bookmarksUri = mDatabaseHelper.buildUri(DatabaseHelper.BrowserDataType.BOOKMARKS);
-        ContentValues values = new ContentValues();
-        values.put("title", StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
-        values.put("url", DESKTOP_BOOKMARK_URL);
-        values.put("parent", desktopFolderId);
-        long now = System.currentTimeMillis();
-        values.put("modified", now);
-        values.put("type", 1);
-        values.put("guid", generatedGuid);
-        values.put("position", 10);
-        values.put("created", now);
-        int updated = resolver.update(bookmarksUri,
-                                      values,
-                                      "url = ?",
-                                      new String[] { DESKTOP_BOOKMARK_URL });
-        if (updated == 0) {
-            Uri uri = resolver.insert(bookmarksUri, values);
-            mAsserter.ok(true, "Inserted at: ", uri.toString());
-        } else {
-            mAsserter.ok(false, "Failed to insert the Desktop bookmark", "Something went wrong");
-        }
-
-        // Add a mobile bookmark
-        mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, BOOKMARK_URL);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mDatabaseHelper.deleteBookmark(DESKTOP_BOOKMARK_URL);
-        super.tearDown();
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testReaderMode.java.in
@@ -0,0 +1,170 @@
+#filter substitution
+package @ANDROID_PACKAGE_NAME@.tests;
+
+import @ANDROID_PACKAGE_NAME@.*;
+import com.jayway.android.robotium.solo.Solo;
+import android.widget.ListView;
+import android.view.View;
+import java.util.ArrayList;
+import android.view.ViewGroup;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * This patch tests the Reader Mode feature by adding and removing items in reading list
+ * checks the reader toolbar functionality(share, add/remove to reading list, go to reading list)
+ * accessing a page from reading list menu, checks that the reader icon is associated in History tab
+ * and that the reading list is properly populated after adding or removing reader items
+ */
+public class testReaderMode extends AboutHomeTest {
+    int height,width;
+
+    @Override
+    protected int getTestType() {
+        return TEST_MOCHITEST;
+    }
+    public void testReaderMode() {
+        blockForGeckoReady();
+
+        Actions.EventExpecter contentEventExpecter;
+        Actions.EventExpecter contentReaderAddedExpecter;
+        Actions.EventExpecter faviconExpecter;
+        ListView list;
+        View child;
+        String textUrl = getAbsoluteUrl(StringHelper.ROBOCOP_TEXT_PAGE_URL);
+        String devType = mDevice.type;
+        int childNo;
+
+        contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
+        loadAndPaint(textUrl);
+        contentEventExpecter.blockForEvent();
+        contentEventExpecter.unregisterListener();
+        View readerIcon = getReaderIcon();
+
+        // Add the page to the Reading List using log click on the reader icon
+        contentReaderAddedExpecter = mActions.expectGeckoEvent("Reader:Added");
+        mSolo.clickLongOnView(readerIcon);
+        String eventData = contentReaderAddedExpecter.blockForEventData();
+        isAdded(eventData);
+        contentReaderAddedExpecter.unregisterListener();
+
+        // Try to add the page to the Reading List using log click on the reader icon a second time
+        contentReaderAddedExpecter = mActions.expectGeckoEvent("Reader:Added");
+        mSolo.clickLongOnView(readerIcon);
+        eventData = contentReaderAddedExpecter.blockForEventData();
+        isAdded(eventData);
+        contentReaderAddedExpecter.unregisterListener();
+
+        // Waiting for the favicon since is the last element loaded usually
+        faviconExpecter = mActions.expectGeckoEvent("Reader:FaviconRequest");
+        mSolo.clickOnView(getReaderIcon());
+
+        // Changing devices orientation to be sure that all devices are in portrait when will access the reader toolbar
+        mSolo.setActivityOrientation(Solo.PORTRAIT);
+        faviconExpecter.blockForEvent();
+        faviconExpecter.unregisterListener();
+        verifyPageTitle("Robocop Text Page");
+
+        // Open the share menu for the reader toolbar
+        height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
+        width = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() - 10;
+        mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
+        mSolo.clickOnScreen(width,height);
+        mAsserter.ok(mSolo.waitForText("Share via"), "Waiting for the share menu", "The share menu is present");
+        mActions.sendSpecialKey(Actions.SpecialKey.BACK); // Close the share menu
+
+        // Remove page from the Reading List using reader toolbar
+        height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
+        width = mDriver.getGeckoLeft() + 50;
+        mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
+        mSolo.clickOnScreen(width,height);
+        mAsserter.ok(mSolo.waitForText("Page removed from your Reading List"), "Waiting for the page to removed from your Reading List", "The page is removed from your Reading List");
+
+        //Add page to the Reading List using reader toolbar
+        mSolo.clickOnScreen(width,height);
+        mAsserter.ok(mSolo.waitForText("Page added to your Reading List"), "Waiting for the page to be added to your Reading List", "The page was added to your Reading List");
+
+        // Open the Reading List menu for the toolbar
+        height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
+        width = mDriver.getGeckoLeft() + mDriver.getGeckoWidth()/2 - 10;
+        mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
+        contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
+        mSolo.clickOnScreen(width,height);
+        contentEventExpecter.blockForEvent();
+        contentEventExpecter.unregisterListener();
+
+        // Check if the page is present in the Reading List
+        mAsserter.ok(mSolo.waitForText("Robocop Text Page"), "Verify if the page is added to your Reading List", "The page is present in your Reading List");
+
+        // Check if the page is added in History tab like a Reading List item
+        openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
+        list = findListViewWithTag("most_recent");
+        child = list.getChildAt(1);
+        mAsserter.ok(child != null, "item can be retrieved", child != null ? child.toString() : "null!");
+        mSolo.clickLongOnView(child);
+        mAsserter.ok(mSolo.waitForText("Open in Reader"), "Verify if the page is present in history as a Reading List item", "The page is present in history as a Reading List item");
+        mActions.sendSpecialKey(Actions.SpecialKey.BACK); // Dismiss the context menu
+        mSolo.waitForText("Robocop Text Page");
+
+        // Verify separately the Reading List entries for tablets and phone because for tablets there is an extra child in UI design
+        if (devType.equals("phone")) {
+            childNo = 1;
+        }
+        else {
+            childNo = 2;
+        }
+        // Verify if the page is present to your Reading List
+        openAboutHomeTab(AboutHomeTabs.READING_LIST);
+        list = findListViewWithTag("reading_list");
+        child = list.getChildAt(childNo-1);
+        mAsserter.ok(child != null, "Verify if the page is present to your Reading List", "The page is present in your Reading List");
+        contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
+        mSolo.clickOnView(child);
+        contentEventExpecter.blockForEvent();
+        contentEventExpecter.unregisterListener();
+        verifyPageTitle("Robocop Text Page");
+
+        // Verify that we are in reader mode and remove the page from Reading List
+        height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
+        width = mDriver.getGeckoLeft() + 50;
+        mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
+        mSolo.clickOnScreen(width,height);
+        mAsserter.ok(mSolo.waitForText("Page removed from your Reading List"), "Waiting for the page to removed from your Reading List", "The page is removed from your Reading List");
+        verifyPageTitle("Robocop Text Page");
+
+        //Check if the Reading List is empty
+        openAboutHomeTab(AboutHomeTabs.READING_LIST);
+        list = findListViewWithTag("reading_list");
+        child = list.getChildAt(childNo-1);
+        mAsserter.ok(child == null, "Verify if the Reading List is empty", "The Reading List is empty");
+    }
+
+    // Get the reader icon method
+    protected View getReaderIcon() {
+        View pageActionLayout = mSolo.getView(0x7f070025);
+        ArrayList<String> pageActionLayoutChilds = new ArrayList();
+        View actionLayoutItem = pageActionLayout;
+        ViewGroup actionLayoutEntry = (ViewGroup)actionLayoutItem;
+        View icon = actionLayoutEntry.getChildAt(1);
+        return icon;
+    }
+
+    // This method check to see if a reader item is added to the reader list
+    private boolean isAdded(String eventData) {
+        try {
+            JSONObject data = new JSONObject(eventData);
+                if (data.getInt("result") == 0) {
+                    mAsserter.ok(true, "Waiting for the page to be added to your Reading List", "The page was added to your Reading List");
+                }
+                else {
+                    if (data.getInt("result") == 2) {
+                        mAsserter.ok(true, "Trying to add a second time the page in your Reading List", "The page is already in your Reading List");
+                    }
+                }
+        } catch (JSONException e) {
+            mAsserter.ok(false, "Error parsing the event data", e.toString());
+            return false;
+        }
+        return true;
+    }
+}
--- a/services/common/Makefile.in
+++ b/services/common/Makefile.in
@@ -1,14 +1,13 @@
 # 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/.
 
 modules := \
-  log4moz.js \
   storageservice.js \
   stringbundle.js \
   tokenserverclient.js \
   utils.js \
   $(NULL)
 
 pp_modules := \
   async.js \
--- a/services/common/bagheeraclient.js
+++ b/services/common/bagheeraclient.js
@@ -19,17 +19,17 @@ this.EXPORTED_SYMBOLS = [
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 #endif
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 
 
 /**
  * Represents the result of a Bagheera request.
  */
 this.BagheeraClientRequestResult = function BagheeraClientRequestResult() {
@@ -42,18 +42,18 @@ Object.freeze(BagheeraClientRequestResul
 
 
 /**
  * Wrapper around RESTRequest so logging is sane.
  */
 function BagheeraRequest(uri) {
   RESTRequest.call(this, uri);
 
-  this._log = Log4Moz.repository.getLogger("Services.BagheeraClient");
-  this._log.level = Log4Moz.Level.Debug;
+  this._log = Log.repository.getLogger("Services.BagheeraClient");
+  this._log.level = Log.Level.Debug;
 }
 
 BagheeraRequest.prototype = Object.freeze({
   __proto__: RESTRequest.prototype,
 });
 
 
 /**
@@ -64,18 +64,18 @@ BagheeraRequest.prototype = Object.freez
  * @param baseURI
  *        (string) The base URI of the Bagheera HTTP endpoint.
  */
 this.BagheeraClient = function BagheeraClient(baseURI) {
   if (!baseURI) {
     throw new Error("baseURI argument must be defined.");
   }
 
-  this._log = Log4Moz.repository.getLogger("Services.BagheeraClient");
-  this._log.level = Log4Moz.Level.Debug;
+  this._log = Log.repository.getLogger("Services.BagheeraClient");
+  this._log.level = Log.Level.Debug;
 
   this.baseURI = baseURI;
 
   if (!baseURI.endsWith("/")) {
     this.baseURI += "/";
   }
 };
 
--- a/services/common/modules-testing/bagheeraserver.js
+++ b/services/common/modules-testing/bagheeraserver.js
@@ -3,32 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 this.EXPORTED_SYMBOLS = ["BagheeraServer"];
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://testing-common/httpd.js");
 
 
 /**
  * This is an implementation of the Bagheera server.
  *
  * The purpose of the server is to facilitate testing of the Bagheera
  * client and the Firefox Health report. It is *not* meant to be a
  * production grade server.
  *
  * The Bagheera server is essentially a glorified document store.
  */
 this.BagheeraServer = function BagheeraServer() {
-  this._log = Log4Moz.repository.getLogger("metrics.BagheeraServer");
+  this._log = Log.repository.getLogger("metrics.BagheeraServer");
 
   this.server = new HttpServer();
   this.namespaces = {};
 
   this.allowAllNamespaces = false;
 }
 
 BagheeraServer.prototype = {
--- a/services/common/modules-testing/logging.js
+++ b/services/common/modules-testing/logging.js
@@ -6,49 +6,49 @@
 
 this.EXPORTED_SYMBOLS = [
   "getTestLogger",
   "initTestLogging",
 ];
 
 const {utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 this.initTestLogging = function initTestLogging(level) {
   function LogStats() {
     this.errorsLogged = 0;
   }
   LogStats.prototype = {
     format: function format(message) {
-      if (message.level == Log4Moz.Level.Error) {
+      if (message.level == Log.Level.Error) {
         this.errorsLogged += 1;
       }
 
       return message.loggerName + "\t" + message.levelDesc + "\t" +
         message.message + "\n";
     }
   };
-  LogStats.prototype.__proto__ = new Log4Moz.Formatter();
+  LogStats.prototype.__proto__ = new Log.Formatter();
 
-  let log = Log4Moz.repository.rootLogger;
+  let log = Log.repository.rootLogger;
   let logStats = new LogStats();
-  let appender = new Log4Moz.DumpAppender(logStats);
+  let appender = new Log.DumpAppender(logStats);
 
   if (typeof(level) == "undefined") {
     level = "Debug";
   }
-  getTestLogger().level = Log4Moz.Level[level];
-  Log4Moz.repository.getLogger("Services").level = Log4Moz.Level[level];
+  getTestLogger().level = Log.Level[level];
+  Log.repository.getLogger("Services").level = Log.Level[level];
 
-  log.level = Log4Moz.Level.Trace;
-  appender.level = Log4Moz.Level.Trace;
+  log.level = Log.Level.Trace;
+  appender.level = Log.Level.Trace;
   // Overwrite any other appenders (e.g. from previous incarnations)
   log.ownAppenders = [appender];
   log.updateAppenders();
 
   return logStats;
 }
 
 this.getTestLogger = function getTestLogger(component) {
-  return Log4Moz.repository.getLogger("Testing");
+  return Log.repository.getLogger("Testing");
 }
 
--- a/services/common/modules-testing/storageserver.js
+++ b/services/common/modules-testing/storageserver.js
@@ -15,17 +15,17 @@ this.EXPORTED_SYMBOLS = [
   "StorageServerCallback",
   "StorageServerCollection",
   "StorageServer",
   "storageServerForUsers",
 ];
 
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 const STORAGE_HTTP_LOGGER = "Services.Common.Test.Server";
 const STORAGE_API_VERSION = "2.0";
 
 // Use the same method that record.js does, which mirrors the server.
 function new_timestamp() {
   return Math.round(Date.now());
@@ -69,17 +69,17 @@ this.ServerBSO = function ServerBSO(id, 
   if (!id) {
     throw new Error("No ID for ServerBSO!");
   }
 
   if (!id.match(/^[a-zA-Z0-9_-]{1,64}$/)) {
     throw new Error("BSO ID is invalid: " + id);
   }
 
-  this._log = Log4Moz.repository.getLogger(STORAGE_HTTP_LOGGER);
+  this._log = Log.repository.getLogger(STORAGE_HTTP_LOGGER);
 
   this.id = id;
   if (!payload) {
     return;
   }
 
   CommonUtils.ensureMillisecondsTimestamp(modified);
 
@@ -282,17 +282,17 @@ this.StorageServerCollection =
   /*
    * Track modified timestamp.
    * We can't just use the timestamps of contained BSOs: an empty collection
    * has a modified time.
    */
   CommonUtils.ensureMillisecondsTimestamp(timestamp);
   this._timestamp = timestamp;
 
-  this._log = Log4Moz.repository.getLogger(STORAGE_HTTP_LOGGER);
+  this._log = Log.repository.getLogger(STORAGE_HTTP_LOGGER);
 }
 StorageServerCollection.prototype = {
   BATCH_MAX_COUNT: 100,         // # of records.
   BATCH_MAX_SIZE: 1024 * 1024,  // # bytes.
 
   _timestamp: null,
 
   get timestamp() {
@@ -867,17 +867,17 @@ this.StorageServerCallback = {
  * StorageServerCallback) as input.
  */
 this.StorageServer = function StorageServer(callback) {
   this.callback     = callback || {__proto__: StorageServerCallback};
   this.server       = new HttpServer();
   this.started      = false;
   this.users        = {};
   this.requestCount = 0;
-  this._log         = Log4Moz.repository.getLogger(STORAGE_HTTP_LOGGER);
+  this._log         = Log.repository.getLogger(STORAGE_HTTP_LOGGER);
 
   // Install our own default handler. This allows us to mess around with the
   // whole URL space.
   let handler = this.server._handler;
   handler._handleDefault = this.handleDefault.bind(this, handler);
 }
 StorageServer.prototype = {
   DEFAULT_QUOTA: 1024 * 1024, // # bytes.
--- a/services/common/rest.js
+++ b/services/common/rest.js
@@ -12,17 +12,17 @@ this.EXPORTED_SYMBOLS = [
   "TokenAuthenticatedRESTRequest"
 ];
 
 #endif
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                   "resource://services-crypto/utils.js");
 
 const Prefs = new Preferences("services.common.rest.");
 
 /**
@@ -86,19 +86,19 @@ this.RESTRequest = function RESTRequest(
   // If we don't have an nsIURI object yet, make one. This will throw if
   // 'uri' isn't a valid URI string.
   if (!(uri instanceof Ci.nsIURI)) {
     uri = Services.io.newURI(uri, null, null);
   }
   this.uri = uri;
 
   this._headers = {};
-  this._log = Log4Moz.repository.getLogger(this._logName);
+  this._log = Log.repository.getLogger(this._logName);
   this._log.level =
-    Log4Moz.Level[Prefs.get("log.logger.rest.request")];
+    Log.Level[Prefs.get("log.logger.rest.request")];
 }
 RESTRequest.prototype = {
 
   _logName: "Services.Common.RESTRequest",
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIBadCertListener2,
     Ci.nsIInterfaceRequestor,
@@ -304,17 +304,17 @@ RESTRequest.prototype = {
     // Set HTTP request body.
     if (method == "PUT" || method == "POST") {
       // Convert non-string bodies into JSON.
       if (typeof data != "string") {
         data = JSON.stringify(data);
       }
 
       this._log.debug(method + " Length: " + data.length);
-      if (this._log.level <= Log4Moz.Level.Trace) {
+      if (this._log.level <= Log.Level.Trace) {
         this._log.trace(method + " Body: " + data);
       }
 
       let stream = Cc["@mozilla.org/io/string-input-stream;1"]
                      .createInstance(Ci.nsIStringInputStream);
       stream.setData(data, data.length);
 
       let type = headers["content-type"] || "text/plain";
@@ -439,17 +439,17 @@ RESTRequest.prototype = {
       this.onComplete(error);
       this.onComplete = this.onProgress = null;
       return;
     }
 
     this._log.debug(this.method + " " + uri + " " + this.response.status);
 
     // Additionally give the full response body when Trace logging.
-    if (this._log.level <= Log4Moz.Level.Trace) {
+    if (this._log.level <= Log.Level.Trace) {
       this._log.trace(this.method + " body: " + this.response.body);
     }
 
     delete this._inputStream;
 
     this.onComplete(null);
     this.onComplete = this.onProgress = null;
   },
@@ -588,19 +588,19 @@ RESTRequest.prototype = {
   }
 };
 
 /**
  * Response object for a RESTRequest. This will be created automatically by
  * the RESTRequest.
  */
 this.RESTResponse = function RESTResponse() {
-  this._log = Log4Moz.repository.getLogger(this._logName);
+  this._log = Log.repository.getLogger(this._logName);
   this._log.level =
-    Log4Moz.Level[Prefs.get("log.logger.rest.response")];
+    Log.Level[Prefs.get("log.logger.rest.response")];
 }
 RESTResponse.prototype = {
 
   _logName: "Sync.RESTResponse",
 
   /**
    * Corresponding REST request
    */
--- a/services/common/storageservice.js
+++ b/services/common/storageservice.js
@@ -30,17 +30,17 @@ this.EXPORTED_SYMBOLS = [
   "StorageServiceClient",
   "StorageServiceRequestError",
 ];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 
 const Prefs = new Preferences("services.common.storageservice.");
 
 /**
  * The data type stored in the storage service.
  *
@@ -465,18 +465,18 @@ this.StorageServiceRequestError = functi
  * Future Features
  * ---------------
  *
  * The current implementation does not support true streaming for things like
  * multi-BSO retrieval. However, the API supports it, so we should be able
  * to implement it transparently.
  */
 function StorageServiceRequest() {
-  this._log = Log4Moz.repository.getLogger("Sync.StorageService.Request");
-  this._log.level = Log4Moz.Level[Prefs.get("log.level")];
+  this._log = Log.repository.getLogger("Sync.StorageService.Request");
+  this._log.level = Log.Level[Prefs.get("log.level")];
 
   this.notModified = false;
 
   this._client                 = null;
   this._request                = null;
   this._method                 = null;
   this._handler                = {};
   this._data                   = null;
@@ -1516,18 +1516,18 @@ Object.freeze(StorageCollectionBatchedDe
  * Storage API consumers almost certainly have added functionality on top of the
  * storage service. It is encouraged to create a child type which adds
  * functionality to this layer.
  *
  * @param baseURI
  *        (string) Base URI for all requests.
  */
 this.StorageServiceClient = function StorageServiceClient(baseURI) {
-  this._log = Log4Moz.repository.getLogger("Services.Common.StorageServiceClient");
-  this._log.level = Log4Moz.Level[Prefs.get("log.level")];
+  this._log = Log.repository.getLogger("Services.Common.StorageServiceClient");
+  this._log.level = Log.Level[Prefs.get("log.level")];
 
   this._baseURI = baseURI;
 
   if (this._baseURI[this._baseURI.length-1] != "/") {
     this._baseURI += "/";
   }
 
   this._log.info("Creating new StorageServiceClient under " + this._baseURI);
--- a/services/common/tests/unit/head_helpers.js
+++ b/services/common/tests/unit/head_helpers.js
@@ -1,19 +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/. */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://testing-common/services-common/logging.js");
 
-let btoa = Cu.import("resource://services-common/log4moz.js").btoa;
-let atob = Cu.import("resource://services-common/log4moz.js").atob;
+let btoa = Cu.import("resource://gre/modules/Log.jsm").btoa;
+let atob = Cu.import("resource://gre/modules/Log.jsm").atob;
 
 function do_check_empty(obj) {
   do_check_attribute_count(obj, 0);
 }
 
 function do_check_attribute_count(obj, c) {
   do_check_eq(c, Object.keys(obj).length);
 }
--- a/services/common/tests/unit/test_load_modules.js
+++ b/services/common/tests/unit/test_load_modules.js
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const modules = [
   "async.js",
   "bagheeraclient.js",
-  "log4moz.js",
   "rest.js",
   "storageservice.js",
   "stringbundle.js",
   "tokenserverclient.js",
   "utils.js",
 ];
 
 const test_modules = [
--- a/services/common/tests/unit/test_restrequest.js
+++ b/services/common/tests/unit/test_restrequest.js
@@ -1,21 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 
 //DEBUG = true;
 
 function run_test() {
-  Log4Moz.repository.getLogger("Services.Common.RESTRequest").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Services.Common.RESTRequest").level =
+    Log.Level.Trace;
   initTestLogging("Trace");
 
   run_next_test();
 }
 
 /**
  * Initializing a RESTRequest with an invalid URI throws
  * NS_ERROR_MALFORMED_URI.
--- a/services/common/tests/unit/test_storage_server.js
+++ b/services/common/tests/unit/test_storage_server.js
@@ -100,18 +100,18 @@ function doPutRequest(request, data) {
 function doDeleteRequest(request) {
   let cb = Async.makeSpinningCallback();
   request.delete(cb);
 
   return waitAndValidateResponse(cb, request);
 }
 
 function run_test() {
-  Log4Moz.repository.getLogger("Services.Common.Test.StorageServer").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Services.Common.Test.StorageServer").level =
+    Log.Level.Trace;
   initTestLogging();
 
   run_next_test();
 }
 
 add_test(function test_creation() {
   _("Ensure a simple server can be created.");
 
--- a/services/common/tests/unit/xpcshell.ini
+++ b/services/common/tests/unit/xpcshell.ini
@@ -20,17 +20,16 @@ firefox-appdir = browser
 [test_utils_stackTrace.js]
 [test_utils_utf8.js]
 [test_utils_uuid.js]
 
 [test_async_chain.js]
 [test_async_querySpinningly.js]
 [test_bagheera_server.js]
 [test_bagheera_client.js]
-[test_log4moz.js]
 [test_observers.js]
 [test_restrequest.js]
 [test_tokenauthenticatedrequest.js]
 
 # Storage service APIs
 [test_storageservice_bso.js]
 [test_storageservice_client.js]
 
--- a/services/common/tokenserverclient.js
+++ b/services/common/tokenserverclient.js
@@ -9,17 +9,17 @@ this.EXPORTED_SYMBOLS = [
   "TokenServerClientError",
   "TokenServerClientNetworkError",
   "TokenServerClientServerError",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 
 const Prefs = new Preferences("services.common.tokenserverclient.");
 
 /**
  * Represents a TokenServerClient error that occurred on the client.
  *
@@ -109,18 +109,18 @@ TokenServerClientServerError.prototype.c
  *
  *  - The server sends a JSON response on error. The client does not currently
  *    parse this. It might be convenient if it did.
  *  - Currently most non-200 status codes are rolled into one error type. It
  *    might be helpful if callers had a richer API that communicated who was
  *    at fault (e.g. differentiating a 503 from a 401).
  */
 this.TokenServerClient = function TokenServerClient() {
-  this._log = Log4Moz.repository.getLogger("Common.TokenServerClient");
-  this._log.level = Log4Moz.Level[Prefs.get("logger.level")];
+  this._log = Log.repository.getLogger("Common.TokenServerClient");
+  this._log.level = Log.Level[Prefs.get("logger.level")];
 }
 TokenServerClient.prototype = {
   /**
    * Logger instance.
    */
   _log: null,
 
   /**
--- a/services/common/utils.js
+++ b/services/common/utils.js
@@ -5,17 +5,17 @@
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 this.EXPORTED_SYMBOLS = ["CommonUtils"];
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm")
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 this.CommonUtils = {
   exceptionStr: function exceptionStr(e) {
     if (!e) {
       return "" + e;
     }
     let message = e.message ? e.message : e;
     return message + " " + CommonUtils.stackTrace(e);
@@ -83,17 +83,17 @@ this.CommonUtils = {
    * Create a nsIURI instance from a string.
    */
   makeURI: function makeURI(URIString) {
     if (!URIString)
       return null;
     try {
       return Services.io.newURI(URIString, null, null);
     } catch (e) {
-      let log = Log4Moz.repository.getLogger("Common.Utils");
+      let log = Log.repository.getLogger("Common.Utils");
       log.debug("Could not create URI: " + CommonUtils.exceptionStr(e));
       return null;
     }
   },
 
   /**
    * Execute a function on the next event loop tick.
    *
@@ -459,17 +459,17 @@ this.CommonUtils = {
    *
    * @param branch
    *        (Preferences) Branch from which to retrieve preference.
    * @param pref
    *        (string) The preference to read from.
    * @param def
    *        (Number) The default value to use if the preference is not defined.
    * @param log
-   *        (Log4Moz.Logger) Logger to write warnings to.
+   *        (Log.Logger) Logger to write warnings to.
    */
   getEpochPref: function getEpochPref(branch, pref, def=0, log=null) {
     if (!Number.isInteger(def)) {
       throw new Error("Default value is not a number: " + def);
     }
 
     let valueStr = branch.get(pref, null);
 
@@ -502,17 +502,17 @@ this.CommonUtils = {
    * @param branch
    *        (Preferences) Branch from which to read preference.
    * @param pref
    *        (string) The preference from which to read.
    * @param def
    *        (Number) The default value (in milliseconds) if the preference is
    *        not defined or invalid.
    * @param log
-   *        (Log4Moz.Logger) Logger to write warnings to.
+   *        (Log.Logger) Logger to write warnings to.
    * @param oldestYear
    *        (Number) Oldest year to accept in read values.
    */
   getDatePref: function getDatePref(branch, pref, def=0, log=null,
                                     oldestYear=2010) {
 
     let valueInt = this.getEpochPref(branch, pref, def, log);
     let date = new Date(valueInt);
--- a/services/datareporting/DataReportingService.js
+++ b/services/datareporting/DataReportingService.js
@@ -218,47 +218,47 @@ DataReportingService.prototype = Object.
   },
 
   _loadHealthReporter: function () {
     let ns = {};
     // Lazy import so application startup isn't adversely affected.
 
     Cu.import("resource://gre/modules/Task.jsm", ns);
     Cu.import("resource://gre/modules/HealthReport.jsm", ns);
-    Cu.import("resource://services-common/log4moz.js", ns);
+    Cu.import("resource://gre/modules/Log.jsm", ns);
 
     // How many times will we rewrite this code before rolling it up into a
     // generic module? See also bug 451283.
     const LOGGERS = [
       "Services.DataReporting",
       "Services.HealthReport",
       "Services.Metrics",
       "Services.BagheeraClient",
       "Sqlite.Connection.healthreport",
     ];
 
     let loggingPrefs = new Preferences(HEALTHREPORT_LOGGING_BRANCH);
     if (loggingPrefs.get("consoleEnabled", true)) {
       let level = loggingPrefs.get("consoleLevel", "Warn");
-      let appender = new ns.Log4Moz.ConsoleAppender();
-      appender.level = ns.Log4Moz.Level[level] || ns.Log4Moz.Level.Warn;
+      let appender = new ns.Log.ConsoleAppender();
+      appender.level = ns.Log.Level[level] || ns.Log.Level.Warn;
 
       for (let name of LOGGERS) {
-        let logger = ns.Log4Moz.repository.getLogger(name);
+        let logger = ns.Log.repository.getLogger(name);
         logger.addAppender(appender);
       }
     }
 
     if (loggingPrefs.get("dumpEnabled", false)) {
       let level = loggingPrefs.get("dumpLevel", "Debug");
-      let appender = new ns.Log4Moz.DumpAppender();
-      appender.level = ns.Log4Moz.Level[level] || ns.Log4Moz.Level.Debug;
+      let appender = new ns.Log.DumpAppender();
+      appender.level = ns.Log.Level[level] || ns.Log.Level.Debug;
 
       for (let name of LOGGERS) {
-        let logger = ns.Log4Moz.repository.getLogger(name);
+        let logger = ns.Log.repository.getLogger(name);
         logger.addAppender(appender);
       }
     }
 
     this._healthReporter = new ns.HealthReporter(HEALTHREPORT_BRANCH,
                                                  this.policy,
                                                  this.sessionRecorder);
 
--- a/services/datareporting/modules-testing/mocks.jsm
+++ b/services/datareporting/modules-testing/mocks.jsm
@@ -3,22 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["MockPolicyListener"];
 
 const {utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 
 this.MockPolicyListener = function MockPolicyListener() {
-  this._log = Log4Moz.repository.getLogger("Services.DataReporting.Testing.MockPolicyListener");
-  this._log.level = Log4Moz.Level["Debug"];
+  this._log = Log.repository.getLogger("Services.DataReporting.Testing.MockPolicyListener");
+  this._log.level = Log.Level["Debug"];
 
   this.requestDataUploadCount = 0;
   this.lastDataRequest = null;
 
   this.requestRemoteDeleteCount = 0;
   this.lastRemoteDeleteRequest = null;
 
   this.notifyUserCount = 0;
--- a/services/datareporting/policy.jsm
+++ b/services/datareporting/policy.jsm
@@ -22,17 +22,17 @@ this.EXPORTED_SYMBOLS = [
   "DataReportingPolicy",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 #endif
 
 Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
 // Used as a sanity lower bound for dates stored in prefs. This module was
 // implemented in 2012, so any earlier dates indicate an incorrect clock.
 const OLDEST_ALLOWED_YEAR = 2012;
 
@@ -258,18 +258,18 @@ this.DataSubmissionRequest.prototype = O
  *        queried and stored.
  * @param healthReportPrefs
  *        (Preferences) Handle on preferences branch holding Health Report state.
  * @param listener
  *        (object) Object with callbacks that will be invoked at certain key
  *        events.
  */
 this.DataReportingPolicy = function (prefs, healthReportPrefs, listener) {
-  this._log = Log4Moz.repository.getLogger("Services.DataReporting.Policy");
-  this._log.level = Log4Moz.Level["Debug"];
+  this._log = Log.repository.getLogger("Services.DataReporting.Policy");
+  this._log.level = Log.Level["Debug"];
 
   for (let handler of this.REQUIRED_LISTENERS) {
     if (!listener[handler]) {
       throw new Error("Passed listener does not contain required handler: " +
                       handler);
     }
   }
 
--- a/services/datareporting/sessions.jsm
+++ b/services/datareporting/sessions.jsm
@@ -11,17 +11,17 @@ this.EXPORTED_SYMBOLS = [
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 #endif
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 
 // We automatically prune sessions older than this.
 const MAX_SESSION_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days.
 const STARTUP_RETRY_INTERVAL_MS = 5000;
 
 // Wait up to 5 minutes for startup measurements before giving up.
@@ -64,17 +64,17 @@ this.SessionRecorder = function (branch)
   if (!branch) {
     throw new Error("branch argument must be defined.");
   }
 
   if (!branch.endsWith(".")) {
     throw new Error("branch argument must end with '.': " + branch);
   }
 
-  this._log = Log4Moz.repository.getLogger("Services.DataReporting.SessionRecorder");
+  this._log = Log.repository.getLogger("Services.DataReporting.SessionRecorder");
 
   this._prefs = new Preferences(branch);
   this._lastActivityWasInactive = false;
   this._activeTicks = 0;
   this.fineTotalTime = 0;
   this._started = false;
   this._timer = null;
   this._startupFieldTries = 0;
--- a/services/healthreport/healthreporter.jsm
+++ b/services/healthreport/healthreporter.jsm
@@ -13,17 +13,17 @@ const {classes: Cc, interfaces: Ci, util
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
 Cu.import("resource://gre/modules/Metrics.jsm");
 Cu.import("resource://services-common/async.js");
 
 Cu.import("resource://services-common/bagheeraclient.js");
 #endif
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -255,17 +255,17 @@ function AbstractHealthReporter(branch, 
   if (!branch.endsWith(".")) {
     throw new Error("Branch must end with a period (.): " + branch);
   }
 
   if (!policy) {
     throw new Error("Must provide policy to HealthReporter constructor.");
   }
 
-  this._log = Log4Moz.repository.getLogger("Services.HealthReport.HealthReporter");
+  this._log = Log.repository.getLogger("Services.HealthReport.HealthReporter");
   this._log.info("Initializing health reporter instance against " + branch);
 
   this._branch = branch;
   this._prefs = new Preferences(branch);
 
   this._policy = policy;
   this.sessionRecorder = sessionRecorder;
 
--- a/services/healthreport/profile.jsm
+++ b/services/healthreport/profile.jsm
@@ -20,17 +20,17 @@ Cu.import("resource://gre/modules/Metric
 #endif
 
 const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
 const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm")
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 // Profile creation time access.
 // This is separate from the provider to simplify testing and enable extraction
 // to a shared location in the future.
 this.ProfileCreationTimeAccessor = function(profile, log) {
   this.profilePath = profile || OS.Constants.Path.profileDir;
   if (!this.profilePath) {
--- a/services/metrics/dataprovider.jsm
+++ b/services/metrics/dataprovider.jsm
@@ -15,17 +15,17 @@ const {utils: Cu} = Components;
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
 #endif
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 
 
 /**
  * Represents a collection of related pieces/fields of data.
  *
  * This is an abstract base type.
@@ -81,17 +81,17 @@ this.Measurement = function () {
       throw new Error("Field does not contain metadata: " + name);
     }
 
     if (!info.type) {
       throw new Error("Field is missing required type property: " + name);
     }
   }
 
-  this._log = Log4Moz.repository.getLogger("Services.Metrics.Measurement." + this.name);
+  this._log = Log.repository.getLogger("Services.Metrics.Measurement." + this.name);
 
   this.id = null;
   this.storage = null;
   this._fields = {};
 
   this._serializers = {};
   this._serializers[this.SERIALIZE_JSON] = {
     singular: this._serializeJSONSingular.bind(this),
@@ -497,17 +497,17 @@ this.Provider = function () {
   if (!this.name) {
     throw new Error("Provider must define a name.");
   }
 
   if (!Array.isArray(this.measurementTypes)) {
     throw new Error("Provider must define measurement types.");
   }
 
-  this._log = Log4Moz.repository.getLogger("Services.Metrics.Provider." + this.name);
+  this._log = Log.repository.getLogger("Services.Metrics.Provider." + this.name);
 
   this.measurements = null;
   this.storage = null;
 }
 
 Provider.prototype = Object.freeze({
   /**
    * Whether the provider only pulls data from other sources.
--- a/services/metrics/providermanager.jsm
+++ b/services/metrics/providermanager.jsm
@@ -9,28 +9,28 @@ this.EXPORTED_SYMBOLS = ["ProviderManage
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
 #endif
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 
 /**
  * Handles and coordinates the collection of metrics data from providers.
  *
  * This provides an interface for managing `Metrics.Provider` instances. It
  * provides APIs for bulk collection of data.
  */
 this.ProviderManager = function (storage) {
-  this._log = Log4Moz.repository.getLogger("Services.Metrics.ProviderManager");
+  this._log = Log.repository.getLogger("Services.Metrics.ProviderManager");
 
   this._providers = new Map();
   this._storage = storage;
 
   this._providerInitQueue = [];
   this._providerInitializing = false;
 
   this._pullOnlyProviders = {};
--- a/services/metrics/storage.jsm
+++ b/services/metrics/storage.jsm
@@ -17,17 +17,17 @@ const {utils: Cu} = Components;
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
 #endif
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Sqlite.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 
 
 // These do not account for leap seconds. Meh.
 function dateToDays(date) {
   return Math.floor(date.getTime() / MILLISECONDS_PER_DAY);
 }
 
@@ -693,17 +693,17 @@ this.MetricsStorageBackend = function (p
  * Instances of this should be obtained by calling MetricsStorageConnection().
  *
  * The current implementation will not work if the database is mutated by
  * multiple connections because of the way we cache primary keys.
  *
  * FUTURE enforce 1 read/write connection per database limit.
  */
 function MetricsStorageSqliteBackend(connection) {
-  this._log = Log4Moz.repository.getLogger("Services.Metrics.MetricsStorage");
+  this._log = Log.repository.getLogger("Services.Metrics.MetricsStorage");
 
   this._connection = connection;
   this._enabledWALCheckpointPages = null;
 
   // Integer IDs to string name.
   this._typesByID = new Map();
 
   // String name to integer IDs.
--- a/services/sync/modules-testing/fakeservices.js
+++ b/services/sync/modules-testing/fakeservices.js
@@ -11,17 +11,17 @@ this.EXPORTED_SYMBOLS = [
   "fakeSHA256HMAC",
 ];
 
 const {utils: Cu} = Components;
 
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 
-let btoa = Cu.import("resource://services-common/log4moz.js").btoa;
+let btoa = Cu.import("resource://gre/modules/Log.jsm").btoa;
 
 this.FakeFilesystemService = function FakeFilesystemService(contents) {
   this.fakeContents = contents;
   let self = this;
 
   Utils.jsonSave = function jsonSave(filePath, that, obj, callback) {
     let json = typeof obj == "function" ? obj.call(that) : obj;
     self.fakeContents["weave/" + filePath + ".json"] = JSON.stringify(json);
--- a/services/sync/modules/addonsreconciler.js
+++ b/services/sync/modules/addonsreconciler.js
@@ -14,17 +14,17 @@
  * standalone file so it could be more easily understood, tested, and
  * hopefully ported.
  */
 
 "use strict";
 
 const Cu = Components.utils;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 
 const DEFAULT_STATE_FILE = "addonsreconciler";
 
 this.CHANGE_INSTALLED   = 1;
 this.CHANGE_UNINSTALLED = 2;
 this.CHANGE_ENABLED     = 3;
@@ -108,19 +108,19 @@ this.EXPORTED_SYMBOLS = ["AddonsReconcil
  * add-ons are first disabled then they are actually uninstalled. So, we will
  * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled
  * events only come after the Addon Manager is closed or another view is
  * switched to. In the case of Sync performing the uninstall, the uninstall
  * events will occur immediately. However, we still see disabling events and
  * heed them like they were normal. In the end, the state is proper.
  */
 this.AddonsReconciler = function AddonsReconciler() {
-  this._log = Log4Moz.repository.getLogger("Sync.AddonsReconciler");
+  this._log = Log.repository.getLogger("Sync.AddonsReconciler");
   let level = Svc.Prefs.get("log.logger.addonsreconciler", "Debug");
-  this._log.level = Log4Moz.Level[level];
+  this._log.level = Log.Level[level];
 
   Svc.Obs.add("xpcom-shutdown", this.stopListening, this);
 };
 AddonsReconciler.prototype = {
   /** Flag indicating whether we are listening to AddonManager events. */
   _listening: false,
 
   /**
@@ -135,17 +135,17 @@ AddonsReconciler.prototype = {
    * to disk when handling events.
    *
    * This allows test code to avoid spinning to write during observer
    * notifications and xpcom shutdown, which appears to cause hangs on WinXP
    * (Bug 873861).
    */
   _shouldPersist: true,
 
-  /** log4moz logger instance */
+  /** Log logger instance */
   _log: null,
 
   /**
    * Container for add-on metadata.
    *
    * Keys are add-on IDs. Values are objects which describe the state of the
    * add-on. This is a minimal mirror of data that can be queried from
    * AddonManager. In some cases, we retain data longer than AddonManager.
--- a/services/sync/modules/addonutils.js
+++ b/services/sync/modules/addonutils.js
@@ -4,27 +4,27 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["AddonUtils"];
 
 const {interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
   "resource://gre/modules/AddonRepository.jsm");
 
 function AddonUtilsInternal() {
-  this._log = Log4Moz.repository.getLogger("Sync.AddonUtils");
-  this._log.Level = Log4Moz.Level[Svc.Prefs.get("log.logger.addonutils")];
+  this._log = Log.repository.getLogger("Sync.AddonUtils");
+  this._log.Level = Log.Level[Svc.Prefs.get("log.logger.addonutils")];
 }
 AddonUtilsInternal.prototype = {
   /**
    * Obtain an AddonInstall object from an AddonSearchResult instance.
    *
    * The callback will be invoked with the result of the operation. The
    * callback receives 2 arguments, error and result. Error will be falsy
    * on success or some kind of error value otherwise. The result argument
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -8,17 +8,17 @@ this.EXPORTED_SYMBOLS = [
   "SyncEngine",
   "Tracker",
   "Store"
 ];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 
@@ -37,19 +37,19 @@ this.Tracker = function Tracker(name, en
   if (!engine) {
     throw new Error("Tracker must be associated with an Engine instance.");
   }
 
   name = name || "Unnamed";
   this.name = this.file = name.toLowerCase();
   this.engine = engine;
 
-  this._log = Log4Moz.repository.getLogger("Sync.Tracker." + name);
+  this._log = Log.repository.getLogger("Sync.Tracker." + name);
   let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
-  this._log.level = Log4Moz.Level[level];
+  this._log.level = Log.Level[level];
 
   this._score = 0;
   this._ignored = [];
   this.ignoreAll = false;
   this.changedIDs = {};
   this.loadChangedIDs();
 }
 Tracker.prototype = {
@@ -192,19 +192,19 @@ this.Store = function Store(name, engine
   if (!engine) {
     throw new Error("Store must be associated with an Engine instance.");
   }
 
   name = name || "Unnamed";
   this.name = name.toLowerCase();
   this.engine = engine;
 
-  this._log = Log4Moz.repository.getLogger("Sync.Store." + name);
+  this._log = Log.repository.getLogger("Sync.Store." + name);
   let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
-  this._log.level = Log4Moz.Level[level];
+  this._log.level = Log.Level[level];
 
   XPCOMUtils.defineLazyGetter(this, "_timer", function() {
     return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   });
 }
 Store.prototype = {
 
   _sleep: function _sleep(delay) {
@@ -378,18 +378,18 @@ Store.prototype = {
     throw "override wipe in a subclass";
   }
 };
 
 this.EngineManager = function EngineManager(service) {
   this.service = service;
 
   this._engines = {};
-  this._log = Log4Moz.repository.getLogger("Sync.EngineManager");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get(
+  this._log = Log.repository.getLogger("Sync.EngineManager");
+  this._log.level = Log.Level[Svc.Prefs.get(
     "log.logger.service.engines", "Debug")];
 }
 EngineManager.prototype = {
   get: function get(name) {
     // Return an array of engines if we have an array of names
     if (Array.isArray(name)) {
       let engines = [];
       name.forEach(function(name) {
@@ -471,19 +471,19 @@ this.Engine = function Engine(name, serv
     throw new Error("Engine must be associated with a Service instance.");
   }
 
   this.Name = name || "Unnamed";
   this.name = name.toLowerCase();
   this.service = service;
 
   this._notify = Utils.notify("weave:engine:");
-  this._log = Log4Moz.repository.getLogger("Sync.Engine." + this.Name);
+  this._log = Log.repository.getLogger("Sync.Engine." + this.Name);
   let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
-  this._log.level = Log4Moz.Level[level];
+  this._log.level = Log.Level[level];
 
   this._tracker; // initialize tracker to load previously changed IDs
   this._log.debug("Engine initialized");
 }
 Engine.prototype = {
   // _storeObj, and _trackerObj should to be overridden in subclasses
   _storeObj: Store,
   _trackerObj: Tracker,
@@ -1051,17 +1051,17 @@ SyncEngine.prototype = {
    * This function essentially determines whether to apply an incoming record.
    *
    * @param  item
    *         Record from server to be tested for application.
    * @return boolean
    *         Truthy if incoming record should be applied. False if not.
    */
   _reconcile: function _reconcile(item) {
-    if (this._log.level <= Log4Moz.Level.Trace) {
+    if (this._log.level <= Log.Level.Trace) {
       this._log.trace("Incoming: " + item);
     }
 
     // We start reconciling by collecting a bunch of state. We do this here
     // because some state may change during the course of this function and we
     // need to operate on the original values.
     let existsLocally   = this._store.itemExists(item.id);
     let locallyModified = item.id in this._modified;
@@ -1268,17 +1268,17 @@ SyncEngine.prototype = {
         }
 
         up.clearRecords();
       });
 
       for each (let id in modifiedIDs) {
         try {
           let out = this._createRecord(id);
-          if (this._log.level <= Log4Moz.Level.Trace)
+          if (this._log.level <= Log.Level.Trace)
             this._log.trace("Outgoing: " + out);
 
           out.encrypt(this.service.collectionKeys.keyForCollection(this.name));
           up.pushData(out);
         }
         catch(ex) {
           this._log.warn("Error creating record: " + Utils.exceptionStr(ex));
         }
--- a/services/sync/modules/engines/forms.js
+++ b/services/sync/modules/engines/forms.js
@@ -9,34 +9,34 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 const FORMS_TTL = 5184000; // 60 days
 
 this.FormRec = function FormRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
 FormRec.prototype = {
   __proto__: CryptoWrapper.prototype,
   _logName: "Sync.Record.Form",
   ttl: FORMS_TTL
 };
 
 Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
 
 
 let FormWrapper = {
-  _log: Log4Moz.repository.getLogger("Sync.Engine.Forms"),
+  _log: Log.repository.getLogger("Sync.Engine.Forms"),
 
   _getEntryCols: ["fieldname", "value"],
   _guidCols:     ["guid"],
 
   // Do a "sync" search by spinning the event loop until it completes.
   _searchSpinningly: function(terms, searchData) {
     let results = [];
     let cb = Async.makeSpinningCallback();
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -8,17 +8,17 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const HISTORY_TTL = 5184000; // 60 days
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 
 this.HistoryRec = function HistoryRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -5,17 +5,17 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["IdentityManager"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 
 // Lazy import to prevent unnecessary load on startup.
 for (let symbol of ["BulkKeyBundle", "SyncKeyBundle"]) {
   XPCOMUtils.defineLazyModuleGetter(this, symbol,
                                     "resource://services-sync/keys.js",
                                     symbol);
 }
@@ -52,18 +52,18 @@ for (let symbol of ["BulkKeyBundle", "Sy
  * functions to support your API. Although, that is certainly not required.
  * If you do monkeypatch, please be advised that Sync expects the core
  * attributes to have values. You will need to carry at least account and
  * username forward. If you do not wish to support one of the built-in
  * authentication mechanisms, you'll probably want to redefine currentAuthState
  * and any other function that involves the built-in functionality.
  */
 this.IdentityManager = function IdentityManager() {
-  this._log = Log4Moz.repository.getLogger("Sync.Identity");
-  this._log.Level = Log4Moz.Level[Svc.Prefs.get("log.logger.identity")];
+  this._log = Log.repository.getLogger("Sync.Identity");
+  this._log.Level = Log.Level[Svc.Prefs.get("log.logger.identity")];
 
   this._basicPassword = null;
   this._basicPasswordAllowLookup = true;
   this._basicPasswordUpdated = false;
   this._syncKey = null;
   this._syncKeyAllowLookup = true;
   this._syncKeySet = false;
   this._syncKeyBundle = null;
--- a/services/sync/modules/jpakeclient.js
+++ b/services/sync/modules/jpakeclient.js
@@ -1,17 +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/. */
 
 this.EXPORTED_SYMBOLS = ["JPAKEClient", "SendCredentialsController"];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/util.js");
 
 const REQUEST_TIMEOUT         = 60; // 1 minute
 const KEYEXCHANGE_VERSION     = 3;
 
 const JPAKE_SIGNERID_SENDER   = "sender";
@@ -109,18 +109,18 @@ const JPAKE_VERIFY_VALUE      = "0123456
  *   client.abort();
  * 
  * Note that after completion or abort, the 'client' instance may not be reused.
  * You will have to create a new one in case you'd like to restart the process.
  */
 this.JPAKEClient = function JPAKEClient(controller) {
   this.controller = controller;
 
-  this._log = Log4Moz.repository.getLogger("Sync.JPAKEClient");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get(
+  this._log = Log.repository.getLogger("Sync.JPAKEClient");
+  this._log.level = Log.Level[Svc.Prefs.get(
     "log.logger.service.jpakeclient", "Debug")];
 
   this._serverURL = Svc.Prefs.get("jpake.serverURL");
   this._pollInterval = Svc.Prefs.get("jpake.pollInterval");
   this._maxTries = Svc.Prefs.get("jpake.maxTries");
   if (this._serverURL.slice(-1) != "/") {
     this._serverURL += "/";
   }
@@ -695,18 +695,18 @@ JPAKEClient.prototype = {
  * Usage:
  *
  *   jpakeclient.controller = new SendCredentialsController(jpakeclient,
  *                                                          service);
  *
  */
 this.SendCredentialsController =
  function SendCredentialsController(jpakeclient, service) {
-  this._log = Log4Moz.repository.getLogger("Sync.SendCredentialsController");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
+  this._log = Log.repository.getLogger("Sync.SendCredentialsController");
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
 
   this._log.trace("Loading.");
   this.jpakeclient = jpakeclient;
   this.service = service;
 
   // Register ourselves as observers the first Sync finishing (either
   // successfully or unsuccessfully, we don't care) or for removing
   // this device's sync configuration, in case that happens while we
--- a/services/sync/modules/keys.js
+++ b/services/sync/modules/keys.js
@@ -7,17 +7,17 @@
 this.EXPORTED_SYMBOLS = [
   "BulkKeyBundle",
   "SyncKeyBundle"
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 
 /**
  * Represents a pair of keys.
  *
  * Each key stored in a key bundle is 256 bits. One key is used for symmetric
  * encryption. The other is used for HMAC.
  *
@@ -115,17 +115,17 @@ KeyBundle.prototype = {
 };
 
 /**
  * Represents a KeyBundle associated with a collection.
  *
  * This is just a KeyBundle with a collection attached.
  */
 this.BulkKeyBundle = function BulkKeyBundle(collection) {
-  let log = Log4Moz.repository.getLogger("Sync.BulkKeyBundle");
+  let log = Log.repository.getLogger("Sync.BulkKeyBundle");
   log.info("BulkKeyBundle being created for " + collection);
   KeyBundle.call(this);
 
   this._collection = collection;
 }
 
 BulkKeyBundle.prototype = {
   __proto__: KeyBundle.prototype,
@@ -172,17 +172,17 @@ BulkKeyBundle.prototype = {
  *
  * Instances of this type should be considered immutable. You create an
  * instance by specifying the username and 26 character "friendly" Base32
  * encoded Sync Key. The Sync Key is derived at instance creation time.
  *
  * If the username or Sync Key is invalid, an Error will be thrown.
  */
 this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) {
-  let log = Log4Moz.repository.getLogger("Sync.SyncKeyBundle");
+  let log = Log.repository.getLogger("Sync.SyncKeyBundle");
   log.info("SyncKeyBundle being created.");
   KeyBundle.call(this);
 
   this.generateFromKey(username, syncKey);
 }
 SyncKeyBundle.prototype = {
   __proto__: KeyBundle.prototype,
 
--- a/services/sync/modules/notifications.js
+++ b/services/sync/modules/notifications.js
@@ -5,17 +5,17 @@
 this.EXPORTED_SYMBOLS = ["Notifications", "Notification", "NotificationButton"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://services-common/observers.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 
 this.Notifications = {
   // Match the referenced values in toolkit/content/widgets/notification.xml.
   get PRIORITY_INFO()     1, // PRIORITY_INFO_LOW
   get PRIORITY_WARNING()  4, // PRIORITY_WARNING_LOW
   get PRIORITY_ERROR()    7, // PRIORITY_CRITICAL_LOW
 
@@ -110,17 +110,17 @@ Notification.prototype.buttons = [];
  * A button to display in a notification.
  */
 this.NotificationButton =
  function NotificationButton(label, accessKey, callback) {
   function callbackWrapper() {
     try {
       callback.apply(this, arguments);
     } catch (e) {
-      let logger = Log4Moz.repository.getLogger("Sync.Notifications");
+      let logger = Log.repository.getLogger("Sync.Notifications");
       logger.error("An exception occurred: " + Utils.exceptionStr(e));
       logger.info(Utils.stackTrace(e));
       throw e;
     }
   }
 
   this.label = label;
   this.accessKey = accessKey;
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -4,28 +4,28 @@
 
 this.EXPORTED_SYMBOLS = [
   "ErrorHandler",
   "SyncScheduler",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
 
 this.SyncScheduler = function SyncScheduler(service) {
   this.service = service;
   this.init();
 }
 SyncScheduler.prototype = {
-  _log: Log4Moz.repository.getLogger("Sync.SyncScheduler"),
+  _log: Log.repository.getLogger("Sync.SyncScheduler"),
 
   _fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
                       LOGIN_FAILED_NO_PASSWORD,
                       LOGIN_FAILED_NO_PASSPHRASE,
                       LOGIN_FAILED_INVALID_PASSPHRASE,
                       LOGIN_FAILED_LOGIN_REJECTED],
 
   /**
@@ -62,17 +62,17 @@ SyncScheduler.prototype = {
 
   get globalScore() Svc.Prefs.get("globalScore", 0),
   set globalScore(value) Svc.Prefs.set("globalScore", value),
 
   get numClients() Svc.Prefs.get("numClients", 0),
   set numClients(value) Svc.Prefs.set("numClients", value),
 
   init: function init() {
-    this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
+    this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
     this.setDefaults();
     Svc.Obs.add("weave:engine:score:updated", this);
     Svc.Obs.add("network:offline-status-changed", this);
     Svc.Obs.add("weave:service:sync:start", this);
     Svc.Obs.add("weave:service:sync:finish", this);
     Svc.Obs.add("weave:engine:sync:finish", this);
     Svc.Obs.add("weave:engine:sync:error", this);
     Svc.Obs.add("weave:service:login:error", this);
@@ -495,34 +495,34 @@ ErrorHandler.prototype = {
     Svc.Obs.add("weave:service:login:error", this);
     Svc.Obs.add("weave:service:sync:error", this);
     Svc.Obs.add("weave:service:sync:finish", this);
 
     this.initLogs();
   },
 
   initLogs: function initLogs() {
-    this._log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
-    this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
+    this._log = Log.repository.getLogger("Sync.ErrorHandler");
+    this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
     this._cleaningUpFileLogs = false;
 
-    let root = Log4Moz.repository.getLogger("Sync");
-    root.level = Log4Moz.Level[Svc.Prefs.get("log.rootLogger")];
+    let root = Log.repository.getLogger("Sync");
+    root.level = Log.Level[Svc.Prefs.get("log.rootLogger")];
 
-    let formatter = new Log4Moz.BasicFormatter();
-    let capp = new Log4Moz.ConsoleAppender(formatter);
-    capp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.console")];
+    let formatter = new Log.BasicFormatter();
+    let capp = new Log.ConsoleAppender(formatter);
+    capp.level = Log.Level[Svc.Prefs.get("log.appender.console")];
     root.addAppender(capp);
 
-    let dapp = new Log4Moz.DumpAppender(formatter);
-    dapp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.dump")];
+    let dapp = new Log.DumpAppender(formatter);
+    dapp.level = Log.Level[Svc.Prefs.get("log.appender.dump")];
     root.addAppender(dapp);
 
-    let fapp = this._logAppender = new Log4Moz.StorageStreamAppender(formatter);
-    fapp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.file.level")];
+    let fapp = this._logAppender = new Log.StorageStreamAppender(formatter);
+    fapp.level = Log.Level[Svc.Prefs.get("log.appender.file.level")];
     root.addAppender(fapp);
   },
 
   observe: function observe(subject, topic, data) {
     this._log.trace("Handling " + topic);
     switch(topic) {
       case "weave:engine:sync:applied":
         if (subject.newFailed) {
--- a/services/sync/modules/record.js
+++ b/services/sync/modules/record.js
@@ -13,17 +13,17 @@ this.EXPORTED_SYMBOLS = [
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 const CRYPTO_COLLECTION = "crypto";
 const KEYS_WBO = "keys";
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 
 this.WBORecord = function WBORecord(collection, id) {
   this.data = {};
   this.payload = {};
@@ -106,17 +106,17 @@ WBORecord.prototype = {
 Utils.deferGetSet(WBORecord, "data", ["id", "modified", "sortindex", "payload"]);
 
 /**
  * An interface and caching layer for records.
  */
 this.RecordManager = function RecordManager(service) {
   this.service = service;
 
-  this._log = Log4Moz.repository.getLogger(this._logName);
+  this._log = Log.repository.getLogger(this._logName);
   this._records = {};
 }
 RecordManager.prototype = {
   _recordType: WBORecord,
   _logName: "Sync.RecordManager",
 
   import: function RecordMgr_import(url) {
     this._log.trace("Importing record: " + (url.spec ? url.spec : url));
@@ -276,17 +276,17 @@ Utils.deferGetSet(CryptoWrapper, "cleart
  * You can update this thing simply by giving it /info/collections. It'll
  * use the last modified time to bring itself up to date.
  */
 this.CollectionKeyManager = function CollectionKeyManager() {
   this.lastModified = 0;
   this._collections = {};
   this._default = null;
 
-  this._log = Log4Moz.repository.getLogger("Sync.CollectionKeyManager");
+  this._log = Log.repository.getLogger("Sync.CollectionKeyManager");
 }
 
 // TODO: persist this locally as an Identity. Bug 610913.
 // Note that the last modified time needs to be preserved.
 CollectionKeyManager.prototype = {
 
   // Return information about old vs new keys:
   // * same: true if two collections are equal
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -9,17 +9,17 @@ this.EXPORTED_SYMBOLS = [
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/util.js");
 
 const DEFAULT_LOAD_FLAGS =
   // Always validate the cache:
   Ci.nsIRequest.LOAD_BYPASS_CACHE |
@@ -45,19 +45,19 @@ const DEFAULT_LOAD_FLAGS =
  *
  *   function callback(error, result) {...}
  *
  * 'error' will be null on successful requests. Likewise, result will not be
  * passed (=undefined) when an error occurs. Note that this is independent of
  * the status of the HTTP response.
  */
 this.AsyncResource = function AsyncResource(uri) {
-  this._log = Log4Moz.repository.getLogger(this._logName);
+  this._log = Log.repository.getLogger(this._logName);
   this._log.level =
-    Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
+    Log.Level[Svc.Prefs.get("log.logger.network.resources")];
   this.uri = uri;
   this._headers = {};
   this._onComplete = Utils.bind2(this, this._onComplete);
 }
 AsyncResource.prototype = {
   _logName: "Sync.AsyncResource",
 
   // ** {{{ AsyncResource.serverTime }}} **
@@ -260,17 +260,17 @@ AsyncResource.prototype = {
                   channel.URI.spec].join(" ");
       this._log.debug("mesg: " + mesg);
 
       if (mesg.length > 200)
         mesg = mesg.substr(0, 200) + "…";
       this._log.debug(mesg);
 
       // Additionally give the full response body when Trace logging.
-      if (this._log.level <= Log4Moz.Level.Trace)
+      if (this._log.level <= Log.Level.Trace)
         this._log.trace(action + " body: " + data);
 
     } catch(ex) {
       // Got a response, but an exception occurred during processing.
       // This shouldn't occur.
       this._log.warn("Caught unexpected exception " + CommonUtils.exceptionStr(ex) +
                      " in _onComplete.");
       this._log.debug(CommonUtils.stackTrace(ex));
@@ -583,18 +583,18 @@ ChannelListener.prototype = {
  *
  * Optionally pass an array of header names. Each header named
  * in this array will be copied between the channels in the
  * event of a redirect.
  */
 function ChannelNotificationListener(headersToCopy) {
   this._headersToCopy = headersToCopy;
 
-  this._log = Log4Moz.repository.getLogger(this._logName);
-  this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
+  this._log = Log.repository.getLogger(this._logName);
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")];
 }
 ChannelNotificationListener.prototype = {
   _logName: "Sync.Resource",
 
   getInterface: function(aIID) {
     return this.QueryInterface(aIID);
   },
 
@@ -604,17 +604,17 @@ ChannelNotificationListener.prototype = 
         aIID.equals(Ci.nsISupports) ||
         aIID.equals(Ci.nsIChannelEventSink))
       return this;
 
     throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) {
-    let log = Log4Moz.repository.getLogger("Sync.CertListener");
+    let log = Log.repository.getLogger("Sync.CertListener");
     log.warn("Invalid HTTPS certificate encountered!");
 
     // This suppresses the UI warning only. The request is still cancelled.
     return true;
   },
 
   asyncOnChannelRedirect:
     function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
--- a/services/sync/modules/rest.js
+++ b/services/sync/modules/rest.js
@@ -1,15 +1,15 @@
 /* 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://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/constants.js");
 
 this.EXPORTED_SYMBOLS = ["SyncStorageRequest"];
 
 const STORAGE_REQUEST_TIMEOUT = 5 * 60; // 5 minutes
 
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -15,17 +15,17 @@ const CLUSTER_BACKOFF = 5 * 60 * 1000; /
 // How long a key to generate from an old passphrase.
 const PBKDF2_KEY_BYTES = 16;
 
 const CRYPTO_COLLECTION = "crypto";
 const KEYS_WBO = "keys";
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
@@ -312,19 +312,19 @@ Sync11Service.prototype = {
     }
 
     this.status = Status;
     this.identity = Status._authManager;
     this.collectionKeys = new CollectionKeyManager();
 
     this.errorHandler = new ErrorHandler(this);
 
-    this._log = Log4Moz.repository.getLogger("Sync.Service");
+    this._log = Log.repository.getLogger("Sync.Service");
     this._log.level =
-      Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
+      Log.Level[Svc.Prefs.get("log.logger.service.main")];
 
     this._log.info("Loading Weave " + WEAVE_VERSION);
 
     this._clusterManager = new ClusterManager(this);
     this.recordManager = new RecordManager(this);
 
     this.enabled = true;
 
--- a/services/sync/modules/stages/cluster.js
+++ b/services/sync/modules/stages/cluster.js
@@ -1,27 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["ClusterManager"];
 
 const {utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/util.js");
 
 /**
  * Contains code for managing the Sync cluster we are in.
  */
 this.ClusterManager = function ClusterManager(service) {
-  this._log = Log4Moz.repository.getLogger("Sync.Service");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
+  this._log = Log.repository.getLogger("Sync.Service");
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
 
   this.service = service;
 }
 ClusterManager.prototype = {
   get identity() {
     return this.service.identity;
   },
 
--- a/services/sync/modules/stages/enginesync.js
+++ b/services/sync/modules/stages/enginesync.js
@@ -5,30 +5,30 @@
 /**
  * This file contains code for synchronizing engines.
  */
 
 this.EXPORTED_SYMBOLS = ["EngineSynchronizer"];
 
 const {utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/util.js");
 
 /**
  * Perform synchronization of engines.
  *
  * This was originally split out of service.js. The API needs lots of love.
  */
 this.EngineSynchronizer = function EngineSynchronizer(service) {
-  this._log = Log4Moz.repository.getLogger("Sync.Synchronizer");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.synchronizer")];
+  this._log = Log.repository.getLogger("Sync.Synchronizer");
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.synchronizer")];
 
   this.service = service;
 
   this.onComplete = null;
 }
 
 EngineSynchronizer.prototype = {
   sync: function sync() {
--- a/services/sync/modules/status.js
+++ b/services/sync/modules/status.js
@@ -5,22 +5,22 @@
 this.EXPORTED_SYMBOLS = ["Status"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.Status = {
-  _log: Log4Moz.repository.getLogger("Sync.Status"),
+  _log: Log.repository.getLogger("Sync.Status"),
   _authManager: new IdentityManager(),
   ready: false,
 
   get service() {
     return this._service;
   },
 
   set service(code) {
@@ -109,17 +109,17 @@ this.Status = {
     // Logger setup.
     let logPref = PREFS_BRANCH + "log.logger.status";
     let logLevel = "Trace";
     try {
       logLevel = Services.prefs.getCharPref(logPref);
     } catch (ex) {
       // Use default.
     }
-    this._log.level = Log4Moz.Level[logLevel];
+    this._log.level = Log.Level[logLevel];
 
     this._log.info("Resetting Status.");
     this.service = STATUS_OK;
     this._login = LOGIN_SUCCEEDED;
     this._sync = SYNC_SUCCEEDED;
     this._engines = {};
     this.partial = false;
   }
--- a/services/sync/modules/userapi.js
+++ b/services/sync/modules/userapi.js
@@ -5,32 +5,32 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "UserAPI10Client",
 ];
 
 const {utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/util.js");
 
 /**
  * A generic client for the user API 1.0 service.
  *
  * http://docs.services.mozilla.com/reg/apis.html
  *
  * Instances are constructed with the base URI of the service.
  */
 this.UserAPI10Client = function UserAPI10Client(baseURI) {
-  this._log = Log4Moz.repository.getLogger("Sync.UserAPI");
-  this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.userapi")];
+  this._log = Log.repository.getLogger("Sync.UserAPI");
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.userapi")];
 
   this.baseURI = baseURI;
 }
 UserAPI10Client.prototype = {
   USER_CREATE_ERROR_CODES: {
     2: "Incorrect or missing captcha.",
     4: "User exists.",
     6: "JSON parse failure.",
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.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/. */
 
 this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "NetUtil", "PlacesUtils",
                          "FileUtils", "Utils", "Async", "Svc", "Str"];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://services-common/stringbundle.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/async.js", this);
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/FileUtils.jsm", this);
 Cu.import("resource://gre/modules/NetUtil.jsm", this);
--- a/services/sync/tests/unit/head_http_server.js
+++ b/services/sync/tests/unit/head_http_server.js
@@ -1,12 +1,12 @@
 const Cm = Components.manager;
 
 // Shared logging for all HTTP server functions.
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 const SYNC_HTTP_LOGGER = "Sync.Test.Server";
 const SYNC_API_VERSION = "1.1";
 
 // Use the same method that record.js does, which mirrors the server.
 // The server returns timestamps with 1/100 sec granularity. Note that this is
 // subject to change: see Bug 650435.
 function new_timestamp() {
   return Math.round(Date.now() / 10) / 100;
@@ -158,17 +158,17 @@ function ServerCollection(wbos, acceptNe
   this.acceptNew = acceptNew || false;
 
   /*
    * Track modified timestamp.
    * We can't just use the timestamps of contained WBOs: an empty collection
    * has a modified time.
    */
   this.timestamp = timestamp || new_timestamp();
-  this._log = Log4Moz.repository.getLogger(SYNC_HTTP_LOGGER);
+  this._log = Log.repository.getLogger(SYNC_HTTP_LOGGER);
 }
 ServerCollection.prototype = {
 
   /**
    * Convenience accessor for our WBO keys.
    * Excludes deleted items, of course.
    *
    * @param filter
@@ -522,17 +522,17 @@ let SyncServerCallback = {
  * Construct a new test Sync server. Takes a callback object (e.g.,
  * SyncServerCallback) as input.
  */
 function SyncServer(callback) {
   this.callback = callback || {__proto__: SyncServerCallback};
   this.server   = new HttpServer();
   this.started  = false;
   this.users    = {};
-  this._log     = Log4Moz.repository.getLogger(SYNC_HTTP_LOGGER);
+  this._log     = Log.repository.getLogger(SYNC_HTTP_LOGGER);
 
   // Install our own default handler. This allows us to mess around with the
   // whole URL space.
   let handler = this.server._handler;
   handler._handleDefault = this.handleDefault.bind(this, handler);
 }
 SyncServer.prototype = {
   server: null,    // HttpServer.
--- a/services/sync/tests/unit/test_addons_engine.js
+++ b/services/sync/tests/unit/test_addons_engine.js
@@ -235,23 +235,23 @@ add_test(function test_disabled_install_
 add_test(function cleanup() {
   // There's an xpcom-shutdown hook for this, but let's give this a shot.
   reconciler.stopListening();
   run_next_test();
 });
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Addons").level =
-    Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Store.Addons").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Tracker.Addons").level =
-    Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.AddonsRepository").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Addons").level =
+    Log.Level.Trace;
+  Log.repository.getLogger("Sync.Store.Addons").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Tracker.Addons").level =
+    Log.Level.Trace;
+  Log.repository.getLogger("Sync.AddonsRepository").level =
+    Log.Level.Trace;
 
   reconciler.startListening();
 
   // Don't flush to disk in the middle of an event listener!
   // This causes test hangs on WinXP.
   reconciler._shouldPersist = false;
 
   advance_test();
--- a/services/sync/tests/unit/test_addons_reconciler.js
+++ b/services/sync/tests/unit/test_addons_reconciler.js
@@ -9,19 +9,19 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 loadAddonTestFunctions();
 startupManager();
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.AddonsReconciler").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.AddonsReconciler").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.AddonsReconciler").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.AddonsReconciler").level =
+    Log.Level.Trace;
 
   Svc.Prefs.set("engine.addons", true);
   Service.engineManager.register(AddonsEngine);
 
   run_next_test();
 }
 
 add_test(function test_defaults() {
--- a/services/sync/tests/unit/test_addons_store.js
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -62,19 +62,19 @@ function createAndStartHTTPServer(port) 
     _("Got exception starting HTTP server on port " + port);
     _("Error: " + Utils.exceptionStr(ex));
     do_throw(ex);
   }
 }
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Addons").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.AddonsRepository").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.AddonsRepository").level =
+    Log.Level.Trace;
 
   reconciler.startListening();
 
   // Don't flush to disk in the middle of an event listener!
   // This causes test hangs on WinXP.
   reconciler._shouldPersist = false;
 
   run_next_test();
--- a/services/sync/tests/unit/test_addons_tracker.js
+++ b/services/sync/tests/unit/test_addons_tracker.js
@@ -38,19 +38,19 @@ function cleanup_and_advance() {
   reconciler.saveState(null, cb);
   cb.wait();
 
   run_next_test();
 }
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Addons").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.AddonsReconciler").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.AddonsReconciler").level =
+    Log.Level.Trace;
 
   cleanup_and_advance();
 }
 
 add_test(function test_empty() {
   _("Verify the tracker is empty to start with.");
 
   do_check_eq(0, Object.keys(tracker.changedIDs).length);
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
 Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 Service.engineManager.register(BookmarksEngine);
--- a/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js
+++ b/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that Sync can correctly handle a legacy microsummary record
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 const GENERATORURI_ANNO = "microsummary/generatorURI";
 const STATICTITLE_ANNO = "bookmarks/staticTitle";
@@ -40,17 +40,17 @@ function run_test() {
   Service.engineManager.register(BookmarksEngine);
   let engine = Service.engineManager.get("bookmarks");
   let store = engine._store;
 
   // Clean up.
   store.wipe();
 
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Bookmarks").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
 
   _("Create a microsummarized bookmark.");
   let id = newMicrosummary(TEST_URL, TEST_TITLE);
   let guid = store.GUIDForId(id);
   _("GUID: " + guid);
   do_check_true(!!guid);
 
   _("Create record object and verify that it's sane.");
--- a/services/sync/tests/unit/test_bookmark_livemarks.js
+++ b/services/sync/tests/unit/test_bookmark_livemarks.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://testing-common/services-common/utils.js");
 
@@ -65,18 +65,18 @@ function makeLivemark(p, mintGUID) {
     b.id = Utils.makeGUID();
 
   return b;
 }
 
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Bookmarks").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Store.Bookmarks").level  = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Store.Bookmarks").level  = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_livemark_descriptions() {
   let record = record631361.payload;
 
   function doRecord(r) {
--- a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js
+++ b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js
@@ -6,18 +6,18 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 let engine = new BookmarksEngine(Service);
 let store = engine._store;
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Bookmarks").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Store.Bookmarks").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace;
 
   let tagRecord = new BookmarkQuery("bookmarks", "abcdefabcdef");
   let uri = "place:folder=499&type=7&queryType=1";
   tagRecord.queryId = "MagicTags";
   tagRecord.parentName = "Bookmarks Toolbar";
   tagRecord.bmkUri = uri;
   tagRecord.title = "tagtag";
   tagRecord.folderName = "bar";
--- a/services/sync/tests/unit/test_bookmark_record.js
+++ b/services/sync/tests/unit/test_bookmark_record.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 function prepareBookmarkItem(collection, id) {
   let b = new Bookmark(collection, id);
@@ -15,18 +15,18 @@ function prepareBookmarkItem(collection,
 }
 
 function run_test() {
   Service.identity.username = "john@example.com";
   Service.identity.syncKey = "abcdeabcdeabcdeabcdeabcdea";
   generateNewKeys(Service.collectionKeys);
   let keyBundle = Service.identity.syncKeyBundle;
 
-  let log = Log4Moz.repository.getLogger("Test");
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let log = Log.repository.getLogger("Test");
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   log.info("Creating a record");
 
   let u = "http://localhost:8080/storage/bookmarks/foo";
   let placesItem = new PlacesItem("bookmarks", "foo", "bookmark");
   let bookmarkItem = prepareBookmarkItem("bookmarks", "foo");
 
   log.info("Checking getTypeObject");
--- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
+++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
 var IOService = Cc["@mozilla.org/network/io-service;1"]
@@ -222,14 +222,14 @@ add_test(function test_smart_bookmarks_d
     server.stop(do_test_finished);
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
   }
 });
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Bookmarks").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
 
   generateNewKeys(Service.collectionKeys);
 
   run_next_test();
 }
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -162,17 +162,17 @@ function test_onItemMoved() {
     Svc.Obs.notify("weave:engine:stop-tracking");
   }
 
 }
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Engine.Bookmarks").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Store.Bookmarks").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Tracker.Bookmarks").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Tracker.Bookmarks").level = Log.Level.Trace;
 
   test_tracking();
   test_onItemChanged();
   test_onItemMoved();
 }
 
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -546,11 +546,11 @@ add_test(function test_receive_display_u
 
   Svc.Obs.add(ev, handler);
 
   do_check_true(engine.processIncomingCommands());
 });
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Clients").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Clients").level = Log.Level.Trace;
   run_next_test();
 }
--- a/services/sync/tests/unit/test_corrupt_keys.js
+++ b/services/sync/tests/unit/test_corrupt_keys.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/tabs.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
@@ -198,18 +198,18 @@ add_task(function test_locally_changed_k
     Svc.Prefs.resetBranch("");
     let deferred = Promise.defer();
     server.stop(deferred.resolve);
     yield deferred.promise;
   }
 });
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   run_next_test();
 }
 
 /**
  * Asynchronously check a url is visited.
  * @param url the url
  * @return {Promise}
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -46,19 +46,19 @@ engineManager.register(CatapultEngine);
 
 // This relies on Service/ErrorHandler being a singleton. Fixing this will take
 // a lot of work.
 let errorHandler = Service.errorHandler;
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 function generateCredentialsChangedFailure() {
   // Make sync fail due to changed credentials. We simply re-encrypt
   // the keys with a different Sync Key, without changing the local one.
   let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562");
@@ -1578,17 +1578,17 @@ add_test(function test_sync_engine_gener
   let server = sync_httpd_setup();
 
   let engine = engineManager.get("catapult");
   engine.enabled = true;
   engine.sync = function sync() {
     Svc.Obs.notify("weave:engine:sync:error", "", "catapult");
   };
 
-  let log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
+  let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
   do_check_eq(Status.engines["catapult"], undefined);
 
   // Don't wait for reset-file-log until the sync is underway.
   // This avoids us catching a delayed notification from an earlier test.
   Svc.Obs.add("weave:engine:sync:finish", function onEngineFinish() {
     Svc.Obs.remove("weave:engine:sync:finish", onEngineFinish);
@@ -1618,17 +1618,17 @@ add_test(function test_sync_engine_gener
   do_check_true(setUp(server));
   Service.sync();
 });
 
 add_test(function test_logs_on_sync_error_despite_shouldReportError() {
   _("Ensure that an error is still logged when weave:service:sync:error " +
     "is notified, despite shouldReportError returning false.");
 
-  let log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
+  let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
   log.info("TESTING");
 
   // Ensure that we report no error.
   Status.login = MASTER_PASSWORD_LOCKED;
   do_check_false(errorHandler.shouldReportError());
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
@@ -1646,17 +1646,17 @@ add_test(function test_logs_on_sync_erro
   });
   Svc.Obs.notify("weave:service:sync:error", {});
 });
 
 add_test(function test_logs_on_login_error_despite_shouldReportError() {
   _("Ensure that an error is still logged when weave:service:login:error " +
     "is notified, despite shouldReportError returning false.");
 
-  let log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
+  let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
   log.info("TESTING");
 
   // Ensure that we report no error.
   Status.login = MASTER_PASSWORD_LOCKED;
   do_check_false(errorHandler.shouldReportError());
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
@@ -1682,17 +1682,17 @@ add_test(function test_engine_applyFaile
 
   let engine = engineManager.get("catapult");
   engine.enabled = true;
   delete engine.exception;
   engine.sync = function sync() {
     Svc.Obs.notify("weave:engine:sync:applied", {newFailed:1}, "catapult");
   };
 
-  let log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
+  let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     do_check_eq(Status.engines["catapult"], ENGINE_APPLY_FAIL);
     do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
 
--- a/services/sync/tests/unit/test_errorhandler_filelog.js
+++ b/services/sync/tests/unit/test_errorhandler_filelog.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 const logsdir            = FileUtils.getDir("ProfD", ["weave", "logs"], true);
 const LOG_PREFIX_SUCCESS = "success-";
 const LOG_PREFIX_ERROR   = "error-";
 
@@ -24,50 +24,50 @@ let errorHandler = Service.errorHandler;
 
 function setLastSync(lastSyncValue) {
   Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
 }
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_noOutput() {
   // Ensure that the log appender won't print anything.
-  errorHandler._logAppender.level = Log4Moz.Level.Fatal + 1;
+  errorHandler._logAppender.level = Log.Level.Fatal + 1;
 
   // Clear log output from startup.
   Svc.Prefs.set("log.appender.file.logOnSuccess", false);
   Svc.Obs.notify("weave:service:sync:finish");
 
   // Clear again without having issued any output.
   Svc.Prefs.set("log.appender.file.logOnSuccess", true);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
-    errorHandler._logAppender.level = Log4Moz.Level.Trace;
+    errorHandler._logAppender.level = Log.Level.Trace;
     Svc.Prefs.resetBranch("");
     run_next_test();
   });
 
   // Fake a successful sync.
   Svc.Obs.notify("weave:service:sync:finish");
 });
 
 add_test(function test_logOnSuccess_false() {
   Svc.Prefs.set("log.appender.file.logOnSuccess", false);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   log.info("this won't show up");
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
     // No log file was written.
     do_check_false(logsdir.directoryEntries.hasMoreElements());
 
     Svc.Prefs.resetBranch("");
@@ -84,17 +84,17 @@ function readFile(file, callback) {
                                                inputStream.available());
     callback(statusCode, data);
   });
 }
 
 add_test(function test_logOnSuccess_true() {
   Svc.Prefs.set("log.appender.file.logOnSuccess", true);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   const MESSAGE = "this WILL show up";
   log.info(MESSAGE);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
@@ -125,17 +125,17 @@ add_test(function test_logOnSuccess_true
 
   // Fake a successful sync.
   Svc.Obs.notify("weave:service:sync:finish");
 });
 
 add_test(function test_sync_error_logOnError_false() {
   Svc.Prefs.set("log.appender.file.logOnError", false);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   log.info("this won't show up");
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
     // No log file was written.
     do_check_false(logsdir.directoryEntries.hasMoreElements());
 
     Svc.Prefs.resetBranch("");
@@ -145,17 +145,17 @@ add_test(function test_sync_error_logOnE
   // Fake an unsuccessful sync due to prolonged failure.
   setLastSync(PROLONGED_ERROR_DURATION);
   Svc.Obs.notify("weave:service:sync:error");
 });
 
 add_test(function test_sync_error_logOnError_true() {
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   const MESSAGE = "this WILL show up";
   log.info(MESSAGE);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
@@ -187,17 +187,17 @@ add_test(function test_sync_error_logOnE
   // Fake an unsuccessful sync due to prolonged failure.
   setLastSync(PROLONGED_ERROR_DURATION);
   Svc.Obs.notify("weave:service:sync:error");
 });
 
 add_test(function test_login_error_logOnError_false() {
   Svc.Prefs.set("log.appender.file.logOnError", false);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   log.info("this won't show up");
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
     // No log file was written.
     do_check_false(logsdir.directoryEntries.hasMoreElements());
 
     Svc.Prefs.resetBranch("");
@@ -207,17 +207,17 @@ add_test(function test_login_error_logOn
   // Fake an unsuccessful login due to prolonged failure.
   setLastSync(PROLONGED_ERROR_DURATION);
   Svc.Obs.notify("weave:service:login:error");
 });
 
 add_test(function test_login_error_logOnError_true() {
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
-  let log = Log4Moz.repository.getLogger("Sync.Test.FileLog");
+  let log = Log.repository.getLogger("Sync.Test.FileLog");
   const MESSAGE = "this WILL show up";
   log.info(MESSAGE);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
--- a/services/sync/tests/unit/test_forms_tracker.js
+++ b/services/sync/tests/unit/test_forms_tracker.js
@@ -1,25 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines/forms.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 function run_test() {
   _("Verify we've got an empty tracker to work with.");
   let engine = new FormEngine(Service);
   let tracker = engine._tracker;
   // Don't do asynchronous writes.
   tracker.persistChangedIDs = false;
 
   do_check_empty(tracker.changedIDs);
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   function addEntry(name, value) {
     engine._store.create({name: name, value: value});
   }
   function removeEntry(name, value) {
     guid = engine._findDupe({name: name, value: value});
     engine._store.remove({id: guid});
   }
--- a/services/sync/tests/unit/test_history_tracker.js
+++ b/services/sync/tests/unit/test_history_tracker.js
@@ -59,17 +59,17 @@ function addVisit() {
 
   // Spin the event loop to embed this async call in a sync API.
   cb.wait();
   return uri;
 }
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Tracker.History").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Tracker.History").level = Log.Level.Trace;
   run_next_test();
 }
 
 add_test(function test_empty() {
   _("Verify we've got an empty, disabled tracker to work with.");
   do_check_empty(tracker.changedIDs);
   do_check_eq(tracker.score, 0);
   do_check_false(tracker._enabled);
--- a/services/sync/tests/unit/test_httpd_sync_server.js
+++ b/services/sync/tests/unit/test_httpd_sync_server.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-sync/util.js");
 
 function run_test() {
-  Log4Moz.repository.getLogger("Sync.Test.Server").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Test.Server").level = Log.Level.Trace;
   initTestLogging();
   run_next_test();
 }
 
 add_test(function test_creation() {
   // Explicit callback for this one.
   let server = new SyncServer({
     __proto__: SyncServerCallback,
--- a/services/sync/tests/unit/test_identity_manager.js
+++ b/services/sync/tests/unit/test_identity_manager.js
@@ -4,17 +4,17 @@
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/util.js");
 
 let identity = new IdentityManager();
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Identity").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Identity").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_username_from_account() {
   _("Ensure usernameFromAccount works properly.");
 
   do_check_eq(identity.usernameFromAccount(null), null);
--- a/services/sync/tests/unit/test_interval_triggers.js
+++ b/services/sync/tests/unit/test_interval_triggers.js
@@ -44,18 +44,18 @@ function setUp(server) {
   let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
   serverKeys.encrypt(Service.identity.syncKeyBundle);
   return serverKeys.upload(Service.resource(Service.cryptoKeysURL));
 }
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_successful_sync_adjustSyncInterval() {
   _("Test successful sync calling adjustSyncInterval");
   let syncSuccesses = 0;
   function onSyncFinish() {
--- a/services/sync/tests/unit/test_jpakeclient.js
+++ b/services/sync/tests/unit/test_jpakeclient.js
@@ -1,9 +1,9 @@
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/jpakeclient.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 const JPAKE_LENGTH_SECRET     = 8;
 const JPAKE_LENGTH_CLIENTID   = 256;
@@ -184,19 +184,19 @@ function run_test() {
   // Ensure PSM is initialized.
   Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
 
   // Simulate Sync setup with credentials in place. We want to make
   // sure the J-PAKE requests don't include those data.
   setBasicCredentials("johndoe", "ilovejane");
 
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.JPAKEClient").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Common.RESTRequest").level =
-    Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.JPAKEClient").level = Log.Level.Trace;
+  Log.repository.getLogger("Common.RESTRequest").level =
+    Log.Level.Trace;
   run_next_test();
 }
 
 
 add_test(function test_success_receiveNoPIN() {
   _("Test a successful exchange started by receiveNoPIN().");
 
   let snd = new JPAKEClient({
--- a/services/sync/tests/unit/test_keys.js
+++ b/services/sync/tests/unit/test_keys.js
@@ -167,18 +167,18 @@ add_test(function test_keymanager() {
   let obj = new SyncKeyBundle(username, testKey);
   do_check_eq(hmacKey, obj.hmacKey);
   do_check_eq(encryptKey, obj.encryptionKey);
 
   run_next_test();
 });
 
 add_test(function test_collections_manager() {
-  let log = Log4Moz.repository.getLogger("Test");
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let log = Log.repository.getLogger("Test");
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let identity = new IdentityManager();
 
   identity.account = "john@example.com";
   identity.syncKey = "a-bbbbb-ccccc-ddddd-eeeee-fffff";
 
   let keyBundle = identity.syncKeyBundle;
 
--- a/services/sync/tests/unit/test_node_reassignment.js
+++ b/services/sync/tests/unit/test_node_reassignment.js
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 _("Test that node reassignment responses are respected on all kinds of " +
   "requests.");
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/rotaryengine.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 Service.engineManager.clear();
 
 function run_test() {
-  Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.ErrorHandler").level  = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Resource").level      = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.RESTRequest").level   = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Service").level       = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.ErrorHandler").level  = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Resource").level      = Log.Level.Trace;
+  Log.repository.getLogger("Sync.RESTRequest").level   = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level       = Log.Level.Trace;
+  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
   initTestLogging();
 
   Service.engineManager.register(RotaryEngine);
 
   // None of the failures in this file should result in a UI error.
   function onUIError() {
     do_throw("Errors should not be presented in the UI.");
   }
--- a/services/sync/tests/unit/test_password_store.js
+++ b/services/sync/tests/unit/test_password_store.js
@@ -2,18 +2,18 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-sync/engines/passwords.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Engine.Passwords").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Store.Passwords").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Engine.Passwords").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Store.Passwords").level = Log.Level.Trace;
 
   const BOGUS_GUID_A = "zzzzzzzzzzzz";
   const BOGUS_GUID_B = "yyyyyyyyyyyy";
   let recordA = {id: BOGUS_GUID_A,
                   hostname: "http://foo.bar.com",
                   formSubmitURL: "http://foo.bar.com/baz",
                   httpRealm: "secure",
                   username: "john",
--- a/services/sync/tests/unit/test_records_crypto.js
+++ b/services/sync/tests/unit/test_records_crypto.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 let cryptoWrap;
@@ -30,18 +30,18 @@ function run_test() {
   let server;
   do_test_pending();
 
   Service.identity.username = "john@example.com";
   Service.identity.syncKey = "a-abcde-abcde-abcde-abcde-abcde";
   let keyBundle = Service.identity.syncKeyBundle;
 
   try {
-    let log = Log4Moz.repository.getLogger("Test");
-    Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+    let log = Log.repository.getLogger("Test");
+    Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
     log.info("Setting up server and authenticator");
 
     server = httpd_setup({"/steam/resource": crypted_resource_handler});
 
     log.info("Creating a record");
 
     let cryptoUri = "http://localhost:8080/crypto/steam";
--- a/services/sync/tests/unit/test_resource.js
+++ b/services/sync/tests/unit/test_resource.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 
 let logger;
 
 let fetched = false;
@@ -148,18 +148,18 @@ function server_headers(metadata, respon
   response.bodyOutputStream.write(body, body.length);
 }
 
 function run_test() {
   initTestLogging("Trace");
 
   do_test_pending();
 
-  logger = Log4Moz.repository.getLogger('Test');
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  logger = Log.repository.getLogger('Test');
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let server = httpd_setup({
     "/open": server_open,
     "/protected": server_protected,
     "/404": server_404,
     "/upload": server_upload,
     "/delete": server_delete,
     "/json": server_json,
--- a/services/sync/tests/unit/test_resource_async.js
+++ b/services/sync/tests/unit/test_resource_async.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 
 let logger;
 
 let fetched = false;
@@ -148,18 +148,18 @@ function server_headers(metadata, respon
   response.bodyOutputStream.write(body, body.length);
 }
 
 let quotaValue;
 Observers.add("weave:service:quota:remaining",
               function (subject) { quotaValue = subject; });
 
 function run_test() {
-  logger = Log4Moz.repository.getLogger('Test');
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  logger = Log.repository.getLogger('Test');
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   Svc.Prefs.set("network.numRetries", 1); // speed up test
   run_next_test();
 }
 
 // This apparently has to come first in order for our PAC URL to be hit.
 // Don't put any other HTTP requests earlier in the file!
 add_test(function test_proxy_auth_redirect() {
--- a/services/sync/tests/unit/test_resource_ua.js
+++ b/services/sync/tests/unit/test_resource_ua.js
@@ -19,17 +19,17 @@ let ua;
 function uaHandler(f) {
   return function(request, response) {
     ua = request.getHeader("User-Agent");
     return f(request, response);
   };
 }
 
 function run_test() {
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
   meta_global = new ServerWBO('global');
   server = httpd_setup({
     "/1.1/johndoe/info/collections": uaHandler(collectionsHelper.handler),
     "/1.1/johndoe/storage/meta/global": uaHandler(meta_global.handler()),
   });
 
   setBasicCredentials("johndoe", "ilovejane");
   Service.serverURL = server.baseURI + "/";
--- a/services/sync/tests/unit/test_score_triggers.js
+++ b/services/sync/tests/unit/test_score_triggers.js
@@ -45,17 +45,17 @@ function sync_httpd_setup() {
 
 function setUp(server) {
   new SyncTestingInfrastructure(server, "johndoe", "ilovejane", "sekrit");
 }
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_tracker_score_updated() {
   let scoreUpdated = 0;
 
   function onScoreUpdated() {
--- a/services/sync/tests/unit/test_sendcredentials_controller.js
+++ b/services/sync/tests/unit/test_sendcredentials_controller.js
@@ -7,18 +7,18 @@ Cu.import("resource://services-sync/serv
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function run_test() {
   setBasicCredentials("johndoe", "ilovejane", Utils.generatePassphrase());
   Service.serverURL  = "http://weave.server/";
 
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.SendCredentialsController").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.SendCredentialsController").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
   run_next_test();
 }
 
 function make_sendCredentials_test(topic) {
   return function test_sendCredentials() {
     _("Test sending credentials on " + topic + " observer notification.");
 
     let sendAndCompleteCalled = false;
--- a/services/sync/tests/unit/test_service_changePassword.js
+++ b/services/sync/tests/unit/test_service_changePassword.js
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Resource").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_change_password() {
   let requestBody;
   let server;
 
--- a/services/sync/tests/unit/test_service_detect_upgrade.js
+++ b/services/sync/tests/unit/test_service_detect_upgrade.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/engines/tabs.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
@@ -287,13 +287,13 @@ add_test(function v5_upgrade() {
 
   } finally {
     Svc.Prefs.resetBranch("");
     server.stop(run_next_test);
   }
 });
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   run_next_test();
 }
--- a/services/sync/tests/unit/test_service_getStorageInfo.js
+++ b/services/sync/tests/unit/test_service_getStorageInfo.js
@@ -7,18 +7,18 @@ Cu.import("resource://services-sync/serv
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 let collections = {steam:  65.11328,
                    petrol: 82.488281,
                    diesel: 2.25488281};
 
 function run_test() {
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.StorageRequest").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.StorageRequest").level = Log.Level.Trace;
   initTestLogging();
 
   setBasicCredentials("johndoe", "ilovejane");
 
   run_next_test();
 }
 
 add_test(function test_success() {
--- a/services/sync/tests/unit/test_service_login.js
+++ b/services/sync/tests/unit/test_service_login.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function login_handling(handler) {
   return function (request, response) {
@@ -18,18 +18,18 @@ function login_handling(handler) {
       response.setStatusLine(request.httpVersion, 401, "Unauthorized");
       response.setHeader("Content-Type", "text/plain");
       response.bodyOutputStream.write(body, body.length);
     }
   };
 }
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   run_next_test();
 }
 
 add_test(function test_offline() {
   try {
     _("The right bits are set when we're offline.");
     Services.io.offline = true;
--- a/services/sync/tests/unit/test_service_sync_401.js
+++ b/services/sync/tests/unit/test_service_sync_401.js
@@ -15,18 +15,18 @@ function login_handling(handler) {
       let body = "Unauthorized";
       response.setStatusLine(request.httpVersion, 401, "Unauthorized");
       response.bodyOutputStream.write(body, body.length);
     }
   };
 }
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let collectionsHelper = track_collections_helper();
   let upd = collectionsHelper.with_updated_collection;
   let collections = collectionsHelper.collections;
 
   do_test_pending();
   let server = httpd_setup({
     "/1.1/johndoe/storage/crypto/keys": upd("crypto", new ServerWBO("keys").handler()),
--- a/services/sync/tests/unit/test_service_sync_locked.js
+++ b/services/sync/tests/unit/test_service_sync_locked.js
@@ -11,17 +11,17 @@ function run_test() {
   function augmentLogger(old) {
     let d = old.debug;
     let i = old.info;
     old.debug = function(m) { debug.push(m); d.call(old, m); }
     old.info  = function(m) { info.push(m);  i.call(old, m); }
     return old;
   }
 
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   augmentLogger(Service._log);
 
   // Avoid daily ping
   Svc.Prefs.set("lastPing", Math.floor(Date.now() / 1000));
 
   _("Check that sync will log appropriately if already in 'progress'.");
   Service._locked = true;
--- a/services/sync/tests/unit/test_service_sync_remoteSetup.js
+++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/fakeservices.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let guidSvc = new FakeGUIDService();
   let clients = new ServerCollection();
   let meta_global = new ServerWBO('global');
 
   let collectionsHelper = track_collections_helper();
   let upd = collectionsHelper.with_updated_collection;
   let collections = collectionsHelper.collections;
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
+++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
@@ -77,18 +77,18 @@ function setUp(server) {
   return serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success;
 }
 
 const PAYLOAD = 42;
 
 
 function run_test() {
   initTestLogging("Trace");
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_newAccount() {
   _("Test: New account does not disable locally enabled engines.");
   let engine = Service.engineManager.get("steam");
   let server = sync_httpd_setup({
--- a/services/sync/tests/unit/test_service_verifyLogin.js
+++ b/services/sync/tests/unit/test_service_verifyLogin.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function login_handling(handler) {
   return function (request, response) {
     if (basic_auth_matches(request, "johndoe", "ilovejane")) {
@@ -22,18 +22,18 @@ function login_handling(handler) {
 function service_unavailable(request, response) {
   let body = "Service Unavailable";
   response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
   response.setHeader("Retry-After", "42");
   response.bodyOutputStream.write(body, body.length);
 }
 
 function run_test() {
-  let logger = Log4Moz.repository.rootLogger;
-  Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
+  let logger = Log.repository.rootLogger;
+  Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   // This test expects a clean slate -- no saved passphrase.
   Services.logins.removeAllLogins();
   let johnHelper = track_collections_helper();
   let johnU      = johnHelper.with_updated_collection;
   let johnColls  = johnHelper.collections;
 
   do_test_pending();
--- a/services/sync/tests/unit/test_syncscheduler.js
+++ b/services/sync/tests/unit/test_syncscheduler.js
@@ -71,18 +71,18 @@ function cleanUpAndGo(server) {
       run_next_test();
     }
   });
 }
 
 function run_test() {
   initTestLogging("Trace");
 
-  Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
-  Log4Moz.repository.getLogger("Sync.scheduler").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+  Log.repository.getLogger("Sync.scheduler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_prefAttributes() {
   _("Test various attributes corresponding to preferences.");
 
   const INTERVAL = 42 * 60 * 1000;   // 42 minutes
--- a/services/sync/tests/unit/test_syncstoragerequest.js
+++ b/services/sync/tests/unit/test_syncstoragerequest.js
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/rest.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function run_test() {
-  Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
+  Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace;
   initTestLogging();
 
   run_next_test();
 }
 
 add_test(function test_user_agent_desktop() {
   let handler = httpd_handler(200, "OK");
   let server = httpd_setup({"/resource": handler});
--- a/testing/marionette/components/marionettecomponent.js
+++ b/testing/marionette/components/marionettecomponent.js
@@ -14,41 +14,41 @@ const MARIONETTE_FORCELOCAL_PREF = 'mari
 
 this.ServerSocket = CC("@mozilla.org/network/server-socket;1",
                        "nsIServerSocket",
                        "initSpecialConnection");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                .getService(Ci.mozIJSSubScriptLoader);
 
 function MarionetteComponent() {
   this._loaded = false;
   this.observerService = Services.obs;
 
   // set up the logger
-  this.logger = Log4Moz.repository.getLogger("Marionette");
-  this.logger.level = Log4Moz.Level["Trace"];
+  this.logger = Log.repository.getLogger("Marionette");
+  this.logger.level = Log.Level["Trace"];
   let logf = FileUtils.getFile('ProfD', ['marionette.log']);
 
   let dumper = false;
-  let formatter = new Log4Moz.BasicFormatter();
-  this.logger.addAppender(new Log4Moz.BoundedFileAppender(logf.path, formatter));
+  let formatter = new Log.BasicFormatter();
+  this.logger.addAppender(new Log.BoundedFileAppender(logf.path, formatter));
 #ifdef DEBUG
   dumper = true;
 #endif
 #ifdef MOZ_B2G
   dumper = true;
 #endif
   if (dumper) {
-    this.logger.addAppender(new Log4Moz.DumpAppender(formatter));
+    this.logger.addAppender(new Log.DumpAppender(formatter));
   }
   this.logger.info("MarionetteComponent loaded");
 }
 
 MarionetteComponent.prototype = {
   classDescription: "Marionette component",
   classID: MARIONETTE_CID,
   contractID: MARIONETTE_CONTRACTID,
@@ -88,35 +88,59 @@ MarionetteComponent.prototype = {
 #ifdef ENABLE_MARIONETTE
         let enabledPref = false;
         try {
           enabledPref = Services.prefs.getBoolPref(MARIONETTE_ENABLED_PREF);
         } catch(e) {}
         if (enabledPref) {
           this.enabled = true;
           this.logger.info("marionette enabled via build flag and pref");
+
+          // We want to suppress the modal dialog that's shown
+          // when starting up in safe-mode to enable testing.
+          if (Services.appinfo.inSafeMode) {
+            this.observerService.addObserver(this, "domwindowopened", false);
+          }
         }
         else {
           this.logger.info("marionette not enabled via pref");
         }
 #endif
         break;
       case "final-ui-startup":
         this.finalUiStartup = true;
         this.observerService.removeObserver(this, aTopic);
         this.observerService.addObserver(this, "xpcom-shutdown", false);
         this.init();
         break;
+      case "domwindowopened":
+        this.observerService.removeObserver(this, aTopic);
+        this._suppressSafeModeDialog(aSubject);
+        break;
       case "xpcom-shutdown":
         this.observerService.removeObserver(this, "xpcom-shutdown");
         this.uninit();
         break;
     }
   },
 
+  _suppressSafeModeDialog: function mc_suppressSafeModeDialog(aWindow) {
+    // Wait for the modal dialog to finish loading.
+    aWindow.addEventListener("load", function onLoad() {
+      aWindow.removeEventListener("load", onLoad);
+
+      if (aWindow.document.getElementById("safeModeDialog")) {
+        aWindow.setTimeout(() => {
+          // Accept the dialog to start in safe-mode.
+          aWindow.document.documentElement.getButton("accept").click();
+        });
+      }
+    });
+  },
+
   init: function mc_init() {
     if (!this._loaded && this.enabled && this.finalUiStartup) {
       this._loaded = true;
 
       let marionette_forcelocal = this.appName == 'B2G' ? false : true;
       try {
         marionette_forcelocal = Services.prefs.getBoolPref(MARIONETTE_FORCELOCAL_PREF);
       }
--- a/testing/marionette/marionette-frame-manager.js
+++ b/testing/marionette/marionette-frame-manager.js
@@ -5,18 +5,18 @@
 this.EXPORTED_SYMBOLS = [
   "FrameManager"
 ];
 
 let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-Cu.import("resource://gre/modules/services-common/log4moz.js");
-let logger = Log4Moz.repository.getLogger("Marionette");
+Cu.import("resource://gre/modules/Log.jsm");
+let logger = Log.repository.getLogger("Marionette");
 
 //list of OOP frames that has the frame script loaded
 let remoteFrames = [];
 
 /**
  * An object representing a frame that Marionette has loaded a
  * frame script in.
  */
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -67,18 +67,18 @@ let nextTouchId = 1000;
 let touchIds = {};
 // last touch for each fingerId
 let multiLast = {};
 let lastCoordinates = null;
 let isTap = false;
 // whether to send mouse event
 let mouseEventsOnly = false;
 
-Cu.import("resource://gre/modules/services-common/log4moz.js");
-let logger = Log4Moz.repository.getLogger("Marionette");
+Cu.import("resource://gre/modules/Log.jsm");
+let logger = Log.repository.getLogger("Marionette");
 logger.info("loaded marionette-listener.js");
 let modalHandler = function() {
   sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
   let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
   if (isLocal) {
     previousFrame = curFrame;
   }
   curFrame = content;
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -2,18 +2,18 @@
  * 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 FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
 
 // import logger
-Cu.import("resource://gre/modules/services-common/log4moz.js");
-let logger = Log4Moz.repository.getLogger("Marionette");
+Cu.import("resource://gre/modules/Log.jsm");
+let logger = Log.repository.getLogger("Marionette");
 logger.info('marionette-server.js loaded');
 
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                .getService(Ci.mozIJSSubScriptLoader);
 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
 loader.loadSubScript("chrome://marionette/content/marionette-common.js");
 Cu.import("resource://gre/modules/Services.jsm");
 loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js");
--- a/testing/talos/mach_commands.py
+++ b/testing/talos/mach_commands.py
@@ -5,16 +5,17 @@
 # Integrates Talos mozharness with mach
 
 from __future__ import print_function, unicode_literals
 
 import os
 import sys
 import json
 import which
+import socket
 
 from mozbuild.base import (
     MozbuildObject,
     MachCommandBase
 )
 
 from mach.decorators import (
     CommandArgument,
@@ -84,17 +85,17 @@ class TalosRunner(MozbuildObject):
             'virtualenv_path': self.virtualenv_path,
             'pypi_url': 'http://pypi.python.org/simple',
             'use_talos_json': True,
             'base_work_dir': self.mozharness_dir,
             'exes': {
                 'python': self.python_interp,
                 'virtualenv': [self.python_interp, self.virtualenv_script]
             },
-            'title': os.uname()[1].lower().split('.')[0],
+            'title': socket.gethostname(),
             'default_actions': [
                 'clone-talos',
                 'create-virtualenv',
                 'run-tests',
             ],
             'python_webserver': True
         }
 
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -377,18 +377,20 @@ File.prototype = {
    *
    * @return {promise}
    * @resolves {number} The number of bytes effectively read.
    * @rejects {OS.File.Error}
    */
   readTo: function readTo(buffer, options = {}) {
     // If |buffer| is a typed array and there is no |bytes| options, we
     // need to extract the |byteLength| now, as it will be lost by
-    // communication
-    if (isTypedArray(buffer) && !("bytes" in options)) {
+    // communication.
+    // Options might be a nullish value, so better check for that before using
+    // the |in| operator.
+    if (isTypedArray(buffer) && !(options && "bytes" in options)) {
       // Preserve reference to option |outExecutionDuration|, if it is passed.
       options = clone(options, ["outExecutionDuration"]);
       options.bytes = buffer.byteLength;
     }
     // Note: Type.void_t.out_ptr.toMsg ensures that
     // - the buffer is effectively shared (not neutered) between both
     //   threads;
     // - we take care of any |byteOffset|.
@@ -414,18 +416,20 @@ File.prototype = {
    * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
    * if |buffer| is a C pointer.
    *
    * @return {number} The number of bytes actually written.
    */
   write: function write(buffer, options = {}) {
     // If |buffer| is a typed array and there is no |bytes| options,
     // we need to extract the |byteLength| now, as it will be lost
-    // by communication
-    if (isTypedArray(buffer)) {
+    // by communication.
+    // Options might be a nullish value, so better check for that before using
+    // the |in| operator.
+    if (isTypedArray(buffer) && !(options && "bytes" in options)) {
       // Preserve reference to option |outExecutionDuration|, if it is passed.
       options = clone(options, ["outExecutionDuration"]);
       options.bytes = buffer.byteLength;
     }
     // Note: Type.void_t.out_ptr.toMsg ensures that
     // - the buffer is effectively shared (not neutered) between both
     //   threads;
     // - we take care of any |byteOffset|.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_bytes.js
@@ -0,0 +1,44 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function run_test() {
+  do_test_pending();
+  run_next_test();
+}
+
+/**
+ * Test to ensure that {bytes:} in options to |readTo| and |write| are correctly
+ * preserved.
+ */
+add_task(function* test_bytes() {
+  let path = OS.Path.join(OS.Constants.Path.tmpDir,
+                          "test_osfile_async_bytes.tmp");
+  let file = yield OS.File.open(path, {trunc: true, read: true, write: true});
+  try {
+    try {
+      // 1. Test write, by supplying {bytes:} options smaller than the actual
+      // buffer.
+      yield file.write(new Uint8Array(2048), {bytes: 1024});
+      do_check_eq((yield file.stat()).size, 1024);
+
+      // 2. Test same for |readTo|.
+      yield file.setPosition(0, OS.File.POS_START);
+      let read = yield file.readTo(new Uint8Array(1024), {bytes: 512});
+      do_check_eq(read, 512);
+
+      // 3. Test that passing nullish values for |options| still works.
+      yield file.setPosition(0, OS.File.POS_END);
+      yield file.write(new Uint8Array(1024), null);
+      yield file.write(new Uint8Array(1024), undefined);
+      do_check_eq((yield file.stat()).size, 3072);
+    } finally {
+      yield file.close();
+    }
+  } finally {
+    yield OS.File.remove(path);
+  }
+});
+
+add_task(do_test_finished);
--- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 head =
 tail =
 
 [test_osfile_closed.js]
 [test_path.js]
 [test_osfile_async.js]
+[test_osfile_async_bytes.js]
 [test_profiledir.js]
 [test_logging.js]
 [test_creationDate.js]
 [test_exception.js]
 [test_path_constants.js]
 [test_removeDir.js]
--- a/toolkit/components/social/FrameWorkerContent.js
+++ b/toolkit/components/social/FrameWorkerContent.js
@@ -132,18 +132,20 @@ FrameWorker.prototype = {
       // interface NavigatorOnLine
       get onLine() workerWindow.navigator.onLine
     };
     sandbox.navigator = navigator;
 
     // Our importScripts function needs to 'eval' the script code from inside
     // a function, but using eval() directly means functions in the script
     // don't end up in the global scope.
-    sandbox._evalInSandbox = function(s) {
-      Cu.evalInSandbox(s, sandbox);
+    sandbox._evalInSandbox = function(s, url) {
+      let baseURI = Services.io.newURI(workerWindow.location.href, null, null);
+      Cu.evalInSandbox(s, sandbox, "1.8",
+                       Services.io.newURI(url, null, baseURI).spec, 1);
     };
 
     // and we delegate ononline and onoffline events to the worker.
     // See http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html#workerglobalscope
     workerWindow.addEventListener('offline', function fw_onoffline(event) {
       Cu.evalInSandbox("onoffline();", sandbox);
     }, false);
     workerWindow.addEventListener('online', function fw_ononline(event) {
--- a/toolkit/components/social/MessagePortWorker.js
+++ b/toolkit/components/social/MessagePortWorker.js
@@ -29,17 +29,17 @@ function importScripts() {
   for (var i=0; i < arguments.length; i++) {
     // load the url *synchronously*
     var scriptURL = arguments[i];
     var xhr = new XMLHttpRequest();
     xhr.open('GET', scriptURL, false);
     xhr.onreadystatechange = function(aEvt) {
       if (xhr.readyState == 4) {
         if (xhr.status == 200 || xhr.status == 0) {
-          _evalInSandbox(xhr.responseText);
+          _evalInSandbox(xhr.responseText, scriptURL);
         }
         else {
           throw new Error("Unable to importScripts ["+scriptURL+"], status " + xhr.status)
         }
       }
     };
     xhr.send(null);
   }
--- a/toolkit/components/social/test/browser/relative_import.js
+++ b/toolkit/components/social/test/browser/relative_import.js
@@ -1,6 +1,9 @@
 dump("relative_import file\n");
 
+// Please keep 'causeError' on line 4; we test the error location.
+function causeError() { does_not_exist(); }
+
 testVar = "oh hai";
 function testFunc() {
   return "oh hai";
 }
--- a/toolkit/components/social/test/browser/worker_relative.js
+++ b/toolkit/components/social/test/browser/worker_relative.js
@@ -1,19 +1,32 @@
 // Used to test XHR in the worker.
 onconnect = function(e) {
   let port = e.ports[0];
   let req;
   try {
     importScripts("relative_import.js");
     // the import should have exposed "testVar" and "testFunc" from the module.
-    if (testVar == "oh hai" && testFunc() == "oh hai") {
-      port.postMessage({topic: "done", result: "ok"});
-    } else {
+    if (testVar != "oh hai" || testFunc() != "oh hai") {
       port.postMessage({topic: "done", result: "import worked but global is not available"});
+      return;
     }
-    return;
+
+    // causeError will cause a script error, so that we can check the
+    // error location for importScripts'ed files is correct.
+    try {
+      causeError();
+    } catch(e) {
+      let fileName = e.fileName;
+      if (fileName.startsWith("http") &&
+          fileName.endsWith("/relative_import.js") &&
+          e.lineNumber == 4)
+        port.postMessage({topic: "done", result: "ok"});
+      else
+        port.postMessage({topic: "done", result: "invalid error location: " + fileName + ":" + e.lineNumber});
+      return;
+    }
   } catch(e) {
     port.postMessage({topic: "done", result: "FAILED to importScripts, " + e.toString() });
     return;
   }
   port.postMessage({topic: "done", result: "FAILED to importScripts, no exception" });
 }
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -129,8 +129,40 @@ this.yieldingEach = function yieldingEac
       }
     }
 
     deferred.resolve();
   }());
 
   return deferred.promise;
 }
+
+
+/**
+ * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
+ * allows the lazy getter to be defined on a prototype and work correctly with
+ * instances.
+ *
+ * @param Object aObject
+ *        The prototype object to define the lazy getter on.
+ * @param String aKey
+ *        The key to define the lazy getter on.
+ * @param Function aCallback
+ *        The callback that will be called to determine the value. Will be
+ *        called with the |this| value of the current instance.
+ */
+this.defineLazyPrototypeGetter =
+function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
+  Object.defineProperty(aObject, aKey, {
+    configurable: true,
+    get: function() {
+      const value = aCallback.call(this);
+
+      Object.defineProperty(this, aKey, {
+        configurable: true,
+        writable: true,
+        value: value
+      });
+
+      return value;
+    }
+  });
+}
--- a/toolkit/devtools/DevToolsUtils.jsm
+++ b/toolkit/devtools/DevToolsUtils.jsm
@@ -17,10 +17,11 @@ this.EXPORTED_SYMBOLS = [ "DevToolsUtils
 Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Components.interfaces.mozIJSSubScriptLoader)
   .loadSubScript("resource://gre/modules/devtools/DevToolsUtils.js", this);
 
 this.DevToolsUtils = {
   safeErrorString: safeErrorString,
   reportException: reportException,
   makeInfallible: makeInfallible,
-  yieldingEach: yieldingEach
+  yieldingEach: yieldingEach,
+  defineLazyPrototypeGetter: defineLazyPrototypeGetter
 };
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2630,16 +2630,18 @@ function ObjectActor(aObj, aThreadActor)
 {
   this.obj = aObj;
   this.threadActor = aThreadActor;
 }
 
 ObjectActor.prototype = {
   actorPrefix: "obj",
 
+  _forcedMagicProps: false,
+
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function OA_grip() {
     let g = {
       "type": "object",
       "class": this.obj.class,
       "actor": this.actorID,
@@ -2686,35 +2688,58 @@ ObjectActor.prototype = {
   release: function OA_release() {
     if (this.registeredPool.objectActors) {
       this.registeredPool.objectActors.delete(this.obj);
     }
     this.registeredPool.removeActor(this);
   },
 
   /**
+   * Force the magic Error properties to appear.
+   */
+  _forceMagicProperties: function OA__forceMagicProperties() {
+    if (this._forcedMagicProps) {
+      return;
+    }
+
+    const MAGIC_ERROR_PROPERTIES = [
+      "message", "stack", "fileName", "lineNumber", "columnNumber"
+    ];
+
+    if (this.obj.class.endsWith("Error")) {
+      for (let property of MAGIC_ERROR_PROPERTIES) {
+        this._propertyDescriptor(property);
+      }
+    }
+
+    this._forcedMagicProps = true;
+  },
+
+  /**
    * Handle a protocol request to provide the names of the properties defined on
    * the object and not its prototype.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onOwnPropertyNames: function OA_onOwnPropertyNames(aRequest) {
+    this._forceMagicProperties();
     return { from: this.actorID,
              ownPropertyNames: this.obj.getOwnPropertyNames() };
   },
 
   /**
    * Handle a protocol request to provide the prototype and own properties of
    * the object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onPrototypeAndProperties: function OA_onPrototypeAndProperties(aRequest) {
+    this._forceMagicProperties();
     let ownProperties = Object.create(null);
     let names;
     try {
       names = this.obj.getOwnPropertyNames();
     } catch (ex) {
       // The above can throw if this.obj points to a dead object.
       // TODO: we should use Cu.isDeadWrapper() - see bug 885800.
       return { from: this.actorID,
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_objectgrips-11.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we get the magic properties on Error objects.
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-grips");
+  gDebuggee.eval(function stopMe(arg1) {
+    debugger;
+  }.toString());
+
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function() {
+    attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_object_grip();
+    });
+  });
+  do_test_pending();
+}
+
+function test_object_grip()
+{
+  gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
+    let args = aPacket.frame.arguments;
+
+    let objClient = gThreadClient.pauseGrip(args[0]);
+    objClient.getOwnPropertyNames(function(aResponse) {
+      do_check_eq(aResponse.ownPropertyNames.length, 5);
+      do_check_eq(aResponse.ownPropertyNames[0], "message");
+      do_check_eq(aResponse.ownPropertyNames[1], "stack");
+      do_check_eq(aResponse.ownPropertyNames[2], "fileName");
+      do_check_eq(aResponse.ownPropertyNames[3], "lineNumber");
+      do_check_eq(aResponse.ownPropertyNames[4], "columnNumber");
+
+      gThreadClient.resume(function() {
+        finishClient(gClient);
+      });
+    });
+
+  });
+
+  gDebuggee.eval("stopMe(new TypeError('error message text'))");
+}
+
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -141,16 +141,17 @@ reason = bug 820380
 [test_objectgrips-03.js]
 [test_objectgrips-04.js]
 [test_objectgrips-05.js]
 [test_objectgrips-06.js]
 [test_objectgrips-07.js]
 [test_objectgrips-08.js]
 [test_objectgrips-09.js]
 [test_objectgrips-10.js]
+[test_objectgrips-11.js]
 [test_interrupt.js]
 [test_stepping-01.js]
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
 [test_stepping-05.js]
 [test_stepping-06.js]
 [test_framebindings-01.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tests/unit/test_defineLazyPrototypeGetter.js
@@ -0,0 +1,68 @@
+/* -*- Mode: js; js-indent-level: 2; -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test DevToolsUtils.defineLazyPrototypeGetter
+
+function Class() {}
+DevToolsUtils.defineLazyPrototypeGetter(Class.prototype, "foo", () => []);
+
+
+function run_test() {
+  test_prototype_attributes();
+  test_instance_attributes();
+  test_multiple_instances();
+  test_callback_receiver();
+}
+
+function test_prototype_attributes() {
+  // Check that the prototype has a getter property with expected attributes.
+  let descriptor = Object.getOwnPropertyDescriptor(Class.prototype, "foo");
+  do_check_eq(typeof descriptor.get, "function");
+  do_check_eq(descriptor.set, undefined);
+  do_check_eq(descriptor.enumerable, false);
+  do_check_eq(descriptor.configurable, true);
+}
+
+function test_instance_attributes() {
+  // Instances should not have an own property until the lazy getter has been
+  // activated.
+  let instance = new Class();
+  do_check_false(instance.hasOwnProperty("foo"));
+  instance.foo;
+  do_check_true(instance.hasOwnProperty("foo"));
+
+  // Check that the instance has an own property with the expecred value and
+  // attributes after the lazy getter is activated.
+  let descriptor = Object.getOwnPropertyDescriptor(instance, "foo");
+  do_check_true(descriptor.value instanceof Array);
+  do_check_eq(descriptor.writable, true);
+  do_check_eq(descriptor.enumerable, false);
+  do_check_eq(descriptor.configurable, true);
+}
+
+function test_multiple_instances() {
+  let instance1 = new Class();
+  let instance2 = new Class();
+  let foo1 = instance1.foo;
+  let foo2 = instance2.foo;
+  // Check that the lazy getter returns the expected type of value.
+  do_check_true(foo1 instanceof Array);
+  do_check_true(foo2 instanceof Array);
+  // Make sure the lazy getter runs once and only once per instance.
+  do_check_eq(instance1.foo, foo1);
+  do_check_eq(instance2.foo, foo2);
+  // Make sure each instance gets its own unique value.
+  do_check_neq(foo1, foo2);
+}
+
+function test_callback_receiver() {
+  function Foo() {};
+  DevToolsUtils.defineLazyPrototypeGetter(Foo.prototype, "foo", function() {
+    return this;
+  });
+
+  // Check that the |this| value in the callback is the instance itself.
+  let instance = new Foo();
+  do_check_eq(instance.foo, instance);
+}
--- a/toolkit/devtools/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/tests/unit/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head = head_devtools.js
 tail =
 
 [test_safeErrorString.js]
+[test_defineLazyPrototypeGetter.js]
--- a/toolkit/devtools/touch-events.js
+++ b/toolkit/devtools/touch-events.js
@@ -55,17 +55,17 @@ function TouchEventHandler (window) {
     handleEvent: function teh_handleEvent(evt) {
       if (evt.button || ignoreEvents ||
           evt.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN)
         return;
 
       // The gaia system window use an hybrid system even on the device which is
       // a mix of mouse/touch events. So let's not cancel *all* mouse events
       // if it is the current target.
-      let content = evt.target.ownerDocument.defaultView;
+      let content = this.getContent(evt.target);
       let isSystemWindow = content.location.toString().indexOf("system.gaiamobile.org") != -1;
 
       let eventTarget = this.target;
       let type = '';
       switch (evt.type) {
         case 'mousedown':
           this.target = evt.target;
 
@@ -133,53 +133,63 @@ function TouchEventHandler (window) {
       }
 
       if (!isSystemWindow) {
         evt.preventDefault();
         evt.stopImmediatePropagation();
       }
     },
     fireMouseEvent: function teh_fireMouseEvent(type, evt)  {
-      let content = evt.target.ownerDocument.defaultView;
+      let content = this.getContent(evt.target);
       var utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils);
       utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true);
     },
     sendContextMenu: function teh_sendContextMenu(target, x, y, delay) {
       let doc = target.ownerDocument;
       let evt = doc.createEvent('MouseEvent');
       evt.initMouseEvent('contextmenu', true, true, doc.defaultView,
                          0, x, y, x, y, false, false, false, false,
                          0, null);
 
-      let content = target.ownerDocument.defaultView;
+      let content = this.getContent(target);
       let timeout = content.setTimeout((function contextMenu() {
         target.dispatchEvent(evt);
         this.cancelClick = true;
       }).bind(this), delay);
 
       return timeout;
     },
     sendTouchEvent: function teh_sendTouchEvent(evt, target, name) {
       let document = target.ownerDocument;
-      let content = document.defaultView;
+      let content = this.getContent(target);
 
       let touchEvent = document.createEvent('touchevent');
       let point = document.createTouch(content, target, 0,
                                        evt.pageX, evt.pageY,
                                        evt.screenX, evt.screenY,
                                        evt.clientX, evt.clientY,
                                        1, 1, 0, 0);
       let touches = document.createTouchList(point);
       let targetTouches = touches;
       let changedTouches = touches;
       touchEvent.initTouchEvent(name, true, true, content, 0,
                                 false, false, false, false,
                                 touches, targetTouches, changedTouches);
       target.dispatchEvent(touchEvent);
       return touchEvent;
+    },
+    getContent: function teh_getContent(target) {
+      let win = target.ownerDocument.defaultView;
+      let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocShellTreeItem);
+      let topDocShell = docShell.sameTypeRootTreeItem;
+      topDocShell.QueryInterface(Ci.nsIDocShell);
+      let top = topDocShell.contentViewer.DOMDocument.defaultView;
+      return top;
     }
   };
 
   return TouchEventHandler;
 }
 
 exports.TouchEventHandler = TouchEventHandler;
--- a/toolkit/modules/Http.jsm
+++ b/toolkit/modules/Http.jsm
@@ -19,17 +19,17 @@ function percentEncode(aString)
  *    a string: send it as is
  *    an array of parameters: encode as form values
  *    null/undefined: no POST data.
  *  method, GET, POST or PUT (this is set automatically if postData exists).
  *  onLoad, a function handle to call when the load is complete, it takes two
  *          parameters: the responseText and the XHR object.
  *  onError, a function handle to call when an error occcurs, it takes three
  *          parameters: the error, the responseText and the XHR object.
- *  logger, an object that implements the debug and log methods (e.g. log4moz).
+ *  logger, an object that implements the debug and log methods (e.g. log.jsm).
  *
  * Headers or post data are given as an array of arrays, for each each inner
  * array the first value is the key and the second is the value, e.g.
  *  [["key1", "value1"], ["key2", "value2"]].
  */
 function httpRequest(aUrl, aOptions) {
   let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
               .createInstance(Ci.nsIXMLHttpRequest);
rename from services/common/log4moz.js
rename to toolkit/modules/Log.jsm
--- a/services/common/log4moz.js
+++ b/toolkit/modules/Log.jsm
@@ -1,31 +1,32 @@
 /* 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";
 
-this.EXPORTED_SYMBOLS = ['Log4Moz'];
+this.EXPORTED_SYMBOLS = ["Log"];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
 const ONE_BYTE = 1;
 const ONE_KILOBYTE = 1024 * ONE_BYTE;
 const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
 
 const STREAM_SEGMENT_SIZE = 4096;
 const PR_UINT32_MAX = 0xffffffff;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
-this.Log4Moz = {
+this.Log = {
   Level: {
     Fatal:  70,
     Error:  60,
     Warn:   50,
     Info:   40,
     Config: 30,
     Debug:  20,
     Trace:  10,
@@ -48,23 +49,23 @@ this.Log4Moz = {
       "CONFIG": 30,
       "DEBUG": 20,
       "TRACE": 10,
       "ALL": 0,
     }
   },
 
   get repository() {
-    delete Log4Moz.repository;
-    Log4Moz.repository = new LoggerRepository();
-    return Log4Moz.repository;
+    delete Log.repository;
+    Log.repository = new LoggerRepository();
+    return Log.repository;
   },
   set repository(value) {
-    delete Log4Moz.repository;
-    Log4Moz.repository = value;
+    delete Log.repository;
+    Log.repository = value;
   },
 
   LogMessage: LogMessage,
   Logger: Logger,
   LoggerRepository: LoggerRepository,
 
   Formatter: Formatter,
   BasicFormatter: BasicFormatter,
@@ -74,36 +75,36 @@ this.Log4Moz = {
   DumpAppender: DumpAppender,
   ConsoleAppender: ConsoleAppender,
   StorageStreamAppender: StorageStreamAppender,
 
   FileAppender: FileAppender,
   BoundedFileAppender: BoundedFileAppender,
 
   // Logging helper:
-  // let logger = Log4Moz.repository.getLogger("foo");
-  // logger.info(Log4Moz.enumerateInterfaces(someObject).join(","));
-  enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) {
+  // let logger = Log.repository.getLogger("foo");
+  // logger.info(Log.enumerateInterfaces(someObject).join(","));
+  enumerateInterfaces: function Log_enumerateInterfaces(aObject) {
     let interfaces = [];
 
     for (i in Ci) {
       try {
         aObject.QueryInterface(Ci[i]);
         interfaces.push(i);
       }
       catch(ex) {}
     }
 
     return interfaces;
   },
 
   // Logging helper:
-  // let logger = Log4Moz.repository.getLogger("foo");
-  // logger.info(Log4Moz.enumerateProperties(someObject).join(","));
-  enumerateProperties: function Log4Moz_enumerateProps(aObject,
+  // let logger = Log.repository.getLogger("foo");
+  // logger.info(Log.enumerateProperties(someObject).join(","));
+  enumerateProperties: function Log_enumerateProps(aObject,
                                                        aExcludeComplexTypes) {
     let properties = [];
 
     for (p in aObject) {
       try {
         if (aExcludeComplexTypes &&
             (typeof aObject[p] == "object" || typeof aObject[p] == "function"))
           continue;
@@ -131,18 +132,18 @@ function LogMessage(loggerName, level, m
 
   // The _structured field will correspond to whether this message is to
   // be interpreted as a structured message.
   this._structured = this.params && this.params.action;
   this.time = Date.now();
 }
 LogMessage.prototype = {
   get levelDesc() {
-    if (this.level in Log4Moz.Level.Desc)
-      return Log4Moz.Level.Desc[this.level];
+    if (this.level in Log.Level.Desc)
+      return Log.Level.Desc[this.level];
     return "UNKNOWN";
   },
 
   toString: function LogMsg_toString(){
     let msg = "LogMessage [" + this.time + " " + this.level + " " +
       this.message;
     if (this.params) {
       msg += " " + JSON.stringify(this.params);
@@ -153,17 +154,17 @@ LogMessage.prototype = {
 
 /*
  * Logger
  * Hierarchical version.  Logs to all appenders, assigned or inherited
  */
 
 function Logger(name, repository) {
   if (!repository)
-    repository = Log4Moz.repository;
+    repository = Log.repository;
   this._name = name;
   this.children = [];
   this.ownAppenders = [];
   this.appenders = [];
   this._repository = repository;
 }
 Logger.prototype = {
   get name() {
@@ -171,18 +172,18 @@ Logger.prototype = {
   },
 
   _level: null,
   get level() {
     if (this._level != null)
       return this._level;
     if (this.parent)
       return this.parent.level;
-    dump("log4moz warning: root logger configuration error: no level defined\n");
-    return Log4Moz.Level.All;
+    dump("Log warning: root logger configuration error: no level defined\n");
+    return Log.Level.All;
   },
   set level(level) {
     this._level = level;
   },
 
   _parent: null,
   get parent() this._parent,
   set parent(parent) {
@@ -253,18 +254,18 @@ Logger.prototype = {
     if (!params) {
       return this.log(this.level, undefined, {"action": action});
     }
     if (typeof params != "object") {
       throw "The params argument is required to be an object.";
     }
 
     let level = params._level || this.level;
-    if ((typeof level == "string") && level in Log4Moz.Level.Numbers) {
-      level = Log4Moz.Level.Numbers[level];
+    if ((typeof level == "string") && level in Log.Level.Numbers) {
+      level = Log.Level.Numbers[level];
     }
 
     params.action = action;
     this.log(level, params._message, params);
   },
 
   log: function (level, string, params) {
     if (this.level > level)
@@ -281,52 +282,52 @@ Logger.prototype = {
       if (!message) {
         message = new LogMessage(this._name, level, string, params);
       }
       appender.append(message);
     }
   },
 
   fatal: function (string, params) {
-    this.log(Log4Moz.Level.Fatal, string, params);
+    this.log(Log.Level.Fatal, string, params);
   },
   error: function (string, params) {
-    this.log(Log4Moz.Level.Error, string, params);
+    this.log(Log.Level.Error, string, params);
   },
   warn: function (string, params) {
-    this.log(Log4Moz.Level.Warn, string, params);
+    this.log(Log.Level.Warn, string, params);
   },
   info: function (string, params) {
-    this.log(Log4Moz.Level.Info, string, params);
+    this.log(Log.Level.Info, string, params);
   },
   config: function (string, params) {
-    this.log(Log4Moz.Level.Config, string, params);
+    this.log(Log.Level.Config, string, params);
   },
   debug: function (string, params) {
-    this.log(Log4Moz.Level.Debug, string, params);
+    this.log(Log.Level.Debug, string, params);
   },
   trace: function (string, params) {
-    this.log(Log4Moz.Level.Trace, string, params);
+    this.log(Log.Level.Trace, string, params);
   }
 };
 
 /*
  * LoggerRepository
  * Implements a hierarchy of Loggers
  */
 
 function LoggerRepository() {}
 LoggerRepository.prototype = {
   _loggers: {},
 
   _rootLogger: null,
   get rootLogger() {
     if (!this._rootLogger) {
       this._rootLogger = new Logger("root", this);
-      this._rootLogger.level = Log4Moz.Level.All;
+      this._rootLogger.level = Log.Level.All;
     }
     return this._rootLogger;
   },
   set rootLogger(logger) {
     throw "Cannot change the root logger";
   },
 
   _updateParents: function LogRep__updateParents(name) {
@@ -431,17 +432,17 @@ StructuredFormatter.prototype = {
  * Simply subclass and override doAppend to implement a new one
  */
 
 function Appender(formatter) {
   this._name = "Appender";
   this._formatter = formatter? formatter : new BasicFormatter();
 }
 Appender.prototype = {
-  level: Log4Moz.Level.All,
+  level: Log.Level.All,
 
   append: function App_append(message) {
     if (message) {
       this.doAppend(this._formatter.format(message));
     }
   },
   toString: function App_toString() {
     return this._name + " [level=" + this._level +
@@ -475,17 +476,17 @@ DumpAppender.prototype = {
 function ConsoleAppender(formatter) {
   this._name = "ConsoleAppender";
   Appender.call(this, formatter);
 }
 ConsoleAppender.prototype = {
   __proto__: Appender.prototype,
 
   doAppend: function CApp_doAppend(message) {
-    if (message.level > Log4Moz.Level.Warn) {
+    if (message.level > Log.Level.Warn) {
       Cu.reportError(message);
       return;
     }
     Cc["@mozilla.org/consoleservice;1"].
       getService(Ci.nsIConsoleService).logStringMessage(message);
   }
 };
 
--- a/toolkit/modules/Sqlite.jsm
+++ b/toolkit/modules/Sqlite.jsm
@@ -9,17 +9,17 @@ this.EXPORTED_SYMBOLS = [
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
@@ -61,17 +61,17 @@ let connectionCounters = new Map();
  *   SYNCHRONOUS = full
  *
  * @param options
  *        (Object) Parameters to control connection and open options.
  *
  * @return Promise<OpenedConnection>
  */
 function openConnection(options) {
-  let log = Log4Moz.repository.getLogger("Sqlite.ConnectionOpener");
+  let log = Log.repository.getLogger("Sqlite.ConnectionOpener");
 
   if (!options.path) {
     throw new Error("path not specified in connection options.");
   }
 
   // Retains absolute paths and normalizes relative as relative to profile.
   let path = OS.Path.join(OS.Constants.Path.profileDir, options.path);
 
@@ -168,27 +168,27 @@ function openConnection(options) {
  *        (string) The basename of this database name. Used for logging.
  * @param number
  *        (Number) The connection number to this database.
  * @param options
  *        (object) Options to control behavior of connection. See
  *        `openConnection`.
  */
 function OpenedConnection(connection, basename, number, options) {
-  let log = Log4Moz.repository.getLogger("Sqlite.Connection." + basename);
+  let log = Log.repository.getLogger("Sqlite.Connection." + basename);
 
   // getLogger() returns a shared object. We can't modify the functions on this
   // object since they would have effect on all instances and last write would
   // win. So, we create a "proxy" object with our custom functions. Everything
   // else is proxied back to the shared logger instance via prototype
   // inheritance.
   let logProxy = {__proto__: log};
 
   // Automatically prefix all log messages with the identifier.
-  for (let level in Log4Moz.Level) {
+  for (let level in Log.Level) {
     if (level == "Desc") {
       continue;
     }
 
     let lc = level.toLowerCase();
     logProxy[lc] = function (msg) {
       return log[lc].call(log, "Conn #" + number + ": " + msg);
     };
@@ -740,17 +740,17 @@ OpenedConnection.prototype = Object.free
 
     let deferred = Promise.defer();
     let userCancelled = false;
     let errors = [];
     let rows = [];
 
     // Don't incur overhead for serializing params unless the messages go
     // somewhere.
-    if (this._log.level <= Log4Moz.Level.Trace) {
+    if (this._log.level <= Log.Level.Trace) {
       let msg = "Stmt #" + index + " " + sql;
 
       if (params) {
         msg += " - " + JSON.stringify(params);
       }
       this._log.trace(msg);
     } else {
       this._log.debug("Stmt #" + index + " starting");
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -12,16 +12,17 @@ EXTRA_JS_MODULES += [
     'Deprecated.jsm',
     'Dict.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'Geometry.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
     'LoadContextInfo.jsm',
+    'Log.jsm',
     'NewTabUtils.jsm',
     'PageMenu.jsm',
     'PermissionsUtils.jsm',
     'PopupNotifications.jsm',
     'Preferences.jsm',
     'PrivateBrowsingUtils.jsm',
     'Promise.jsm',
     'PropertyListUtils.jsm',
rename from services/common/tests/unit/test_log4moz.js
rename to toolkit/modules/tests/xpcshell/test_Log.js
--- a/services/common/tests/unit/test_log4moz.js
+++ b/toolkit/modules/tests/xpcshell/test_Log.js
@@ -1,73 +1,75 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const {utils: Cu} = Components;
+
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 
-Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/Log.jsm");
 
 let testFormatter = {
   format: function format(message) {
     return message.loggerName + "\t" +
       message.levelDesc + "\t" +
       message.message + "\n";
   }
 };
 
 function MockAppender(formatter) {
-  Log4Moz.Appender.call(this, formatter);
+  Log.Appender.call(this, formatter);
   this.messages = [];
 }
 MockAppender.prototype = {
-  __proto__: Log4Moz.Appender.prototype,
+  __proto__: Log.Appender.prototype,
 
   doAppend: function DApp_doAppend(message) {
     this.messages.push(message);
   }
 };
 
 function run_test() {
   run_next_test();
 }
 
 add_test(function test_Logger() {
-  let log = Log4Moz.repository.getLogger("test.logger");
-  let appender = new MockAppender(new Log4Moz.BasicFormatter());
+  let log = Log.repository.getLogger("test.logger");
+  let appender = new MockAppender(new Log.BasicFormatter());
 
-  log.level = Log4Moz.Level.Debug;
-  appender.level = Log4Moz.Level.Info;
+  log.level = Log.Level.Debug;
+  appender.level = Log.Level.Info;
   log.addAppender(appender);
   log.info("info test");
   log.debug("this should be logged but not appended.");
 
   do_check_eq(appender.messages.length, 1);
 
   let msgRe = /\d+\ttest.logger\t\INFO\tinfo test/;
   do_check_true(msgRe.test(appender.messages[0]));
 
   run_next_test();
 });
 
 add_test(function test_Logger_parent() {
   // Check whether parenting is correct
-  let grandparentLog = Log4Moz.repository.getLogger("grandparent");
-  let childLog = Log4Moz.repository.getLogger("grandparent.parent.child");
+  let grandparentLog = Log.repository.getLogger("grandparent");
+  let childLog = Log.repository.getLogger("grandparent.parent.child");
   do_check_eq(childLog.parent.name, "grandparent");
 
-  let parentLog = Log4Moz.repository.getLogger("grandparent.parent");
+  let parentLog = Log.repository.getLogger("grandparent.parent");
   do_check_eq(childLog.parent.name, "grandparent.parent");
 
   // Check that appends are exactly in scope
-  let gpAppender = new MockAppender(new Log4Moz.BasicFormatter());
-  gpAppender.level = Log4Moz.Level.Info;
+  let gpAppender = new MockAppender(new Log.BasicFormatter());
+  gpAppender.level = Log.Level.Info;
   grandparentLog.addAppender(gpAppender);
   childLog.info("child info test");
-  Log4Moz.repository.rootLogger.info("this shouldn't show up in gpAppender");
+  Log.repository.rootLogger.info("this shouldn't show up in gpAppender");
 
   do_check_eq(gpAppender.messages.length, 1);
   do_check_true(gpAppender.messages[0].indexOf("child info test") > 0);
 
   run_next_test();
 });
 
 // A utility method for checking object equivalence.
@@ -91,52 +93,52 @@ function checkObjects(expected, actual) 
   }
 
   for (let key in actual) {
     do_check_neq(expected[key], undefined);
   }
 }
 
 add_test(function test_StructuredLogCommands() {
-  let appender = new MockAppender(new Log4Moz.StructuredFormatter());
-  let logger = Log4Moz.repository.getLogger("test.StructuredOutput");
+  let appender = new MockAppender(new Log.StructuredFormatter());
+  let logger = Log.repository.getLogger("test.StructuredOutput");
   logger.addAppender(appender);
-  logger.level = Log4Moz.Level.Info;
+  logger.level = Log.Level.Info;
 
   logger.logStructured("test_message", {_message: "message string one"});
   logger.logStructured("test_message", {_message: "message string two",
                                         _level: "ERROR",
-                                        source_file: "test_log4moz.js"});
+                                        source_file: "test_Log.js"});
   logger.logStructured("test_message");
-  logger.logStructured("test_message", {source_file: "test_log4moz.js",
+  logger.logStructured("test_message", {source_file: "test_Log.js",
                                         message_position: 4});
 
   let messageOne = {"_time": /\d+/,
                     "_namespace": "test.StructuredOutput",
                     "_level": "INFO",
                     "_message": "message string one",
                     "action": "test_message"};
 
   let messageTwo = {"_time": /\d+/,
                     "_namespace": "test.StructuredOutput",
                     "_level": "ERROR",
                     "_message": "message string two",
                     "action": "test_message",
-                    "source_file": "test_log4moz.js"};
+                    "source_file": "test_Log.js"};
 
   let messageThree = {"_time": /\d+/,
                       "_namespace": "test.StructuredOutput",
                       "_level": "INFO",
                       "action": "test_message"};
 
   let messageFour = {"_time": /\d+/,
                      "_namespace": "test.StructuredOutput",
                      "_level": "INFO",
                      "action": "test_message",
-                     "source_file": "test_log4moz.js",
+                     "source_file": "test_Log.js",
                      "message_position": 4};
 
   checkObjects(messageOne, JSON.parse(appender.messages[0]));
   checkObjects(messageTwo, JSON.parse(appender.messages[1]));
   checkObjects(messageThree, JSON.parse(appender.messages[2]));
   checkObjects(messageFour, JSON.parse(appender.messages[3]));
 
   let errored = false;
@@ -156,39 +158,39 @@ add_test(function test_StructuredLogComm
     errored = true;
     do_check_eq(e, "The params argument is required to be an object.");
   } finally {
     do_check_true(errored);
   }
 
   // Logging with unstructured interface should produce the same messages
   // as the structured interface for these cases.
-  let appender = new MockAppender(new Log4Moz.StructuredFormatter());
-  let logger = Log4Moz.repository.getLogger("test.StructuredOutput1");
+  let appender = new MockAppender(new Log.StructuredFormatter());
+  let logger = Log.repository.getLogger("test.StructuredOutput1");
   messageOne._namespace = "test.StructuredOutput1";
   messageTwo._namespace = "test.StructuredOutput1";
   logger.addAppender(appender);
-  logger.level = Log4Moz.Level.All;
+  logger.level = Log.Level.All;
   logger.info("message string one", {action: "test_message"});
   logger.error("message string two", {action: "test_message",
-                                      source_file: "test_log4moz.js"});
+                                      source_file: "test_Log.js"});
 
   checkObjects(messageOne, JSON.parse(appender.messages[0]));
   checkObjects(messageTwo, JSON.parse(appender.messages[1]));
 
   run_next_test();
 });
 
 add_test(function test_StorageStreamAppender() {
-  let appender = new Log4Moz.StorageStreamAppender(testFormatter);
+  let appender = new Log.StorageStreamAppender(testFormatter);
   do_check_eq(appender.getInputStream(), null);
 
   // Log to the storage stream and verify the log was written and can be
   // read back.
-  let logger = Log4Moz.repository.getLogger("test.StorageStreamAppender");
+  let logger = Log.repository.getLogger("test.StorageStreamAppender");
   logger.addAppender(appender);
   logger.info("OHAI");
   let inputStream = appender.getInputStream();
   let data = NetUtil.readInputStreamToString(inputStream,
                                              inputStream.available());
   do_check_eq(data, "test.StorageStreamAppender\tINFO\tOHAI\n");
 
   // We can read it again even.
@@ -213,21 +215,21 @@ function fileContents(path) {
   let decoder = new TextDecoder();
   return OS.File.read(path).then(array => {
     return decoder.decode(array);
   });
 }
 
 add_task(function test_FileAppender() {
   // This directory does not exist yet
-  let dir = OS.Path.join(do_get_profile().path, "test_log4moz");
+  let dir = OS.Path.join(do_get_profile().path, "test_Log");
   do_check_false(yield OS.File.exists(dir));
   let path = OS.Path.join(dir, "test_FileAppender");
-  let appender = new Log4Moz.FileAppender(path, testFormatter);
-  let logger = Log4Moz.repository.getLogger("test.FileAppender");
+  let appender = new Log.FileAppender(path, testFormatter);
+  let logger = Log.repository.getLogger("test.FileAppender");
   logger.addAppender(appender);
 
   // Logging to a file that can't be created won't do harm.
   do_check_false(yield OS.File.exists(path));
   logger.info("OHAI!");
 
   yield OS.File.makeDir(dir);
   logger.info("OHAI");
@@ -266,21 +268,21 @@ add_task(function test_FileAppender() {
               "test.FileAppender\tDEBUG\t1\n" +
               "test.FileAppender\tINFO\t2\n" +
               "test.FileAppender\tINFO\t3\n" +
               "test.FileAppender\tINFO\t4\n" +
               "test.FileAppender\tINFO\t5\n");
 });
 
 add_task(function test_BoundedFileAppender() {
-  let dir = OS.Path.join(do_get_profile().path, "test_log4moz");
+  let dir = OS.Path.join(do_get_profile().path, "test_Log");
   let path = OS.Path.join(dir, "test_BoundedFileAppender");
   // This appender will hold about two lines at a time.
-  let appender = new Log4Moz.BoundedFileAppender(path, testFormatter, 40);
-  let logger = Log4Moz.repository.getLogger("test.BoundedFileAppender");
+  let appender = new Log.BoundedFileAppender(path, testFormatter, 40);
+  let logger = Log.repository.getLogger("test.BoundedFileAppender");
   logger.addAppender(appender);
 
   logger.info("ONE");
   logger.info("TWO");
   yield appender._lastWritePromise;
 
   do_check_eq((yield fileContents(path)),
               "test.BoundedFileAppender\tINFO\tONE\n" +
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -5,16 +5,17 @@ support-files =
   propertyLists/bug710259_propertyListBinary.plist
   propertyLists/bug710259_propertyListXML.plist
   chromeappsstore.sqlite
 
 [test_AsyncShutdown.js]
 [test_dict.js]
 [test_FileUtils.js]
 [test_Http.js]
+[test_Log.js]
 [test_PermissionsUtils.js]
 [test_Preferences.js]
 [test_Promise.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_task.js]
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -828,16 +828,63 @@ AndroidGeckoEvent::MakeMultiTouchInput(n
                              Orientations()[i],
                              Pressures()[i]);
         event.mTouches.AppendElement(data);
     }
 
     return event;
 }
 
+WidgetMouseEvent
+AndroidGeckoEvent::MakeMouseEvent(nsIWidget* widget)
+{
+    uint32_t msg = NS_EVENT_NULL;
+    if (Points().Length() > 0) {
+        switch (Action()) {
+            case AndroidMotionEvent::ACTION_HOVER_MOVE:
+                msg = NS_MOUSE_MOVE;
+                break;
+            case AndroidMotionEvent::ACTION_HOVER_ENTER:
+                msg = NS_MOUSEENTER;
+                break;
+            case AndroidMotionEvent::ACTION_HOVER_EXIT:
+                msg = NS_MOUSELEAVE;
+                break;
+            default:
+                break;
+        }
+    }
+
+    WidgetMouseEvent event(true, msg, widget,
+                           WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+    if (msg == NS_EVENT_NULL) {
+        // unknown type, or no point data. abort
+        return event;
+    }
+
+    // XXX can we synthesize different buttons?
+    event.button = WidgetMouseEvent::eLeftButton;
+    if (msg != NS_MOUSE_MOVE) {
+        event.clickCount = 1;
+    }
+    event.modifiers = 0;
+    event.time = Time();
+
+    // We are dispatching this event directly into Gecko (as opposed to going
+    // through the AsyncPanZoomController), and the Points() array has points
+    // in CSS pixels, which we need to convert to LayoutDevice pixels.
+    const nsIntPoint& offset = widget->WidgetToScreenOffset();
+    CSSToLayoutDeviceScale scale = widget->GetDefaultScale();
+    event.refPoint = LayoutDeviceIntPoint((Points()[0].x * scale.scale) - offset.x,
+                                          (Points()[0].y * scale.scale) - offset.y);
+
+    return event;
+}
+
 void
 AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
 {
     if (jobj) {
         mX = jenv->GetIntField(jobj, jXField);
         mY = jenv->GetIntField(jobj, jYField);
     } else {
         mX = 0;
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -585,16 +585,17 @@ public:
     int DHCPGateway() { return mDHCPGateway; }
     short ScreenOrientation() { return mScreenOrientation; }
     RefCountedJavaObject* ByteBuffer() { return mByteBuffer; }
     int Width() { return mWidth; }
     int Height() { return mHeight; }
     int RequestId() { return mCount; } // for convenience
     WidgetTouchEvent MakeTouchEvent(nsIWidget* widget);
     MultiTouchInput MakeMultiTouchInput(nsIWidget* widget);
+    WidgetMouseEvent MakeMouseEvent(nsIWidget* widget);
     void UnionRect(nsIntRect const& aRect);
     nsIObserver *Observer() { return mObserver; }
 
 protected:
     int mAction;
     int mType;
     bool mAckNeeded;
     int64_t mTime;
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1163,56 +1163,26 @@ nsWindow::GetNativeData(uint32_t aDataTy
     }
 
     return nullptr;
 }
 
 void
 nsWindow::OnMouseEvent(AndroidGeckoEvent *ae)
 {
-    uint32_t msg;
-    switch (ae->Action()) {
-        case AndroidMotionEvent::ACTION_HOVER_MOVE:
-            msg = NS_MOUSE_MOVE;
-            break;
-
-        case AndroidMotionEvent::ACTION_HOVER_ENTER:
-            msg = NS_MOUSEENTER;
-            break;
-
-        case AndroidMotionEvent::ACTION_HOVER_EXIT:
-            msg = NS_MOUSELEAVE;
-            break;
-
-        default:
-            return;
-    }
-
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
 
-send_again:
-
-    WidgetMouseEvent event(true, msg, this,
-                           WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
-    // XXX can we synthesize different buttons?
-    event.button = WidgetMouseEvent::eLeftButton;
-
-    if (msg != NS_MOUSE_MOVE)
-        event.clickCount = 1;
+    WidgetMouseEvent event = ae->MakeMouseEvent(this);
+    if (event.message == NS_EVENT_NULL) {
+        // invalid event type, abort
+        return;
+    }
 
     // XXX add the double-click handling logic here
-    if (ae->Points().Length() > 0)
-        DispatchMotionEvent(event, ae, ae->Points()[0]);
-    if (Destroyed())
-        return;
-
-    if (msg == NS_MOUSE_BUTTON_DOWN) {
-        msg = NS_MOUSE_MOVE;
-        goto send_again;
-    }
+    DispatchEvent(&event);
 }
 
 bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)
 {
     nsRefPtr<nsWindow> kungFuDeathGrip(this);
 
     // End any composition in progress in case the touch event listener
     // modifies the input field value (see bug 856155)