Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 14 Oct 2014 16:59:12 -0700
changeset 210319 c76eb2abb768775a1300591b6fd18f38f65ca1b3
parent 210302 01f04d75519d2a99328df5f1583f3446cca98683 (current diff)
parent 210318 7246195fb751da0975f4a5abff617ff42e2ba712 (diff)
child 210366 62f0b771583c09fa24583da75ab08115fa04ce79
push id27650
push userkwierso@gmail.com
push dateTue, 14 Oct 2014 23:59:16 +0000
treeherdermozilla-central@c76eb2abb768 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.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 m-c a=merge
b2g/components/AppFrames.jsm
toolkit/components/places/tests/queries/test_excludeReadOnlyFolders.js
--- a/b2g/chrome/content/devtools/debugger.js
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -16,16 +16,22 @@ XPCOMUtils.defineLazyGetter(this, "devto
     Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   return devtools;
 });
 
 XPCOMUtils.defineLazyGetter(this, "discovery", function() {
   return devtools.require("devtools/toolkit/discovery/discovery");
 });
 
+XPCOMUtils.defineLazyGetter(this, "B2GTabList", function() {
+  const { B2GTabList } =
+    devtools.require("resource://gre/modules/DebuggerActors.js");
+  return B2GTabList;
+});
+
 let RemoteDebugger = {
   _promptDone: false,
   _promptAnswer: false,
   _listening: false,
 
   prompt: function() {
     this._listen();
 
@@ -86,25 +92,17 @@ let RemoteDebugger = {
      *
      * * @param connection DebuggerServerConnection
      *        The conection to the client.
      */
     DebuggerServer.createRootActor = function createRootActor(connection)
     {
       let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
       let parameters = {
-        // We do not expose browser tab actors yet,
-        // but we still have to define tabList.getList(),
-        // otherwise, client won't be able to fetch global actors
-        // from listTabs request!
-        tabList: {
-          getList: function() {
-            return promise.resolve([]);
-          }
-        },
+        tabList: new B2GTabList(connection),
         // Use an explicit global actor list to prevent exposing
         // unexpected actors
         globalActorFactories: restrictPrivileges ? {
           webappsActor: DebuggerServer.globalActorFactories.webappsActor,
           deviceActor: DebuggerServer.globalActorFactories.deviceActor,
         } : DebuggerServer.globalActorFactories
       };
       let { RootActor } = devtools.require("devtools/server/actors/root");
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -24,17 +24,17 @@ XPCOMUtils.defineLazyGetter(this, 'WebCo
 XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
   return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
   return devtools.require('devtools/server/actors/memory').MemoryFront;
 });
 
-Cu.import('resource://gre/modules/AppFrames.jsm');
+Cu.import('resource://gre/modules/Frames.jsm');
 
 /**
  * The Developer HUD is an on-device developer tool that displays widgets,
  * showing visual debug information about apps. Each widget corresponds to a
  * metric as tracked by a metric watcher (e.g. consoleWatcher).
  */
 let developerHUD = {
 
@@ -75,19 +75,20 @@ let developerHUD = {
     this._client = new DebuggerClient(transport);
 
     for (let w of this._watchers) {
       if (w.init) {
         w.init(this._client);
       }
     }
 
-    AppFrames.addObserver(this);
+    Frames.addObserver(this);
 
-    for (let frame of AppFrames.list()) {
+    let appFrames = Frames.list().filter(frame => frame.getAttribute('mozapp'));
+    for (let frame of appFrames) {
       this.trackFrame(frame);
     }
 
     SettingsListener.observe('hud.logging', this._logging, enabled => {
       this._logging = enabled;
     });
   },
 
@@ -95,17 +96,17 @@ let developerHUD = {
     if (!this._client) {
       return;
     }
 
     for (let frame of this._targets.keys()) {
       this.untrackFrame(frame);
     }
 
-    AppFrames.removeObserver(this);
+    Frames.removeObserver(this);
 
     this._client.close();
     delete this._client;
   },
 
   /**
    * This method will ask all registered watchers to track and update metrics
    * on an app frame.
@@ -132,21 +133,29 @@ let developerHUD = {
         w.untrackTarget(target);
       }
 
       target.destroy();
       this._targets.delete(frame);
     }
   },
 
-  onAppFrameCreated: function (frame, isFirstAppFrame) {
+  onFrameCreated: function (frame, isFirstAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp) {
+      return;
+    }
     this.trackFrame(frame);
   },
 
-  onAppFrameDestroyed: function (frame, isLastAppFrame) {
+  onFrameDestroyed: function (frame, isLastAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp) {
+      return;
+    }
     this.untrackFrame(frame);
   },
 
   log: function dwp_log(message) {
     if (this._logging) {
       dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
     }
   }
new file mode 100644
--- /dev/null
+++ b/b2g/components/DebuggerActors.js
@@ -0,0 +1,83 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cu } = require("chrome");
+const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
+const promise = require("promise");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const { BrowserTabList } = require("devtools/server/actors/webbrowser");
+
+XPCOMUtils.defineLazyGetter(this, "Frames", function() {
+  const { Frames } =
+    Cu.import("resource://gre/modules/Frames.jsm", {});
+  return Frames;
+});
+
+/**
+ * Unlike the original BrowserTabList which iterates over XUL windows, we
+ * override many portions to refer to Frames for the info needed here.
+ */
+function B2GTabList(connection) {
+  BrowserTabList.call(this, connection);
+  this._listening = false;
+}
+
+B2GTabList.prototype = Object.create(BrowserTabList.prototype);
+
+B2GTabList.prototype._getBrowsers = function() {
+  return Frames.list().filter(frame => {
+    // Ignore app frames
+    return !frame.getAttribute("mozapp");
+  });
+};
+
+B2GTabList.prototype._getSelectedBrowser = function() {
+  return this._getBrowsers().find(frame => {
+    // Find the one visible browser (if any)
+    return !frame.classList.contains("hidden");
+  });
+};
+
+B2GTabList.prototype._checkListening = function() {
+  // The conditions from BrowserTabList are merged here, since we must listen to
+  // all events with our observer design.
+  this._listenForEventsIf(this._onListChanged && this._mustNotify ||
+                          this._actorByBrowser.size > 0);
+};
+
+B2GTabList.prototype._listenForEventsIf = function(shouldListen) {
+  if (this._listening != shouldListen) {
+    let op = shouldListen ? "addObserver" : "removeObserver";
+    Frames[op](this);
+    this._listening = shouldListen;
+  }
+};
+
+B2GTabList.prototype.onFrameCreated = function(frame) {
+  let mozapp = frame.getAttribute("mozapp");
+  if (mozapp) {
+    // Ignore app frames
+    return;
+  }
+  this._notifyListChanged();
+  this._checkListening();
+};
+
+B2GTabList.prototype.onFrameDestroyed = function(frame) {
+  let mozapp = frame.getAttribute("mozapp");
+  if (mozapp) {
+    // Ignore app frames
+    return;
+  }
+  let actor = this._actorByBrowser.get(frame);
+  if (actor) {
+    this._handleActorClose(actor, frame);
+  }
+};
+
+exports.B2GTabList = B2GTabList;
rename from b2g/components/AppFrames.jsm
rename to b2g/components/Frames.jsm
--- a/b2g/components/AppFrames.jsm
+++ b/b2g/components/Frames.jsm
@@ -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/. */
 
 'use strict';
 
-this.EXPORTED_SYMBOLS = ['AppFrames'];
+this.EXPORTED_SYMBOLS = ['Frames'];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/SystemAppProxy.jsm');
 
 const listeners = [];
@@ -22,21 +22,23 @@ const Observer = {
   // Also save current number of iframes opened by app
   _apps: new Map(),
 
   start: function () {
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'message-manager-disconnect', false);
 
-    SystemAppProxy.getAppFrames().forEach((frame) => {
+    SystemAppProxy.getFrames().forEach(frame => {
       let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
       this._frames.set(mm, frame);
       let mozapp = frame.getAttribute('mozapp');
-      this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
+      if (mozapp) {
+        this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
+      }
     });
   },
 
   stop: function () {
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
     Services.obs.removeObserver(this, 'message-manager-disconnect');
     this._frames.clear();
@@ -63,58 +65,66 @@ const Observer = {
         this.onMessageManagerDestroyed(subject);
         break;
     }
   },
 
   onMessageManagerCreated: function (mm, frame) {
     this._frames.set(mm, frame);
 
+    let isFirstAppFrame = null;
     let mozapp = frame.getAttribute('mozapp');
-    let count = (this._apps.get(mozapp) || 0) + 1;
-    this._apps.set(mozapp, count);
+    if (mozapp) {
+      let count = (this._apps.get(mozapp) || 0) + 1;
+      this._apps.set(mozapp, count);
+      isFirstAppFrame = (count === 1);
+    }
 
-    let isFirstAppFrame = (count === 1);
     listeners.forEach(function (listener) {
       try {
-        listener.onAppFrameCreated(frame, isFirstAppFrame);
+        listener.onFrameCreated(frame, isFirstAppFrame);
       } catch(e) {
-        dump('Exception while calling Frames.jsm listener:' + e + '\n' + e.stack + '\n');
+        dump('Exception while calling Frames.jsm listener:' + e + '\n' +
+             e.stack + '\n');
       }
     });
   },
 
   onMessageManagerDestroyed: function (mm) {
     let frame = this._frames.get(mm);
     if (!frame) {
-      // We receive an event for a non mozapp message manager
+      // We received an event for an unknown message manager
       return;
     }
 
     this._frames.delete(mm);
 
+    let isLastAppFrame = null;
     let mozapp = frame.getAttribute('mozapp');
-    let count = (this._apps.get(mozapp) || 0) - 1;
-    this._apps.set(mozapp, count);
+    if (mozapp) {
+      let count = (this._apps.get(mozapp) || 0) - 1;
+      this._apps.set(mozapp, count);
+      isLastAppFrame = (count === 0);
+    }
 
-    let isLastAppFrame = (count === 0);
     listeners.forEach(function (listener) {
       try {
-        listener.onAppFrameDestroyed(frame, isLastAppFrame);
+        listener.onFrameDestroyed(frame, isLastAppFrame);
       } catch(e) {
-        dump('Exception while calling Frames.jsm listener:' + e + '\n' + e.stack + '\n');
+        dump('Exception while calling Frames.jsm listener:' + e + '\n' +
+             e.stack + '\n');
       }
     });
   }
 
 };
 
-let AppFrames = this.AppFrames = {
+let Frames = this.Frames = {
 
-  list: () => SystemAppProxy.getAppFrames(),
+  list: () => SystemAppProxy.getFrames(),
 
   addObserver: function (listener) {
     if (listeners.indexOf(listener) !== -1) {
       return;
     }
 
     listeners.push(listener);
     if (listeners.length == 1) {
--- a/b2g/components/SystemAppProxy.jsm
+++ b/b2g/components/SystemAppProxy.jsm
@@ -112,30 +112,23 @@ let SystemAppProxy = {
     } else {
       this._pendingListeners = this._pendingListeners.filter(
         args => {
           return args[0] != name || args[1] != listener;
         });
     }
   },
 
-  getAppFrames: function systemApp_getAppFrames() {
+  getFrames: function systemApp_getFrames() {
     let systemAppFrame = this._frame;
     if (!systemAppFrame) {
       return [];
     }
-
     let list = [systemAppFrame];
-
-    // List all app frames hosted in the system app: the homescreen,
-    // all regular apps, activities, rocket bar, attention screen and the keyboard.
-    // Bookmark apps and other system app internal frames like captive portal
-    // are also hosted in system app, but they are not using mozapp attribute.
-    let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
+    let frames = systemAppFrame.contentDocument.querySelectorAll('iframe');
     for (let i = 0; i < frames.length; i++) {
       list.push(frames[i]);
     }
-
     return list;
   }
 };
 this.SystemAppProxy = SystemAppProxy;
 
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -43,19 +43,20 @@ EXTRA_PP_COMPONENTS += [
 
 if CONFIG['MOZ_UPDATER']:
     EXTRA_PP_COMPONENTS += [
         'UpdatePrompt.js',
     ]
 
 EXTRA_JS_MODULES += [
     'AlertsHelper.jsm',
-    'AppFrames.jsm',
     'ContentRequestHelper.jsm',
+    'DebuggerActors.js',
     'ErrorPage.jsm',
+    'Frames.jsm',
     'FxAccountsMgmtService.jsm',
     'LogCapture.jsm',
     'LogParser.jsm',
     'LogShake.jsm',
     'SignInToWebsite.jsm',
     'SystemAppProxy.jsm',
     'TelURIParser.jsm',
     'WebappsUpdater.jsm',
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1287,16 +1287,17 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.security.tls.version.max", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Developer edition preferences
 pref("browser.devedition.theme.enabled", false);
+pref("browser.devedition.theme.showCustomizeButton", false);
 
 // Disable the error console
 pref("devtools.errorconsole.enabled", false);
 
 // Developer toolbar and GCLI preferences
 pref("devtools.toolbar.enabled", true);
 pref("devtools.toolbar.visible", false);
 pref("devtools.commands.dir", "");
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -32,16 +32,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
 const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
+const kPrefDeveditionTheme           = "browser.devedition.theme.enabled";
 
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
  */
 const kSubviewEvents = [
   "ViewShowing",
@@ -134,16 +135,17 @@ let gBuildWindows = new Map();
 let gNewElementCount = 0;
 let gGroupWrapperCache = new Map();
 let gSingleWrapperCache = new WeakMap();
 let gListeners = new Set();
 
 let gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
+  gUIStateBeforeReset: null,
 };
 
 let gModuleName = "[CustomizableUI]";
 #include logging.js
 
 let CustomizableUIInternal = {
   initialize: function() {
     LOG("Initializing");
@@ -2294,23 +2296,25 @@ let CustomizableUIInternal = {
     this._rebuildRegisteredAreas();
 
     gResetting = false;
   },
 
   _resetUIState: function() {
     try {
       gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
+      gUIStateBeforeReset.deveditionTheme = Services.prefs.getBoolPref(kPrefDeveditionTheme);
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
     } catch(e) { }
 
     this._resetExtraToolbars();
 
     Services.prefs.clearUserPref(kPrefCustomizationState);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
+    Services.prefs.clearUserPref(kPrefDeveditionTheme);
     LOG("State reset");
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
     gSavedState = null;
@@ -2362,30 +2366,33 @@ let CustomizableUIInternal = {
     }
   },
 
   /**
    * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
    */
   undoReset: function() {
     if (gUIStateBeforeReset.uiCustomizationState == null ||
-        gUIStateBeforeReset.drawInTitlebar == null) {
+        gUIStateBeforeReset.drawInTitlebar == null ||
+        gUIStateBeforeReset.deveditionTheme == null) {
       return;
     }
     gUndoResetting = true;
 
     let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
     let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
+    let deveditionTheme = gUIStateBeforeReset.deveditionTheme;
 
     // Need to clear the previous state before setting the prefs
     // because pref observers may check if there is a previous UI state.
     this._clearPreviousUIState();
 
     Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
     Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
+    Services.prefs.setBoolPref(kPrefDeveditionTheme, deveditionTheme);
     this.loadSavedState();
     // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
     // and we don't need to do anything else here:
     if (gSavedState) {
       for (let areaId of Object.keys(gSavedState.placements)) {
         let placements = gSavedState.placements[areaId];
         gPlacements.set(areaId, placements);
       }
@@ -2553,16 +2560,20 @@ let CustomizableUIInternal = {
         }
       }
     }
 
     if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
       LOG(kPrefDrawInTitlebar + " pref is non-default");
       return false;
     }
+    if (Services.prefs.prefHasUserValue(kPrefDeveditionTheme)) {
+      LOG(kPrefDeveditionTheme + " pref is non-default");
+      return false;
+    }
 
     return true;
   },
 
   setToolbarVisibility: function(aToolbarId, aIsVisible) {
     // We only persist the attribute the first time.
     let isFirstChangedToolbar = true;
     for (let window of CustomizableUI.windows) {
@@ -3253,17 +3264,18 @@ this.CustomizableUI = {
   /**
    * Can the last Restore Defaults operation be undone.
    *
    * @return A boolean stating whether an undo of the
    *         Restore Defaults can be performed.
    */
   get canUndoReset() {
     return gUIStateBeforeReset.uiCustomizationState != null ||
-           gUIStateBeforeReset.drawInTitlebar != null;
+           gUIStateBeforeReset.drawInTitlebar != null ||
+           gUIStateBeforeReset.deveditionTheme != null;
   },
 
   /**
    * Get the placement of a widget. This is by far the best way to obtain
    * information about what the state of your widget is. The internals of
    * this call are cheap (no DOM necessary) and you will know where the user
    * has put your widget.
    *
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -12,16 +12,18 @@ const kPrefCustomizationDebug = "browser
 const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
 const kPaletteId = "customization-palette";
 const kAboutURI = "about:customizing";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kPlaceholderClass = "panel-customization-placeholder";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
+const kDeveditionThemePref = "browser.devedition.theme.enabled";
+const kDeveditionButtonPref = "browser.devedition.theme.showCustomizeButton";
 const kMaxTransitionDurationMs = 2000;
 
 const kPanelItemContextMenu = "customizationPanelItemContextMenu";
 const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/CustomizableUI.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -64,18 +66,21 @@ function CustomizeMode(aWindow) {
   this.tipPanel = this.document.getElementById("customization-tipPanel");
   let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
   if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
     lwthemeButton.setAttribute("hidden", "true");
   }
 #ifdef CAN_DRAW_IN_TITLEBAR
   this._updateTitlebarButton();
   Services.prefs.addObserver(kDrawInTitlebarPref, this, false);
+#endif
+  this._updateDevEditionThemeButton();
+  Services.prefs.addObserver(kDeveditionButtonPref, this, false);
+  Services.prefs.addObserver(kDeveditionThemePref, this, false);
   this.window.addEventListener("unload", this);
-#endif
 };
 
 CustomizeMode.prototype = {
   _changed: false,
   _transitioning: false,
   window: null,
   document: null,
   // areas is used to cache the customizable areas when in customization mode.
@@ -100,16 +105,18 @@ CustomizeMode.prototype = {
   get _handler() {
     return this.window.CustomizationHandler;
   },
 
   uninit: function() {
 #ifdef CAN_DRAW_IN_TITLEBAR
     Services.prefs.removeObserver(kDrawInTitlebarPref, this);
 #endif
+    Services.prefs.removeObserver(kDeveditionButtonPref, this);
+    Services.prefs.removeObserver(kDeveditionThemePref, this);
   },
 
   toggle: function() {
     if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) {
       this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
       return;
     }
     if (this._customizing) {
@@ -1442,32 +1449,31 @@ CustomizeMode.prototype = {
       case "mouseup":
         this._onMouseUp(aEvent);
         break;
       case "keypress":
         if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
           this.exit();
         }
         break;
-#ifdef CAN_DRAW_IN_TITLEBAR
       case "unload":
         this.uninit();
         break;
-#endif
     }
   },
 
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "nsPref:changed":
         this._updateResetButton();
         this._updateUndoResetButton();
 #ifdef CAN_DRAW_IN_TITLEBAR
         this._updateTitlebarButton();
 #endif
+        this._updateDevEditionThemeButton();
         break;
       case "lightweight-theme-window-updated":
         if (aSubject == this.window) {
           aData = JSON.parse(aData);
           if (!aData) {
             this.removeLWTStyling();
           } else {
             this.updateLWTStyling(aData);
@@ -1493,16 +1499,39 @@ CustomizeMode.prototype = {
   },
 
   toggleTitlebar: function(aShouldShowTitlebar) {
     // Drawing in the titlebar means not showing the titlebar, hence the negation:
     Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
   },
 #endif
 
+  _updateDevEditionThemeButton: function() {
+    let button = this.document.getElementById("customization-devedition-theme-button");
+
+    let themeEnabled = Services.prefs.getBoolPref(kDeveditionThemePref);
+    if (themeEnabled) {
+      button.setAttribute("checked", "true");
+    } else {
+      button.removeAttribute("checked");
+    }
+
+    let buttonVisible = Services.prefs.getBoolPref(kDeveditionButtonPref);
+    if (buttonVisible) {
+      button.removeAttribute("hidden");
+    } else {
+      button.setAttribute("hidden", "true");
+    }
+  },
+  toggleDevEditionTheme: function() {
+    let button = this.document.getElementById("customization-devedition-theme-button");
+    let preferenceValue = button.hasAttribute("checked");
+    Services.prefs.setBoolPref(kDeveditionThemePref, preferenceValue);
+  },
+
   _onDragStart: function(aEvent) {
     __dumpDragData(aEvent);
     let item = aEvent.target;
     while (item && item.localName != "toolbarpaletteitem") {
       if (item.localName == "toolbar") {
         return;
       }
       item = item.parentNode;
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -47,16 +47,24 @@
             <toolbarbutton class="customization-lwtheme-menu-footeritem"
                            label="&customizeMode.lwthemes.menuGetMore;"
                            accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
                            tabindex="0"
                            oncommand="gCustomizeMode.getMoreThemes(event);"/>
           </hbox>
         </panel>
       </button>
+
+      <button id="customization-devedition-theme-button"
+              class="customizationmode-button"
+              hidden="true"
+              label="&customizeMode.deveditionTheme.label;"
+              oncommand="gCustomizeMode.toggleDevEditionTheme()"
+              type="checkbox" />
+
       <spacer id="customization-footer-spacer"/>
       <button id="customization-undo-reset-button"
               class="customizationmode-button"
               hidden="true"
               oncommand="gCustomizeMode.undoReset();"
               label="&undoCmd.label;"/>
       <button id="customization-reset-button"
               oncommand="gCustomizeMode.reset();"
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -96,12 +96,50 @@ add_task(function() {
   is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
   is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
 
   Services.prefs.clearUserPref(prefName);
   ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
   is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
 });
 
+// Bug 1082108 - Restore Defaults should clear user pref for devedition theme
+add_task(function() {
+  let prefName = "browser.devedition.theme.enabled";
+  Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
+  let defaultValue = Services.prefs.getBoolPref(prefName);
+  let restoreDefaultsButton = document.getElementById("customization-reset-button");
+  let deveditionThemeButton = document.getElementById("customization-devedition-theme-button");
+  let undoResetButton = document.getElementById("customization-undo-reset-button");
+  ok(CustomizableUI.inDefaultState, "Should be in default state at start of test");
+  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled when in default state");
+  is(deveditionThemeButton.hasAttribute("checked"), defaultValue, "Devedition theme button should reflect pref value");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test");
+  Services.prefs.setBoolPref(prefName, !defaultValue);
+  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed");
+  is(deveditionThemeButton.hasAttribute("checked"), !defaultValue, "Devedition theme button should reflect changed pref value");
+  ok(!CustomizableUI.inDefaultState, "With devedition theme flipped, no longer default");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden after pref change");
+
+  yield gCustomizeMode.reset();
+  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled after reset");
+  is(deveditionThemeButton.hasAttribute("checked"), defaultValue, "devedition theme button should reflect default value after reset");
+  is(Services.prefs.getBoolPref(prefName), defaultValue, "Reset should reset devedition.theme.enabled");
+  ok(CustomizableUI.inDefaultState, "In default state after devedition theme reset");
+  is(undoResetButton.hidden, false, "Undo reset button should be visible after reset");
+  ok(!undoResetButton.disabled, "Undo reset button should be enabled after reset");
+
+  yield gCustomizeMode.undoReset();
+  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled after undo-reset");
+  is(deveditionThemeButton.hasAttribute("checked"), !defaultValue, "devedition theme button should reflect undo-reset value");
+  ok(!CustomizableUI.inDefaultState, "No longer in default state after undo");
+  is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
+
+  Services.prefs.clearUserPref(prefName);
+  ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
+});
+
 add_task(function asyncCleanup() {
   yield gCustomizeMode.reset();
   yield endCustomizing();
 });
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -35,17 +35,27 @@ this.EXPORTED_SYMBOLS = ["injectLoopAPI"
  * object.
  *
  * @param {Error}        error        Error object to copy
  * @param {nsIDOMWindow} targetWindow The content window to attach the API
  */
 const cloneErrorObject = function(error, targetWindow) {
   let obj = new targetWindow.Error();
   for (let prop of Object.getOwnPropertyNames(error)) {
-    obj[prop] = String(error[prop]);
+    let value = error[prop];
+    if (typeof value != "string" && typeof value != "number") {
+      value = String(value);
+    }
+
+    Object.defineProperty(Cu.waiveXrays(obj), prop, {
+      configurable: false,
+      enumerable: true,
+      value: value,
+      writable: false
+    });
   }
   return obj;
 };
 
 /**
  * Makes an object or value available to an unprivileged target window.
  *
  * Primitives are returned as they are, while objects are cloned into the
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -39,16 +39,53 @@ let CloudSync = null;
 #ifdef MOZ_SERVICES_SYNC
 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
                                   "resource://services-sync/main.js");
 #endif
 
 // copied from utilityOverlay.js
 const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
 
+// This function isn't public both because it's synchronous and because it is
+// going to be removed in bug 1072833.
+function IsLivemark(aItemId) {
+  // Since this check may be done on each dragover event, it's worth maintaining
+  // a cache.
+  let self = IsLivemark;
+  if (!("ids" in self)) {
+    const LIVEMARK_ANNO = PlacesUtils.LMANNO_FEEDURI;
+
+    let idsVec = PlacesUtils.annotations.getItemsWithAnnotation(LIVEMARK_ANNO);
+    self.ids = new Set(idsVec);
+
+    let obs = Object.freeze({
+      QueryInterface: XPCOMUtils.generateQI(Ci.nsIAnnotationObserver),
+
+      onItemAnnotationSet(itemId, annoName) {
+        if (annoName == LIVEMARK_ANNO)
+          self.ids.add(itemId);
+      },
+
+      onItemAnnotationRemoved(itemId, annoName) {
+        // If annoName is set to an empty string, the item is gone.
+        if (annoName == LIVEMARK_ANNO || annoName == "")
+          self.ids.delete(itemId);
+      },
+
+      onPageAnnotationSet() { },
+      onPageAnnotationRemoved() { },
+    });
+    PlacesUtils.annotations.addObserver(obs);
+    PlacesUtils.registerShutdownFunction(() => {
+      PlacesUtils.annotations.removeObserver(obs);
+    });
+  }
+  return self.ids.has(aItemId);
+}
+
 this.PlacesUIUtils = {
   ORGANIZER_LEFTPANE_VERSION: 7,
   ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
   ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
 
   LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
   DESCRIPTION_ANNO: "bookmarkProperties/description",
 
@@ -556,16 +593,99 @@ this.PlacesUIUtils = {
    */
   getItemDescription: function PUIU_getItemDescription(aItemId) {
     if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO))
       return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO);
     return "";
   },
 
   /**
+   * Check whether or not the given node represents a removable entry (either in
+   * history or in bookmarks).
+   *
+   * @param aNode
+   *        a node, except the root node of a query.
+   * @return true if the aNode represents a removable entry, false otherwise.
+   */
+  canUserRemove: function (aNode) {
+    let parentNode = aNode.parent;
+    if (!parentNode)
+      throw new Error("canUserRemove doesn't accept root nodes");
+
+    // If it's not a bookmark, we can remove it unless it's a child of a
+    // livemark.
+    if (aNode.itemId == -1) {
+      // Rather than executing a db query, checking the existence of the feedURI
+      // annotation, detect livemark children by the fact that they are the only
+      // direct non-bookmark children of bookmark folders.
+      return !PlacesUtils.nodeIsFolder(parentNode);
+    }
+
+    // Otherwise it has to be a child of an editable folder.
+    return !this.isContentsReadOnly(parentNode);
+  },
+
+  /**
+   * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
+   * TO GUIDS IS COMPLETE (BUG 1071511).
+   *
+   * Check whether or not the given node or item-id points to a folder which
+   * should not be modified by the user (i.e. its children should be unremovable
+   * and unmovable, new children should be disallowed, etc).
+   * These semantics are not inherited, meaning that read-only folder may
+   * contain editable items (for instance, the places root is read-only, but all
+   * of its direct children aren't).
+   *
+   * You should only pass folder item ids or folder nodes for aNodeOrItemId.
+   * While this is only enforced for the node case (if an item id of a separator
+   * or a bookmark is passed, false is returned), it's considered the caller's
+   * job to ensure that it checks a folder.
+   * Also note that folder-shortcuts should only be passed as result nodes.
+   * Otherwise they are just treated as bookmarks (i.e. false is returned).
+   *
+   * @param aNodeOrItemId
+   *        any item id or result node.
+   * @throws if aNodeOrItemId is neither an item id nor a folder result node.
+   * @note livemark "folders" are considered read-only (but see bug 1072833).
+   * @return true if aItemId points to a read-only folder, false otherwise.
+   */
+  isContentsReadOnly: function (aNodeOrItemId) {
+    let itemId;
+    if (typeof(aNodeOrItemId) == "number") {
+      itemId = aNodeOrItemId;
+    }
+    else if (PlacesUtils.nodeIsFolder(aNodeOrItemId)) {
+      itemId = PlacesUtils.getConcreteItemId(aNodeOrItemId);
+    }
+    else {
+      throw new Error("invalid value for aNodeOrItemId");
+    }
+
+    if (itemId == PlacesUtils.placesRootId || IsLivemark(itemId))
+      return true;
+
+    // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+    // performing at least a synchronous DB query (and on its very first call
+    // in a fresh profile, it also creates the entire structure).
+    // Therefore we don't want to this function, which is called very often by
+    // isCommandEnabled, to ever be the one that invokes it first, especially
+    // because isCommandEnabled may be called way before the left pane folder is
+    // even created (for example, if the user only uses the bookmarks menu or
+    // toolbar for managing bookmarks).  To do so, we avoid comparing to those
+    // special folder if the lazy getter is still in place.  This is safe merely
+    // because the only way to access the left pane contents goes through
+    // "resolving" the leftPaneFolderId getter.
+    if ("get" in Object.getOwnPropertyDescriptor(this, "leftPaneFolderId"))
+      return false;
+
+    return itemId == this.leftPaneFolderId ||
+           itemId == this.allBookmarksFolderId;
+  },
+
+  /**
    * Gives the user a chance to cancel loading lots of tabs at once
    */
   _confirmOpenInTabs:
   function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) {
     const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
     var reallyOpen = true;
 
     if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
@@ -957,18 +1077,16 @@ this.PlacesUIUtils = {
       create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
               // Left Pane Root Folder.
         let folderId = bs.createFolder(aParentId,
                                        queries[aFolderName].title,
                                        bs.DEFAULT_INDEX);
         // We should never backup this, since it changes between profiles.
         as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
                              0, as.EXPIRE_NEVER);
-        // Disallow manipulating this folder within the organizer UI.
-        bs.setFolderReadonly(folderId, true);
 
         if (aIsRoot) {
           // Mark as special left pane root.
           as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
                                PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
                                0, as.EXPIRE_NEVER);
         }
         else {
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1371,18 +1371,18 @@ PlacesToolbar.prototype = {
       return null;
 
     let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
     let elt = aEvent.target;
     if (elt._placesNode && elt != this._rootElt &&
         elt.localName != "menupopup") {
       let eltRect = elt.getBoundingClientRect();
       let eltIndex = Array.indexOf(this._rootElt.childNodes, elt);
-      if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
-          !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
+      if (PlacesUIUtils.nodeIsFolder(elt._placesNode) &&
+          !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) {
         // This is a folder.
         // If we are in the middle of it, drop inside it.
         // Otherwise, drop before it, with regards to RTL mode.
         let threshold = eltRect.width * 0.25;
         if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
                        : (aEvent.clientX < eltRect.left + threshold)) {
           // Drop before this folder.
           dropPoint.ip =
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -195,17 +195,17 @@ PlacesController.prototype = {
     case "placesCmd_reload":
       // Livemark containers
       var selectedNode = this._view.selectedNode;
       return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
     case "placesCmd_sortBy:name":
       var selectedNode = this._view.selectedNode;
       return selectedNode &&
              PlacesUtils.nodeIsFolder(selectedNode) &&
-             !PlacesUtils.nodeIsReadOnly(selectedNode) &&
+             !PlacesUIUtils.isContentsReadOnly(selectedNode) &&
              this._view.result.sortingMode ==
                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_createBookmark":
       var node = this._view.selectedNode;
       return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
     default:
       return false;
     }
@@ -325,31 +325,17 @@ PlacesController.prototype = {
 
     for (var j = 0; j < ranges.length; j++) {
       var nodes = ranges[j];
       for (var i = 0; i < nodes.length; ++i) {
         // Disallow removing the view's root node
         if (nodes[i] == root)
           return false;
 
-        if (PlacesUtils.nodeIsFolder(nodes[i]) &&
-            !PlacesControllerDragHelper.canMoveNode(nodes[i]))
-          return false;
-
-        // We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
-        // a node has children that cannot be edited, reordered or removed. Here,
-        // we don't care if a node's children can't be reordered or edited, just
-        // that they're removable. All history results have removable children
-        // (based on the principle that any URL in the history table should be
-        // removable), but some special bookmark folders may have non-removable
-        // children, e.g. live bookmark folder children. It doesn't make sense
-        // to delete a child of a live bookmark folder, since when the folder
-        // refreshes, the child will return.
-        var parent = nodes[i].parent || root;
-        if (PlacesUtils.isReadonlyFolder(parent))
+        if (!PlacesUIUtils.canUserRemove(nodes[i]))
           return false;
       }
     }
 
     return true;
   },
 
   /**
@@ -1555,83 +1541,41 @@ let PlacesControllerDragHelper = {
    *
    * @param   aUnwrappedNode
    *          A node unwrapped by PlacesUtils.unwrapNodes().
    * @return True if the node can be moved, false otherwise.
    */
   canMoveUnwrappedNode: function (aUnwrappedNode) {
     return aUnwrappedNode.id > 0 &&
            !PlacesUtils.isRootItem(aUnwrappedNode.id) &&
-           aUnwrappedNode.parent != PlacesUtils.placesRootId &&
+           !PlacesUIUtils.isContentsReadOnly(aUnwrappedNode.parent) ||
            aUnwrappedNode.parent != PlacesUtils.tagsFolderId &&
-           aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId &&
-           !aUnwrappedNode.parentReadOnly;
+           aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId;
   },
 
   /**
    * Determines if a node can be moved.
    *
    * @param   aNode
    *          A nsINavHistoryResultNode node.
    * @return True if the node can be moved, false otherwise.
    */
   canMoveNode:
   function PCDH_canMoveNode(aNode) {
-    // Can't move query root.
-    if (!aNode.parent)
-      return false;
-
-    let parentId = PlacesUtils.getConcreteItemId(aNode.parent);
-    let concreteId = PlacesUtils.getConcreteItemId(aNode);
-
-    // Can't move children of tag containers.
-    if (PlacesUtils.nodeIsTagQuery(aNode.parent))
-      return false;
-
-    // Can't move children of read-only containers.
-    if (PlacesUtils.nodeIsReadOnly(aNode.parent))
-      return false;
-
-    // Check for special folders, etc.
-    if (PlacesUtils.nodeIsContainer(aNode) &&
-        !this.canMoveContainer(aNode.itemId, parentId))
+    // Only bookmark items are movable.
+    if (aNode.itemId == -1)
       return false;
 
-    return true;
-  },
-
-  /**
-   * Determines if a container node can be moved.
-   *
-   * @param   aId
-   *          A bookmark folder id.
-   * @param   [optional] aParentId
-   *          The parent id of the folder.
-   * @return True if the container can be moved to the target.
-   */
-  canMoveContainer:
-  function PCDH_canMoveContainer(aId, aParentId) {
-    if (aId == -1)
-      return false;
-
-    // Disallow moving of roots and special folders.
-    const ROOTS = [PlacesUtils.placesRootId, PlacesUtils.bookmarksMenuFolderId,
-                   PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
-                   PlacesUtils.toolbarFolderId];
-    if (ROOTS.indexOf(aId) != -1)
-      return false;
-
-    // Get parent id if necessary.
-    if (aParentId == null || aParentId == -1)
-      aParentId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
-
-    if (PlacesUtils.bookmarks.getFolderReadonly(aParentId))
-      return false;
-
-    return true;
+    // Once tags and bookmarked are divorced, the tag-query check should be
+    // removed.
+    let parentNode = aNode.parent;
+    return parentNode != null &&
+           !(PlacesUtils.nodeIsFolder(parentNode) &&
+             PlacesUIUtils.isContentsReadOnly(parentNode)) &&
+           !PlacesUtils.nodeIsTagQuery(parentNode);
   },
 
   /**
    * Handles the drop of one or more items onto a view.
    * @param   insertionPoint
    *          The insertion point where the items should be dropped
    */
   onDrop: Task.async(function* (insertionPoint, dt) {
@@ -1721,22 +1665,20 @@ let PlacesControllerDragHelper = {
 
   /**
    * Checks if we can insert into a container.
    * @param   aContainer
    *          The container were we are want to drop
    */
   disallowInsertion: function(aContainer) {
     NS_ASSERT(aContainer, "empty container");
-    // Allow dropping into Tag containers.
-    if (PlacesUtils.nodeIsTagQuery(aContainer))
-      return false;
-    // Disallow insertion of items under readonly folders.
-    return (!PlacesUtils.nodeIsFolder(aContainer) ||
-             PlacesUtils.nodeIsReadOnly(aContainer));
+    // Allow dropping into Tag containers and editable folders.
+    return !PlacesUtils.nodeIsTagQuery(aContainer) &&
+           (!PlacesUtils.nodeIsFolder(aContainer) ||
+            PlacesUIUtils.isContentsReadOnly(aContainer));
   }
 };
 
 
 XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService",
                                    "@mozilla.org/widget/dragservice;1",
                                    "nsIDragService");
 
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -101,19 +101,19 @@
               if (isMenu && elt.lastChild &&
                   elt.lastChild.hasAttribute("placespopup"))
                 dropPoint.folderElt = elt;
               return dropPoint;
             }
 
             let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
                             elt._placesNode.title : null;
-            if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
-                 PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
-                !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
+            if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
+                 !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
+                PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
               // This is a folder or a tag container.
               if (eventY - eltY < eltHeight * 0.20) {
                 // If mouse is in the top part of the element, drop above folder.
                 dropPoint.ip = new InsertionPoint(
                                     PlacesUtils.getConcreteItemId(resultNode),
                                     -1,
                                     Ci.nsITreeView.DROP_BEFORE,
                                     tagName,
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1644,33 +1644,49 @@ PlacesTreeView.prototype = {
     this._result.sortingMode = newSort;
   },
 
   isEditable: function PTV_isEditable(aRow, aColumn) {
     // At this point we only support editing the title field.
     if (aColumn.index != 0)
       return false;
 
-    // Only bookmark-nodes are editable, and those are never built lazily
     let node = this._rows[aRow];
-    if (!node || node.itemId == -1)
+    if (!node) {
+      Cu.reportError("isEditable called for an unbuilt row.");
+      return false;
+    }
+    let itemId = node.itemId;
+
+    // Only bookmark-nodes are editable.  Fortunately, this checks also takes
+    // care of livemark children.
+    if (itemId == -1)
       return false;
 
-    // The following items are never editable:
-    // * Read-only items.
+    // The following items are also not editable, even though they are bookmark
+    // items.
     // * places-roots
+    // * the left pane special folders and queries (those are place: uri
+    //   bookmarks)
     // * separators
-    if (PlacesUtils.nodeIsReadOnly(node) ||
-        PlacesUtils.nodeIsSeparator(node))
+    //
+    // Note that concrete itemIds aren't used intentionally.  For example, we
+    // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
+    // except for the one under All Bookmarks.
+    if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemId))
       return false;
 
-    if (PlacesUtils.nodeIsFolder(node)) {
-      let itemId = PlacesUtils.getConcreteItemId(node);
-      if (PlacesUtils.isRootItem(itemId))
-        return false;
+    let parentId = PlacesUtils.getConcreteItemId(node.parent);
+    if (parentId == PlacesUIUtils.leftPaneFolderId ||
+        parentId == PlacesUIUtils.allBookmarksFolderId) {
+      // Note that the for the time being this is the check that actually
+      // blocks renaming places "roots", and not the isRootItem check above.
+      // That's because places root are only exposed through folder shortcuts
+      // descendants of the left pane folder.
+      return false;
     }
 
     return true;
   },
 
   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
     // We may only get here if the cell is editable.
     let node = this._rows[aRow];
--- a/browser/components/places/tests/browser/browser_423515.js
+++ b/browser/components/places/tests/browser/browser_423515.js
@@ -22,18 +22,16 @@ function test() {
   tests.push({
     populate: function() {
       this.id =
         PlacesUtils.bookmarks.createFolder(rootId, "", IDX);
     },
     validate: function() {
       is(rootNode.childCount, 1,
         "populate added data to the test root");
-      is(PlacesControllerDragHelper.canMoveContainer(this.id),
-         true, "can move regular folder id");
       is(PlacesControllerDragHelper.canMoveNode(rootNode.getChild(0)),
          true, "can move regular folder node");
     }
   });
 
   // add a regular folder shortcut, should be moveable
   tests.push({
     populate: function() {
@@ -52,19 +50,16 @@ function test() {
 
       var shortcutNode = rootNode.getChild(1);
       is(shortcutNode.type, 9, "node is folder shortcut");
       is(this.shortcutId, shortcutNode.itemId, "shortcut id and shortcut node item id match");
 
       var concreteId = PlacesUtils.getConcreteItemId(shortcutNode);
       is(concreteId, folderNode.itemId, "shortcut node id and concrete id match");
 
-      is(PlacesControllerDragHelper.canMoveContainer(this.shortcutId),
-         true, "can move folder shortcut id");
-
       is(PlacesControllerDragHelper.canMoveNode(shortcutNode),
          true, "can move folder shortcut node");
     }
   });
 
   // add a regular query, should be moveable
   tests.push({
     populate: function() {
@@ -78,19 +73,16 @@ function test() {
         "populated data to the test root");
 
       var bmNode = rootNode.getChild(0);
       is(bmNode.itemId, this.bookmarkId, "bookmark id and bookmark node item id match");
 
       var queryNode = rootNode.getChild(1);
       is(queryNode.itemId, this.queryId, "query id and query node item id match");
 
-      is(PlacesControllerDragHelper.canMoveContainer(this.queryId),
-         true, "can move query id");
-
       is(PlacesControllerDragHelper.canMoveNode(queryNode),
          true, "can move query node");
     }
   });
 
   // test that special folders cannot be moved
   // test that special folders shortcuts can be moved
   tests.push({
@@ -122,33 +114,26 @@ function test() {
         node.containerOpen = false;
         ok(false, "Unable to find child node");
         return null;
       }
 
       for (var i = 0; i < this.folders.length; i++) {
         var id = this.folders[i];
 
-        is(PlacesControllerDragHelper.canMoveContainer(id),
-           false, "shouldn't be able to move special folder id");
-
         var node = getRootChildNode(id);
         isnot(node, null, "Node found");
         is(PlacesControllerDragHelper.canMoveNode(node),
            false, "shouldn't be able to move special folder node");
 
         var shortcutId = this.shortcuts[id];
         var shortcutNode = rootNode.getChild(i);
 
         is(shortcutNode.itemId, shortcutId, "shortcut id and shortcut node item id match");
 
-        dump("can move shortcut id?\n");
-        is(PlacesControllerDragHelper.canMoveContainer(shortcutId),
-           true, "should be able to move special folder shortcut id");
-
         dump("can move shortcut node?\n");
         is(PlacesControllerDragHelper.canMoveNode(shortcutNode),
            true, "should be able to move special folder shortcut node");
       }
     }
   });
 
   // test that a tag container cannot be moved
@@ -164,56 +149,23 @@ function test() {
       var options = PlacesUtils.history.getNewQueryOptions();
       options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY;
       var tagsNode = PlacesUtils.history.executeQuery(query, options).root;
 
       tagsNode.containerOpen = true;
       is(tagsNode.childCount, 1, "has new tag");
 
       var tagNode = tagsNode.getChild(0);
-      
+
       is(PlacesControllerDragHelper.canMoveNode(tagNode),
          false, "should not be able to move tag container node");
-
       tagsNode.containerOpen = false;
     }
   });
 
-  // test that any child of a read-only node cannot be moved
-  tests.push({
-    populate: function() {
-      this.id =
-        PlacesUtils.bookmarks.createFolder(rootId, "foo", IDX);
-      PlacesUtils.bookmarks.createFolder(this.id, "bar", IDX);
-      PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
-    },
-    validate: function() {
-      is(rootNode.childCount, 1,
-        "populate added data to the test root");
-      var readOnlyFolder = rootNode.getChild(0);
-
-      // test that we can move the read-only folder
-      is(PlacesControllerDragHelper.canMoveContainer(this.id),
-         true, "can move read-only folder id");
-      is(PlacesControllerDragHelper.canMoveNode(readOnlyFolder),
-         true, "can move read-only folder node");
-
-      // test that we cannot move the child of a read-only folder
-      readOnlyFolder.QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      readOnlyFolder.containerOpen = true;
-      var childFolder = readOnlyFolder.getChild(0);
-
-      is(PlacesControllerDragHelper.canMoveContainer(childFolder.itemId),
-         false, "cannot move a child of a read-only folder");
-      is(PlacesControllerDragHelper.canMoveNode(childFolder),
-         false, "cannot move a child node of a read-only folder node");
-      readOnlyFolder.containerOpen = false;
-    }
-  });
-
   tests.forEach(function(aTest) {
     PlacesUtils.bookmarks.removeFolderChildren(rootId);
     aTest.populate();
     aTest.validate();
   });
 
   rootNode.containerOpen = false;
   PlacesUtils.bookmarks.removeItem(rootId);
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -125,16 +125,25 @@
   padding: 2px 12px;
   background-color: rgb(251,251,251);
   color: rgb(71,71,71);
   box-shadow: 0 1px rgba(255, 255, 255, 0.5),
               inset 0 1px rgba(255, 255, 255, 0.5);
   -moz-appearance: none;
 }
 
+#customization-titlebar-visibility-button[checked],
+#customization-devedition-theme-button[checked] {
+  background-color: rgb(218, 218, 218);
+  border-color: rgb(168, 168, 168);
+  text-shadow: 0 1px rgb(236, 236, 236);
+  box-shadow: 0 1px rgba(255, 255, 255, 0.5),
+              inset 0 1px rgb(196, 196, 196);
+}
+
 .customizationmode-button[disabled="true"] {
   opacity: .5;
 }
 
 .customizationmode-button > .box-inherit > .box-inherit > .button-icon,
 .customizationmode-button > .button-box > .button-icon {
   height: 24px;
 }
@@ -151,21 +160,16 @@
 }
 
 #customization-titlebar-visibility-button > .button-box > .button-icon {
   vertical-align: middle;
 }
 
 #customization-titlebar-visibility-button[checked] {
   -moz-image-region: rect(0, 48px, 24px, 24px);
-  background-color: rgb(218, 218, 218);
-  border-color: rgb(168, 168, 168);
-  text-shadow: 0 1px rgb(236, 236, 236);
-  box-shadow: 0 1px rgba(255, 255, 255, 0.5),
-              inset 0 1px rgb(196, 196, 196);
 }
 
 #main-window[customize-entered] #customization-panel-container {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-separatorHorizontal.png"),
                     url("chrome://browser/skin/customizableui/customizeMode-separatorVertical.png"),
                     url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, #3e86ce, #3878ba);
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -2403,20 +2403,28 @@ HTMLInputElement::SetUserInput(const nsA
     Sequence<nsString> list;
     list.AppendElement(aValue);
     MozSetFileNameArray(list);
     return NS_OK;
   } else {
     SetValueInternal(aValue, true, true);
   }
 
-  return nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
-                                              static_cast<nsIDOMHTMLInputElement*>(this),
-                                              NS_LITERAL_STRING("input"), true,
-                                              true);
+  nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+                                       static_cast<nsIDOMHTMLInputElement*>(this),
+                                       NS_LITERAL_STRING("input"), true,
+                                       true);
+
+  // If this element is not currently focused, it won't receive a change event for this
+  // update through the normal channels. So fire a change event immediately, instead.
+  if (!ShouldBlur(this)) {
+    FireChangeEventIfNeeded();
+  }
+
+  return NS_OK;
 }
 
 nsIEditor*
 HTMLInputElement::GetEditor()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetEditor();
--- a/content/html/content/test/test_bug388558.html
+++ b/content/html/content/test/test_bug388558.html
@@ -37,17 +37,17 @@ function testUserInput() {
   is(inputChange, 1,
      "Change event dispatched when setting the value of the input element");
 
   input.value = "";
   is(inputChange, 1, 
      "Change event dispatched when setting the value of the input element (2).");
 
   SpecialPowers.wrap(input).setUserInput("foo");
-  is(inputChange, 1,
+  is(inputChange, 2,
      "Change event dispatched when input element doesn't have focus.");
 
   textarea.focus();
   textarea.setUserInput("foo");
   textarea.blur();
   is(textareaChange, 1, "Textarea element should have got one change event.");
 
   textarea.focus();
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2853,24 +2853,31 @@ nsDocShell::PopProfileTimelineMarkers(JS
   // {name,start,end} JS object.
   // Paint markers are different because paint is handled at root docShell level
   // in the information that a paint was done is then stored at each sub
   // docShell level but we can only be sure that a paint did happen in a
   // docShell if an Layer marker type was recorded too.
 
   nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
 
+  // If we see an unpaired START, we keep it around for the next call
+  // to PopProfileTimelineMarkers.  We store the kept START objects in
+  // this array.
+  decltype(mProfileTimelineMarkers) keptMarkers;
+
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
     ProfilerMarkerTracing* startPayload = static_cast<ProfilerMarkerTracing*>(
       mProfileTimelineMarkers[i]->mPayload);
     const char* startMarkerName = mProfileTimelineMarkers[i]->mName;
 
     bool hasSeenPaintedLayer = false;
 
     if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
+      bool hasSeenEnd = false;
+
       // The assumption is that the devtools timeline flushes markers frequently
       // enough for the amount of markers to always be small enough that the
       // nested for loop isn't going to be a performance problem.
       for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
         ProfilerMarkerTracing* endPayload = static_cast<ProfilerMarkerTracing*>(
           mProfileTimelineMarkers[j]->mPayload);
         const char* endMarkerName = mProfileTimelineMarkers[j]->mName;
 
@@ -2888,25 +2895,36 @@ nsDocShell::PopProfileTimelineMarkers(JS
           if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
             mozilla::dom::ProfileTimelineMarker marker;
             marker.mName = NS_ConvertUTF8toUTF16(startMarkerName);
             marker.mStart = mProfileTimelineMarkers[i]->mTime;
             marker.mEnd = mProfileTimelineMarkers[j]->mTime;
             profileTimelineMarkers.AppendElement(marker);
           }
 
+          // We want the start to be dropped either way.
+          hasSeenEnd = true;
+
           break;
         }
       }
+
+      // If we did not see the corresponding END, keep the START.
+      if (!hasSeenEnd) {
+        keptMarkers.AppendElement(mProfileTimelineMarkers[i]);
+        mProfileTimelineMarkers.RemoveElementAt(i);
+        --i;
+      }
     }
   }
 
   ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers);
 
   ClearProfileTimelineMarkers();
+  mProfileTimelineMarkers.SwapElements(keptMarkers);
 
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
 float
@@ -2947,18 +2965,17 @@ nsDocShell::AddProfileTimelineMarker(con
 #endif
 }
 
 void
 nsDocShell::ClearProfileTimelineMarkers()
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
-    delete mProfileTimelineMarkers[i]->mPayload;
-    mProfileTimelineMarkers[i]->mPayload = nullptr;
+    delete mProfileTimelineMarkers[i];
   }
   mProfileTimelineMarkers.Clear();
 #endif
 }
 
 nsIDOMStorageManager*
 nsDocShell::TopSessionStorageManager()
 {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -16,16 +16,17 @@
 #include "nsIScrollable.h"
 #include "nsITextScroll.h"
 #include "nsIContentViewerContainer.h"
 #include "nsIDOMStorageManager.h"
 #include "nsDocLoader.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeckoProfiler.h"
+#include "ProfilerMarkers.h"
 
 // Helper Classes
 #include "nsCOMPtr.h"
 #include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 
@@ -954,21 +955,27 @@ private:
     {
       InternalProfileTimelineMarker(const char* aName,
                                     ProfilerMarkerTracing* aPayload,
                                     float aTime)
         : mName(aName)
         , mPayload(aPayload)
         , mTime(aTime)
       {}
+
+      ~InternalProfileTimelineMarker()
+      {
+        delete mPayload;
+      }
+
       const char* mName;
       ProfilerMarkerTracing* mPayload;
       float mTime;
     };
-    nsTArray<nsAutoPtr<InternalProfileTimelineMarker>> mProfileTimelineMarkers;
+    nsTArray<InternalProfileTimelineMarker*> mProfileTimelineMarkers;
 
     // Get the elapsed time (in millis) since the profile timeline recording
     // started
     float GetProfileTimelineDelta();
 
     // Get rid of all the timeline markers accumulated so far
     void ClearProfileTimelineMarkers();
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -83,30 +83,27 @@
 #include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE
 #include "ClientLayerManager.h"
 #include "nsRefreshDriver.h"
 #include "nsIContentViewer.h"
 #include "LayersLogging.h"
 #include "mozilla/Preferences.h"
 #include "nsFrameSelection.h"
 #include "FrameLayerBuilder.h"
+#include "mozilla/layers/AsyncPanZoomController.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 #include "GeckoProfiler.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
 #include "RestyleManager.h"
 
-// Additional includes used on B2G by code in GetOrMaybeCreateDisplayPort().
-#ifdef MOZ_WIDGET_GONK
-#include "mozilla/layers/AsyncPanZoomController.h"
-#endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::image;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::gfx;
 
@@ -2729,19 +2726,16 @@ nsLayoutUtils::GetFramesForArea(nsIFrame
 #endif
 
   nsDisplayItem::HitTestState hitTestState;
   list.HitTest(&builder, aRect, &hitTestState, &aOutFrames);
   list.DeleteAll();
   return NS_OK;
 }
 
-// This function is only used on B2G, and some compilers complain about
-// unused static functions, so we need to #ifdef it.
-#ifdef MOZ_WIDGET_GONK
 // aScrollFrame and aScrollFrameAsScrollable must be non-nullptr
 static FrameMetrics
 CalculateFrameMetricsForDisplayPort(nsIFrame* aScrollFrame,
                                     nsIScrollableFrame* aScrollFrameAsScrollable) {
   // Calculate the metrics necessary for calculating the displayport.
   // This code has a lot in common with the code in ComputeFrameMetrics();
   // we may want to refactor this at some point.
   FrameMetrics metrics;
@@ -2779,17 +2773,16 @@ CalculateFrameMetricsForDisplayPort(nsIF
   metrics.SetScrollOffset(CSSPoint::FromAppUnits(
       aScrollFrameAsScrollable->GetScrollPosition()));
 
   metrics.mScrollableRect = CSSRect::FromAppUnits(
       nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrameAsScrollable, nullptr));
 
   return metrics;
 }
-#endif
 
 bool
 nsLayoutUtils::GetOrMaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder,
                                            nsIFrame* aScrollFrame,
                                            nsRect aDisplayPortBase,
                                            nsRect* aOutDisplayport) {
   nsIContent* content = aScrollFrame->GetContent();
   nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame);
@@ -2799,18 +2792,17 @@ nsLayoutUtils::GetOrMaybeCreateDisplayPo
 
   // Set the base rect. Note that this will not influence 'haveDisplayPort',
   // which is based on either the whole rect or margins being set, but it
   // will affect what is returned in 'aOutDisplayPort' if margins are set.
   SetDisplayPortBase(content, aDisplayPortBase);
 
   bool haveDisplayPort = GetDisplayPort(content, aOutDisplayport);
 
-#ifdef MOZ_WIDGET_GONK
-  // On B2G, we perform an optimization where we ensure that at least one
+  // We perform an optimization where we ensure that at least one
   // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a displayport.
   // If that's not the case yet, and we are async-scrollable, we will get a
   // displayport.
   // Note: we only do this in processes where we do subframe scrolling to
   //       begin with (i.e., not in the parent process on B2G).
   if (aBuilder.IsPaintingToWindow() && WantSubAPZC() &&
       !aBuilder.HaveScrollableDisplayPort() &&
       scrollableFrame->WantAsyncScroll()) {
@@ -2829,17 +2821,16 @@ nsLayoutUtils::GetOrMaybeCreateDisplayPo
           alignment.height, 0, nsLayoutUtils::RepaintMode::DoNotRepaint);
       haveDisplayPort = GetDisplayPort(content, aOutDisplayport);
       NS_ASSERTION(haveDisplayPort, "should have a displayport after having just set it");
     }
 
     // Record that the we now have a scrollable display port.
     aBuilder.SetHaveScrollableDisplayPort();
   }
-#endif
 
   return haveDisplayPort;
 }
 
 nsresult
 nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame,
                           const nsRegion& aDirtyRegion, nscolor aBackstop,
                           uint32_t aFlags)
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -292,70 +292,20 @@ pref("browser.mirroring.enabled.roku", t
 // Enable sparse localization by setting a few package locale overrides
 pref("chrome.override_package.global", "browser");
 pref("chrome.override_package.mozapps", "browser");
 pref("chrome.override_package.passwordmgr", "browser");
 
 // enable xul error pages
 pref("browser.xul.error_pages.enabled", true);
 
-// Specify emptyRestriction = 0 so that bookmarks appear in the list by default
-pref("browser.urlbar.default.behavior", 0);
-pref("browser.urlbar.default.behavior.emptyRestriction", 0);
-
-// Let the faviconservice know that we display favicons as 32x32px so that it
-// uses the right size when optimizing favicons
-pref("places.favicons.optimizeToDimension", 32);
-
-// various and sundry awesomebar prefs (should remove/re-evaluate
-// these once bug 447900 is fixed)
-pref("browser.urlbar.clickSelectsAll", true);
-pref("browser.urlbar.doubleClickSelectsAll", true);
-pref("browser.urlbar.autoFill", false);
-pref("browser.urlbar.matchOnlyTyped", false);
-pref("browser.urlbar.matchBehavior", 1);
-pref("browser.urlbar.filter.javascript", true);
-pref("browser.urlbar.maxRichResults", 24); // increased so we see more results when portrait
-pref("browser.urlbar.search.chunkSize", 1000);
-pref("browser.urlbar.search.timeout", 100);
-pref("browser.urlbar.restrict.history", "^");
-pref("browser.urlbar.restrict.bookmark", "*");
-pref("browser.urlbar.restrict.tag", "+");
-pref("browser.urlbar.match.title", "#");
-pref("browser.urlbar.match.url", "@");
-pref("browser.urlbar.autocomplete.search_threshold", 5);
 pref("browser.history.grouping", "day");
 pref("browser.history.showSessions", false);
 pref("browser.sessionhistory.max_entries", 50);
 pref("browser.history_expire_sites", 40000);
-pref("browser.places.migratePostDataAnnotations", true);
-pref("browser.places.updateRecentTagsUri", true);
-pref("places.frecency.numVisits", 10);
-pref("places.frecency.numCalcOnIdle", 50);
-pref("places.frecency.numCalcOnMigrate", 50);
-pref("places.frecency.updateIdleTime", 60000);
-pref("places.frecency.firstBucketCutoff", 4);
-pref("places.frecency.secondBucketCutoff", 14);
-pref("places.frecency.thirdBucketCutoff", 31);
-pref("places.frecency.fourthBucketCutoff", 90);
-pref("places.frecency.firstBucketWeight", 100);
-pref("places.frecency.secondBucketWeight", 70);
-pref("places.frecency.thirdBucketWeight", 50);
-pref("places.frecency.fourthBucketWeight", 30);
-pref("places.frecency.defaultBucketWeight", 10);
-pref("places.frecency.embedVisitBonus", 0);
-pref("places.frecency.linkVisitBonus", 100);
-pref("places.frecency.typedVisitBonus", 2000);
-pref("places.frecency.bookmarkVisitBonus", 150);
-pref("places.frecency.downloadVisitBonus", 0);
-pref("places.frecency.permRedirectVisitBonus", 0);
-pref("places.frecency.tempRedirectVisitBonus", 0);
-pref("places.frecency.defaultVisitBonus", 0);
-pref("places.frecency.unvisitedBookmarkBonus", 140);
-pref("places.frecency.unvisitedTypedBonus", 200);
 
 // disable color management
 pref("gfx.color_management.mode", 0);
 
 // 0=fixed margin, 1=velocity bias, 2=dynamic resolution, 3=no margins, 4=prediction bias
 pref("gfx.displayport.strategy", 1);
 
 // all of the following displayport strategy prefs will be divided by 1000
--- a/mobile/android/base/BrowserLocaleManager.java
+++ b/mobile/android/base/BrowserLocaleManager.java
@@ -244,16 +244,47 @@ public class BrowserLocaleManager implem
         final Locale changed = configuration.locale;
         if (changed.equals(currentActivityLocale)) {
             return null;
         }
 
         return changed;
     }
 
+    /**
+     * Gecko needs to know the OS locale to compute a useful Accept-Language
+     * header. If it changed since last time, send a message to Gecko and
+     * persist the new value. If unchanged, returns immediately.
+     *
+     * @param prefs the SharedPreferences instance to use. Cannot be null.
+     * @param osLocale the new locale instance. Safe if null.
+     */
+    public static void storeAndNotifyOSLocale(final SharedPreferences prefs,
+                                              final Locale osLocale) {
+        if (osLocale == null) {
+            return;
+        }
+
+        final String lastOSLocale = prefs.getString("osLocale", null);
+        final String osLocaleString = osLocale.toString();
+
+        if (osLocaleString.equals(lastOSLocale)) {
+            return;
+        }
+
+        // Store the Java-native form.
+        prefs.edit().putString("osLocale", osLocaleString).apply();
+
+        // The value we send to Gecko should be a language tag, not
+        // a Java locale string.
+        final String osLanguageTag = BrowserLocaleManager.getLanguageTag(osLocale);
+        final GeckoEvent localeOSEvent = GeckoEvent.createBroadcastEvent("Locale:OS", osLanguageTag);
+        GeckoAppShell.sendEventToGecko(localeOSEvent);
+    }
+
     @Override
     public String getAndApplyPersistedLocale(Context context) {
         initialize(context);
 
         final long t1 = android.os.SystemClock.uptimeMillis();
         final String localeCode = getPersistedLocale(context);
         if (localeCode == null) {
             return null;
@@ -325,16 +356,19 @@ public class BrowserLocaleManager implem
         config.locale = locale;
         res.updateConfiguration(config, null);
     }
 
     private SharedPreferences getSharedPreferences(Context context) {
         return GeckoSharedPrefs.forApp(context);
     }
 
+    /**
+     * @return the persisted locale in Java format: "en_US".
+     */
     private String getPersistedLocale(Context context) {
         final SharedPreferences settings = getSharedPreferences(context);
         final String locale = settings.getString(PREF_LOCALE, "");
 
         if ("".equals(locale)) {
             return null;
         }
         return locale;
@@ -359,29 +393,35 @@ public class BrowserLocaleManager implem
     }
 
     /**
      * Updates the Java locale and the Android configuration.
      *
      * Returns the persisted locale if it differed.
      *
      * Does not notify Gecko.
+     *
+     * @param localeCode a locale string in Java format: "en_US".
+     * @return if it differed, a locale string in Java format: "en_US".
      */
     private String updateLocale(Context context, String localeCode) {
         // Fast path.
         final Locale defaultLocale = Locale.getDefault();
         if (defaultLocale.toString().equals(localeCode)) {
             return null;
         }
 
         final Locale locale = parseLocaleCode(localeCode);
 
         return updateLocale(context, locale);
     }
 
+    /**
+     * @return the Java locale string: e.g., "en_US".
+     */
     private String updateLocale(Context context, final Locale locale) {
         // Fast path.
         if (Locale.getDefault().equals(locale)) {
             return null;
         }
 
         Locale.setDefault(locale);
         currentLocale = locale;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1208,16 +1208,19 @@ public abstract class GeckoApp
             Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
         }
 
         // Did the OS locale change while we were backgrounded? If so,
         // we need to die so that Gecko will re-init add-ons that touch
         // the UI.
         // This is using a sledgehammer to crack a nut, but it'll do for
         // now.
+        // Our OS locale pref will be detected as invalid after the
+        // restart, and will be propagated to Gecko accordingly, so there's
+        // no need to touch that here.
         if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
             Log.i(LOGTAG, "System locale changed. Restarting.");
             doRestart();
             GeckoAppShell.systemExit();
             return;
         }
 
         if (GeckoThread.isCreated()) {
@@ -1319,38 +1322,46 @@ public abstract class GeckoApp
                 editor.apply();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
                 final EventDispatcher dispatcher = EventDispatcher.getInstance();
 
-                final String osLocale = Locale.getDefault().toString();
-                String appLocale = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
-                Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
-
-                if (appLocale == null) {
-                    appLocale = osLocale;
+                // This is the locale prior to fixing it up.
+                final Locale osLocale = Locale.getDefault();
+
+                // Both of these are Java-format locale strings: "en_US", not "en-US".
+                final String osLocaleString = osLocale.toString();
+                String appLocaleString = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
+                Log.d(LOGTAG, "OS locale is " + osLocaleString + ", app locale is " + appLocaleString);
+
+                if (appLocaleString == null) {
+                    appLocaleString = osLocaleString;
                 }
 
                 mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
                                                                      profilePath,
                                                                      dispatcher,
-                                                                     osLocale,
-                                                                     appLocale,
+                                                                     osLocaleString,
+                                                                     appLocaleString,
                                                                      previousSession);
 
-                final String uiLocale = appLocale;
+                final String uiLocale = appLocaleString;
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
                     public void run() {
                         GeckoApp.this.onLocaleReady(uiLocale);
                     }
                 });
+
+                // We use per-profile prefs here, because we're tracking against
+                // a Gecko pref. The same applies to the locale switcher!
+                BrowserLocaleManager.storeAndNotifyOSLocale(GeckoSharedPrefs.forProfile(GeckoApp.this), osLocale);
             }
         });
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
         IntentHelper.init(this);
     }
 
     /**
@@ -1848,38 +1859,27 @@ public abstract class GeckoApp
             // Check if launched from data reporting notification.
             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
             // Copy extras.
             settingsIntent.putExtras(intent);
             startActivity(settingsIntent);
         }
     }
 
-    /*
+    /**
      * Handles getting a URI from an intent in a way that is backwards-
      * compatible with our previous implementations.
      */
     protected String getURIFromIntent(Intent intent) {
         final String action = intent.getAction();
         if (ACTION_ALERT_CALLBACK.equals(action) || NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
             return null;
         }
 
-        String uri = intent.getDataString();
-        if (uri != null) {
-            return uri;
-        }
-
-        if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
-            uri = StringUtils.getStringExtra(intent, "args");
-            if (uri != null && uri.startsWith("--url=")) {
-                uri.replace("--url=", "");
-            }
-        }
-        return uri;
+        return intent.getDataString();
     }
 
     protected int getOrientation() {
         return GeckoScreenOrientation.getInstance().getAndroidOrientation();
     }
 
     @Override
     public void onResume()
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -196,20 +196,16 @@ public class GLController {
         // is blocked on the gecko sync event in updateCompositor() above
         mCompositorCreated = true;
     }
 
     public boolean isServerSurfaceValid() {
         return mServerSurfaceValid;
     }
 
-    public boolean isCompositorCreated() {
-        return mCompositorCreated;
-    }
-
     private void initEGL() {
         if (mEGL != null) {
             return;
         }
 
         // This join() should not be necessary, but makes this code a bit easier to think about.
         // The EGLPreloadingThread should long be done by now, and even if it's not,
         // it shouldn't be a problem to be initalizing EGL from two different threads.
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -471,23 +471,18 @@ public class LayerView extends FrameLayo
      * Gecko is also sent the new window size, and this will likely cause an
      * extra draw a few frames later, after it's re-rendered and caught up.
      *
      * In the case that there is no valid GL surface (for example, when
      * resuming, or when coming back from the awesomescreen), or we're using a
      * TextureView instead of a SurfaceView, the first phase is skipped.
      */
     private void onSizeChanged(int width, int height) {
-        if (!mGLController.isCompositorCreated()) {
-            return;
-        }
-
-        surfaceChanged(width, height);
-
-        if (mSurfaceView == null) {
+        if (!mGLController.isServerSurfaceValid() || mSurfaceView == null) {
+            surfaceChanged(width, height);
             return;
         }
 
         if (mListener != null) {
             mListener.sizeChanged(width, height);
         }
 
         if (mOverscroll != null) {
--- a/mobile/android/base/tests/BaseTest.java
+++ b/mobile/android/base/tests/BaseTest.java
@@ -77,16 +77,26 @@ abstract class BaseTest extends BaseRobo
     protected String mRawBaseUrl;
     protected String mProfile;
     public Device mDevice;
     protected DatabaseHelper mDatabaseHelper;
     protected int mScreenMidWidth;
     protected int mScreenMidHeight;
     private final HashSet<Integer> mKnownTabIDs = new HashSet<Integer>();
 
+    protected void blockForDelayedStartup() {
+        try {
+            Actions.EventExpecter delayedStartupExpector = mActions.expectGeckoEvent("Gecko:DelayedStartup");
+            delayedStartupExpector.blockForEvent(GECKO_READY_WAIT_MS, true);
+            delayedStartupExpector.unregisterListener();
+        } catch (Exception e) {
+            mAsserter.dumpLog("Exception in blockForDelayedStartup", e);
+        }
+    }
+
     protected void blockForGeckoReady() {
         try {
             Actions.EventExpecter geckoReadyExpector = mActions.expectGeckoEvent("Gecko:Ready");
             if (!GeckoThread.checkLaunchState(LaunchState.GeckoRunning)) {
                 geckoReadyExpector.blockForEvent(GECKO_READY_WAIT_MS, true);
             }
             geckoReadyExpector.unregisterListener();
         } catch (Exception e) {
@@ -122,24 +132,27 @@ abstract class BaseTest extends BaseRobo
         mDevice = new Device();
         mDatabaseHelper = new DatabaseHelper(mActivity, mAsserter);
 
         // Ensure Robocop tests have access to network, and are run with Display powered on.
         throwIfHttpGetFails();
         throwIfScreenNotOn();
     }
 
-    protected void initializeProfile() {
-        final GeckoProfile profile;
+    protected GeckoProfile getTestProfile() {
         if (mProfile.startsWith("/")) {
-            profile = GeckoProfile.get(getActivity(), "default", mProfile);
-        } else {
-            profile = GeckoProfile.get(getActivity(), mProfile);
+            return GeckoProfile.get(getActivity(), "default", mProfile);
         }
 
+        return GeckoProfile.get(getActivity(), mProfile);
+    }
+
+    protected void initializeProfile() {
+        final GeckoProfile profile = getTestProfile();
+
         // In Robocop tests, we typically don't get initialized correctly, because
         // GeckoProfile doesn't create the profile directory.
         profile.enqueueInitialization(profile.getDir());
     }
 
     @Override
     protected void runTest() throws Throwable {
         try {
--- a/mobile/android/base/tests/JavascriptTest.java
+++ b/mobile/android/base/tests/JavascriptTest.java
@@ -16,21 +16,24 @@ public class JavascriptTest extends Base
     public JavascriptTest(String javascriptUrl) {
         super();
         this.javascriptUrl = javascriptUrl;
     }
 
     public void testJavascript() throws Exception {
         blockForGeckoReady();
 
+        doTestJavascript();
+    }
+
+    protected void doTestJavascript() throws Exception {
         // We want to be waiting for Robocop messages before the page is loaded
         // because the test harness runs each test in the suite (and possibly
         // completes testing) before the page load event is fired.
-        final Actions.EventExpecter expecter =
-            mActions.expectGeckoEvent(EVENT_TYPE);
+        final Actions.EventExpecter expecter = mActions.expectGeckoEvent(EVENT_TYPE);
         mAsserter.dumpLog("Registered listener for " + EVENT_TYPE);
 
         final String url = getAbsoluteUrl(StringHelper.getHarnessUrlForJavascript(javascriptUrl));
         mAsserter.dumpLog("Loading JavaScript test from " + url);
         loadUrl(url);
 
         final JavascriptMessageParser testMessageParser =
                 new JavascriptMessageParser(mAsserter, false);
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -99,16 +99,17 @@ skip-if = android_version == "10"
 [testAccounts]
 [testAndroidLog]
 [testBrowserDiscovery]
 [testDebuggerServer]
 [testDeviceSearchEngine]
 [testJNI]
 # [testMozPay] # see bug 945675
 [testOrderedBroadcast]
+[testOSLocale]
 [testResourceSubstitutions]
 [testRestrictedProfiles]
 [testSharedPreferences]
 [testSimpleDiscovery]
 [testUITelemetry]
 [testVideoDiscovery]
 
 # Used for Talos, please don't use in mochitest
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testOSLocale.java
@@ -0,0 +1,134 @@
+/* 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/. */
+
+package org.mozilla.gecko.tests;
+
+import java.util.Locale;
+
+import org.mozilla.gecko.BrowserLocaleManager;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.PrefsHelper;
+
+import android.content.SharedPreferences;
+
+
+public class testOSLocale extends BaseTest {
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Clear per-profile SharedPreferences as a workaround for Bug 1069687.
+        // We're trying to exercise logic that only applies on first onCreate!
+        // We can't rely on this occurring prior to the first broadcast, though,
+        // so see the main test method for more logic.
+        final String profileName = getTestProfile().getName();
+        mAsserter.info("Setup", "Clearing pref in " + profileName + ".");
+        GeckoSharedPrefs.forProfileName(getActivity(), profileName)
+                        .edit()
+                        .remove("osLocale")
+                        .apply();
+    }
+
+    public static class PrefState extends PrefsHelper.PrefHandlerBase {
+        private static final String PREF_LOCALE_OS = "intl.locale.os";
+        private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
+
+        private static final String[] TO_FETCH = {PREF_LOCALE_OS, PREF_ACCEPT_LANG};
+
+        public volatile String osLocale;
+        public volatile String acceptLanguages;
+
+        private final Object waiter = new Object();
+
+        public void fetch() throws InterruptedException {
+            synchronized (waiter) {
+                PrefsHelper.getPrefs(TO_FETCH, this);
+                waiter.wait(MAX_WAIT_MS);
+            }
+        }
+
+        @Override
+        public void prefValue(String pref, String value) {
+            switch (pref) {
+            case PREF_LOCALE_OS:
+                osLocale = value;
+                return;
+            case PREF_ACCEPT_LANG:
+                acceptLanguages = value;
+                return;
+            }
+        }
+
+        @Override
+        public void finish() {
+            synchronized (waiter) {
+                waiter.notify();
+            }
+        }
+    }
+
+    public void testOSLocale() throws Exception {
+        blockForDelayedStartup();
+
+        final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getActivity());
+        final PrefState state = new PrefState();
+
+        state.fetch();
+
+        // We don't know at this point whether we were run against a dirty profile or not.
+        //
+        // If we cleared the pref above prior to BrowserApp's delayed init, or our Gecko
+        // profile has been used before, then we're already going to be set up for en-US.
+        //
+        // If we cleared the pref after the initial broadcast, and our Android-side profile
+        // has been used before but the Gecko profile is clean, then the Gecko prefs won't
+        // have been set.
+        //
+        // Instead, we always send a new locale code, and see what we get.
+        final Locale fr = BrowserLocaleManager.parseLocaleCode("fr");
+        BrowserLocaleManager.storeAndNotifyOSLocale(prefs, fr);
+
+        state.fetch();
+
+        mAsserter.is(state.osLocale, "fr", "We're in fr.");
+
+        // Now we can see what the expected Accept-Languages header should be.
+        // The OS locale is 'fr', so we have our app locale (en-US),
+        // the OS locale (fr), then any remaining fallbacks from intl.properties.
+        mAsserter.is(state.acceptLanguages, "en-us,fr,en", "We have the default en-US+fr Accept-Languages.");
+
+        // Now set the app locale to be es-ES.
+        BrowserLocaleManager.getInstance().setSelectedLocale(getActivity(), "es-ES");
+
+        state.fetch();
+
+        mAsserter.is(state.osLocale, "fr", "We're still in fr.");
+
+        // The correct set here depends on whether the
+        // browser was built with multiple locales or not.
+        // This is exasperating, but hey.
+        final boolean isMultiLocaleBuild = false;
+
+        // This never changes.
+        final String SELECTED_LOCALES = "es-es,fr,";
+
+
+        // Expected, from es-ES's intl.properties:
+        final String EXPECTED = SELECTED_LOCALES +
+                                (isMultiLocaleBuild ? "es,en-us,en" :  // Expected, from es-ES's intl.properties.
+                                                      "en-us,en");     // Expected, from en-US (the default).
+
+        mAsserter.is(state.acceptLanguages, EXPECTED, "We have the right es-ES+fr Accept-Languages for this build.");
+
+        // And back to en-US.
+        final Locale en_US = BrowserLocaleManager.parseLocaleCode("en-US");
+        BrowserLocaleManager.storeAndNotifyOSLocale(prefs, en_US);
+        BrowserLocaleManager.getInstance().resetToSystemLocale(getActivity());
+
+        state.fetch();
+
+        mAsserter.is(state.osLocale, "en-US", "We're in en-US.");
+        mAsserter.is(state.acceptLanguages, "en-us,en", "We have the default processed en-US Accept-Languages.");
+    }
+}
\ No newline at end of file
--- a/mobile/android/chrome/content/aboutFeedback.js
+++ b/mobile/android/chrome/content/aboutFeedback.js
@@ -62,17 +62,19 @@ function init() {
 
   document.getElementById("open-play-store").addEventListener("click", openPlayStore, false);
   document.forms[0].addEventListener("submit", sendFeedback, false);
   for (let anchor of document.querySelectorAll(".no-thanks")) {
     anchor.addEventListener("click", evt => window.close(), false);
   }
 
   let sumoLink = Services.urlFormatter.formatURLPref("app.support.baseURL");
-  document.getElementById("sumo-link").href = sumoLink;
+  document.getElementById("help-section").addEventListener("click", function() {
+    window.open(sumoLink, "_blank");
+  }, false);
 
   window.addEventListener("popstate", function (aEvent) {
 	updateActiveSection(aEvent.state ? aEvent.state.section : "intro")
   }, false);
 
   // Fill "Last visited site" input with most recent history entry URL.
   Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
     document.getElementById("last-url").value = aData;
--- a/mobile/android/chrome/content/aboutFeedback.xhtml
+++ b/mobile/android/chrome/content/aboutFeedback.xhtml
@@ -77,14 +77,14 @@
       <div class="message">&sad.thanksMessageTop;</div>
       <div class="message">&sad.thanksMessageBottom;</div>
     </div>
   </section>
 
   <footer>
     <div id="help-section">
       <img id="sumo-icon" />
-      <span>&support.pre3;<a id="sumo-link">&support.link2;</a>&support.post3;</span>
+      <span>&support.pre3;<span class="link">&support.link2;</span>&support.post3;</span>
     </div>
   </footer>
   <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutFeedback.js"></script>
 </body>
 </html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -337,16 +337,17 @@ var BrowserApp = {
       } catch(ex) { console.log(ex); }
     }, false);
 
     BrowserEventHandler.init();
     ViewportHandler.init();
 
     Services.androidBridge.browserApp = this;
 
+    Services.obs.addObserver(this, "Locale:OS", false);
     Services.obs.addObserver(this, "Locale:Changed", false);
     Services.obs.addObserver(this, "Tab:Load", false);
     Services.obs.addObserver(this, "Tab:Selected", false);
     Services.obs.addObserver(this, "Tab:Closed", false);
     Services.obs.addObserver(this, "Session:Back", false);
     Services.obs.addObserver(this, "Session:ShowHistory", false);
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
@@ -1753,16 +1754,44 @@ var BrowserApp = {
       case "Webapps:Load":
         this._loadWebapp(JSON.parse(aData));
         break;
 
       case "Webapps:AutoUninstall":
         WebappManager.autoUninstall(JSON.parse(aData));
         break;
 
+      case "Locale:OS":
+        // We know the system locale. We use this for generating Accept-Language headers.
+        console.log("Locale:OS: " + aData);
+        let currentOSLocale;
+        try {
+          currentOSLocale = Services.prefs.getCharPref("intl.locale.os");
+        } catch (e) {
+        }
+        if (currentOSLocale == aData) {
+          break;
+        }
+
+        console.log("New OS locale.");
+
+        // Ensure that this choice is immediately persisted, because
+        // Gecko won't be told again if it forgets.
+        Services.prefs.setCharPref("intl.locale.os", aData);
+        Services.prefs.savePrefFile(null);
+
+        let appLocale;
+        try {
+          appLocale = Services.prefs.getCharPref("general.useragent.locale");
+        } catch (e) {
+        }
+
+        this.computeAcceptLanguages(aData, appLocale);
+        break;
+
       case "Locale:Changed":
         if (aData) {
           // The value provided to Locale:Changed should be a BCP47 language tag
           // understood by Gecko -- for example, "es-ES" or "de".
           console.log("Locale:Changed: " + aData);
           Services.prefs.setCharPref("general.useragent.locale", aData);
         } else {
           // Resetting.
@@ -1774,25 +1803,92 @@ var BrowserApp = {
 
         // Ensure that this choice is immediately persisted, because
         // Gecko won't be told again if it forgets.
         Services.prefs.savePrefFile(null);
 
         // Blow away the string cache so that future lookups get the
         // correct locale.
         Services.strings.flushBundles();
+
+        // Make sure we use the right Accept-Language header.
+        let osLocale;
+        try {
+          // This should never not be set at this point, but better safe than sorry.
+          osLocale = Services.prefs.getCharPref("intl.locale.os");
+        } catch (e) {
+        }
+
+        this.computeAcceptLanguages(osLocale, aData);
         break;
 
       default:
         dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
         break;
 
     }
   },
 
+  /**
+   * Set intl.accept_languages accordingly.
+   *
+   * After Bug 881510 this will also accept a real Accept-Language choice as
+   * input; all Accept-Language logic lives here.
+   *
+   * osLocale should never be null, but this method is safe regardless.
+   * appLocale may explicitly be null.
+   */
+  computeAcceptLanguages(osLocale, appLocale) {
+    let defaultBranch = Services.prefs.getDefaultBranch(null);
+    let defaultAccept = defaultBranch.getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString).data;
+    console.log("Default intl.accept_languages = " + defaultAccept);
+
+    // A guard for potential breakage. Bug 438031.
+    // This should not be necessary, because we're reading from the default branch,
+    // but better safe than sorry.
+    if (defaultAccept && defaultAccept.startsWith("chrome://")) {
+      defaultAccept = null;
+    } else {
+      // Ensure lowercase everywhere so we can compare.
+      defaultAccept = defaultAccept.toLowerCase();
+    }
+
+    if (appLocale) {
+      appLocale = appLocale.toLowerCase();
+    }
+
+    if (osLocale) {
+      osLocale = osLocale.toLowerCase();
+    }
+
+    // Eliminate values if they're present in the default.
+    let chosen;
+    if (defaultAccept) {
+      // intl.accept_languages is a comma-separated list, with no q-value params. Those
+      // are added when the header is generated.
+      chosen = defaultAccept.split(",")
+                            .map(String.trim)
+                            .filter((x) => (x != appLocale && x != osLocale));
+    } else {
+      chosen = [];
+    }
+
+    if (osLocale) {
+      chosen.unshift(osLocale);
+    }
+
+    if (appLocale && appLocale != osLocale) {
+      chosen.unshift(appLocale);
+    }
+
+    let result = chosen.join(",");
+    console.log("Setting intl.accept_languages to " + result);
+    Services.prefs.setCharPref("intl.accept_languages", result);
+  },
+
   get defaultBrowserWidth() {
     delete this.defaultBrowserWidth;
     let width = Services.prefs.getIntPref("browser.viewport.desktopWidth");
     return this.defaultBrowserWidth = width;
   },
 
   get layersTileWidth() {
     delete this.layersTileWidth;
--- a/mobile/android/themes/core/aboutFeedback.css
+++ b/mobile/android/themes/core/aboutFeedback.css
@@ -185,18 +185,19 @@ footer {
   font-size: 16px;
   width: 100%;
   background-color: #0092DB;
   border-radius: 4px;
   border-width: 0;
   color: #fff;
 }
 
-#sumo-link {
+.link {
   color: #222;
+  text-decoration: underline;
 }
 
 @media screen and (max-height: 400px) {
   body {
     padding-top: 40px;
   }
 
   .bottom-links {
--- a/toolkit/components/passwordmgr/test/test_input_events.html
+++ b/toolkit/components/passwordmgr/test/test_input_events.html
@@ -12,62 +12,82 @@ Login Manager test: input events should 
 <script>
 commonInit();
 SimpleTest.waitForExplicitFinish();
 
 /** Test for Login Manager: form fill, should get input events. **/
 
 var usernameInputFired = false;
 var passwordInputFired = false;
+var usernameChangeFired = false;
+var passwordChangeFired = false;
 var onloadFired = false;
 
 function onNewEvent(e) {
   info("Got " + e.type + " event.");
   if (e.type == "load") {
     onloadFired = true;
-  } else if (e.target.name == "uname") {
-    ise(e.target.value, "testuser", "Should get 'testuser' as username");
-    ok(!usernameInputFired, "Should not have gotten an input event for the username field yet.");
-    usernameInputFired = true;
-  } else if (e.target.name == "pword") {
-    ise(e.target.value, "testpass", "Should get 'testpass' as password");
-    ok(!passwordInputFired, "Should not have gotten an input event for the password field yet.");
-    passwordInputFired = true;
+  } else if (e.type == "input") {
+    if (e.target.name == "uname") {
+      ise(e.target.value, "testuser", "Should get 'testuser' as username");
+      ok(!usernameInputFired, "Should not have gotten an input event for the username field yet.");
+      usernameInputFired = true;
+    } else if (e.target.name == "pword") {
+      ise(e.target.value, "testpass", "Should get 'testpass' as password");
+      ok(!passwordInputFired, "Should not have gotten an input event for the password field yet.");
+      passwordInputFired = true;
+    }
+  } else if (e.type == "change") {
+    if (e.target.name == "uname") {
+      ise(e.target.value, "testuser", "Should get 'testuser' as username");
+      ok(usernameInputFired, "Should get input event before change event for username field.");
+      ok(!usernameChangeFired, "Should not have gotten a change event for the username field yet.");
+      usernameChangeFired = true;
+    } else if (e.target.name == "pword") {
+      ise(e.target.value, "testpass", "Should get 'testpass' as password");
+      ok(passwordInputFired, "Should get input event before change event for password field.");
+      ok(!passwordChangeFired, "Should not have gotten a change event for the password field yet.");
+      passwordChangeFired = true;
+    }
   }
-  if (onloadFired && usernameInputFired && passwordInputFired) {
+  if (onloadFired && usernameInputFired && passwordInputFired && usernameChangeFired && passwordChangeFired) {
     ok(true, "All events fired as expected, we're done.");
     SimpleTest.finish();
   }
 }
 
 SimpleTest.registerCleanupFunction(function cleanup() {
   clearTimeout(timeout);
   $_(1, "uname").removeAttribute("oninput");
   $_(1, "pword").removeAttribute("oninput");
+  $_(1, "uname").removeAttribute("onchange");
+  $_(1, "pword").removeAttribute("onchange");
   document.body.removeAttribute("onload");
 });
 
 var timeout = setTimeout(function() {
   ok(usernameInputFired, "Username input event should have fired by now.");
   ok(passwordInputFired, "Password input event should have fired by now.");
+  ok(usernameChangeFired, "Username change event should have fired by now.");
+  ok(passwordChangeFired, "Password change event should have fired by now.");
   ok(onloadFired, "Window load event should have fired by now.");
   ok(false, "Not all events fired yet.");
   SimpleTest.finish();
 }, 10000);
 
 </script>
 
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
-    <input  type="text"       name="uname" oninput="onNewEvent(event)">
-    <input  type="password"   name="pword" oninput="onNewEvent(event)">
+    <input  type="text"       name="uname" oninput="onNewEvent(event)" onchange="onNewEvent(event)">
+    <input  type="password"   name="pword" oninput="onNewEvent(event)" onchange="onNewEvent(event)">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
 
 </div>
 <pre id="test"></pre>
 </body>
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -203,45 +203,18 @@ this.PlacesUtils = {
   nodeAncestors: function PU_nodeAncestors(aNode) {
     let node = aNode.parent;
     while (node) {
       yield node;
       node = node.parent;
     }
   },
 
-  /**
-   * Cache array of read-only item IDs.
-   *
-   * The first time this property is called:
-   * - the cache is filled with all ids with the RO annotation
-   * - an annotation observer is added
-   * - a shutdown observer is added
-   *
-   * When the annotation observer detects annotations added or
-   * removed that are the RO annotation name, it adds/removes
-   * the ids from the cache.
-   *
-   * At shutdown, the annotation and shutdown observers are removed.
-   */
-  get _readOnly() {
-    // Add annotations observer.
-    this.annotations.addObserver(this, false);
-    this.registerShutdownFunction(function () {
-      this.annotations.removeObserver(this);
-    });
-
-    var readOnly = this.annotations.getItemsWithAnnotation(this.READ_ONLY_ANNO);
-    this.__defineGetter__("_readOnly", function() readOnly);
-    return this._readOnly;
-  },
-
   QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIAnnotationObserver
-  , Ci.nsIObserver
+    Ci.nsIObserver
   , Ci.nsITransactionListener
   ]),
 
   _shutdownFunctions: [],
   registerShutdownFunction: function PU_registerShutdownFunction(aFunc)
   {
     // If this is the first registered function, add the shutdown observer.
     if (this._shutdownFunctions.length == 0) {
@@ -270,34 +243,16 @@ this.PlacesUtils = {
         while (this._bookmarksServiceObserversQueue.length > 0) {
           let observerInfo = this._bookmarksServiceObserversQueue.shift();
           this.bookmarks.addObserver(observerInfo.observer, observerInfo.weak);
         }
         break;
     }
   },
 
-  //////////////////////////////////////////////////////////////////////////////
-  //// nsIAnnotationObserver
-
-  onItemAnnotationSet: function PU_onItemAnnotationSet(aItemId, aAnnotationName)
-  {
-    if (aAnnotationName == this.READ_ONLY_ANNO &&
-        this._readOnly.indexOf(aItemId) == -1)
-      this._readOnly.push(aItemId);
-  },
-
-  onItemAnnotationRemoved:
-  function PU_onItemAnnotationRemoved(aItemId, aAnnotationName)
-  {
-    var index = this._readOnly.indexOf(aItemId);
-    if (aAnnotationName == this.READ_ONLY_ANNO && index > -1)
-      delete this._readOnly[index];
-  },
-
   onPageAnnotationSet: function() {},
   onPageAnnotationRemoved: function() {},
 
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsITransactionListener
 
   didDo: function PU_didDo(aManager, aTransaction, aDoResult)
@@ -337,37 +292,16 @@ this.PlacesUtils = {
   willUndo: function PU_willUndo() {},
   willRedo: function PU_willRedo() {},
   willBeginBatch: function PU_willBeginBatch() {},
   willEndBatch: function PU_willEndBatch() {},
   didEndBatch: function PU_didEndBatch() {},
   willMerge: function PU_willMerge() {},
   didMerge: function PU_didMerge() {},
 
-
-  /**
-   * Determines if a node is read only (children cannot be inserted, sometimes
-   * they cannot be removed depending on the circumstance)
-   * @param   aNode
-   *          A result node
-   * @returns true if the node is readonly, false otherwise
-   */
-  nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) {
-    let itemId = aNode.itemId;
-    if (itemId != -1) {
-      return this._readOnly.indexOf(itemId) != -1;
-    }
-
-    if (this.nodeIsQuery(aNode) &&
-        asQuery(aNode).queryOptions.resultType !=
-        Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS)
-      return aNode.childrenReadOnly;
-    return false;
-  },
-
   /**
    * Determines whether or not a ResultNode is a host container.
    * @param   aNode
    *          A result node
    * @returns true if the node is a host container, false otherwise
    */
   nodeIsHost: function PU_nodeIsHost(aNode) {
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
@@ -429,27 +363,16 @@ this.PlacesUtils = {
               Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY ||
             resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
             resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY ||
             this.nodeIsDay(aNode) ||
             this.nodeIsHost(aNode));
   },
 
   /**
-   * Determines whether or not a node is a readonly folder.
-   * @param   aNode
-   *          The node to test.
-   * @returns true if the node is a readonly folder.
-  */
-  isReadonlyFolder: function(aNode) {
-    return this.nodeIsFolder(aNode) &&
-           this._readOnly.indexOf(asQuery(aNode).folderItemId) != -1;
-  },
-
-  /**
    * Gets the concrete item-id for the given node. Generally, this is just
    * node.itemId, but for folder-shortcuts that's node.folderItemId.
    */
   getConcreteItemId: function PU_getConcreteItemId(aNode) {
     if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
       return asQuery(aNode).folderItemId;
     else if (PlacesUtils.nodeIsTagQuery(aNode)) {
       // RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
@@ -1108,20 +1031,19 @@ this.PlacesUtils = {
   _serializeNodeAsJSONToOutputStream: function (aNode, aStream) {
     function addGenericProperties(aPlacesNode, aJSNode) {
       aJSNode.title = aPlacesNode.title;
       aJSNode.id = aPlacesNode.itemId;
       let guid = aPlacesNode.bookmarkGuid;
       if (guid) {
         aJSNode.itemGuid = guid;
         var parent = aPlacesNode.parent;
-        if (parent) {
+        if (parent)
           aJSNode.parent = parent.itemId;
-          aJSNode.parentReadOnly = PlacesUtils.nodeIsReadOnly(parent);
-        }
+
         var dateAdded = aPlacesNode.dateAdded;
         if (dateAdded)
           aJSNode.dateAdded = dateAdded;
         var lastModified = aPlacesNode.lastModified;
         if (lastModified)
           aJSNode.lastModified = lastModified;
 
         // XXX need a hasAnnos api
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -218,17 +218,17 @@ interface nsINavBookmarkObserver : nsISu
 };
 
 /**
  * The BookmarksService interface provides methods for managing bookmarked
  * history items.  Bookmarks consist of a set of user-customizable
  * folders.  A URI in history can be contained in one or more such folders.
  */
 
-[scriptable, uuid(A78EA368-E28E-462E-897A-26606D4DDCE6)]
+[scriptable, uuid(4C309044-B6DA-4511-AF57-E8940DB00045)]
 interface nsINavBookmarksService : nsISupports
 {
   /**
    * The item ID of the Places root.
    */
   readonly attribute long long placesRoot;
 
   /**
@@ -462,40 +462,16 @@ interface nsINavBookmarksService : nsISu
 
   /**
    * Get an item's type (bookmark, separator, folder).
    * The type is one of the TYPE_* constants defined above.
    */
   unsigned short getItemType(in long long aItemId);
 
   /**
-   * Checks whether a folder is marked as read-only.
-   * If this is set to true, UI will not allow the user to add, remove,
-   * or reorder children in this folder. The default for all folders is false.
-   * Note: This does not restrict API calls, only UI actions.
-   *
-   * @param aItemId
-   *        the item-id of the folder.
-   */
-  boolean getFolderReadonly(in long long aItemId);
-
-  /**
-   * Sets or unsets the readonly flag from a folder.
-   * If this is set to true, UI will not allow the user to add, remove,
-   * or reorder children in this folder. The default for all folders is false.
-   * Note: This does not restrict API calls, only UI actions.
-   *
-   * @param aFolder
-   *        the item-id of the folder.
-   * @param aReadOnly
-   *        the read-only state (boolean).
-   */
-  void setFolderReadonly(in long long aFolder, in boolean aReadOnly);
-
-  /**
    * Returns true if the given URI is in any bookmark folder. If you want the
    * results to be redirect-aware, use getBookmarkedURIFor()
    */
   boolean isBookmarked(in nsIURI aURI);
 
   /**
    * Used to see if the given URI is bookmarked, or any page that redirected to
    * it is bookmarked. For example, if I bookmark "mozilla.org" by manually
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -170,17 +170,17 @@ interface nsINavHistoryResultNode : nsIS
 };
 
 
 /**
  * Base class for container results. This includes all types of groupings.
  * Bookmark folders and places queries will be QueryResultNodes which extends
  * these items.
  */
-[scriptable, uuid(5bac9734-c0ff-44eb-8d19-da88462ff6da)]
+[scriptable, uuid(3E9CC95F-0D93-45F1-894F-908EEB9866D7)]
 interface nsINavHistoryContainerResultNode : nsINavHistoryResultNode
 {
 
   /**
    * Set this to allow descent into the container. When closed, attempting
    * to call getChildren or childCount will result in an error. You should
    * set this to false when you are done reading.
    *
@@ -251,36 +251,27 @@ interface nsINavHistoryContainerResultNo
    * @throws NS_ERROR_NOT_AVAILABLE if this container is closed.
    * @return a result node that matches the given details if any, null
    *         otherwise.
    */
   nsINavHistoryResultNode findNodeByDetails(in AUTF8String aURIString,
                                             in PRTime aTime,
                                             in long long aItemId,
                                             in boolean aRecursive);
-
-  /**
-   * Returns false if this node's list of children can be modified
-   * (adding or removing children, or reordering children), or true if
-   * the UI should not allow the list of children to be modified.
-   * This is false for bookmark folder nodes unless setFolderReadOnly() has
-   * been called to override it, and true for non-folder nodes.
-   */
-  readonly attribute boolean childrenReadOnly;
 };
 
 
 /**
  * Used for places queries and as a base for bookmark folders.
  *
  * Note that if you request places to *not* be expanded in the options that
  * generated this node, this item will report it has no children and never try
  * to populate itself.
  */
-[scriptable, uuid(a4144c3e-8125-46d5-a719-831bec8095f4)]
+[scriptable, uuid(91AC5E59-3F5C-4ACD-AB3B-325FC425A5A1)]
 interface nsINavHistoryQueryResultNode : nsINavHistoryContainerResultNode
 {
   /**
    * Get the queries which build this node's children.
    * Only valid for RESULT_TYPE_QUERY nodes.
    */
   void getQueries([optional] out unsigned long queryCount,
                   [retval,array,size_is(queryCount)] out nsINavHistoryQuery queries);
@@ -1113,22 +1104,19 @@ interface nsINavHistoryQueryOptions : ns
   /**
    * Set to true to exclude queries ("place:" URIs) from the query results.
    * Simple folder queries (bookmark folder symlinks) will still be included.
    * Defaults to false.
    */
   attribute boolean excludeQueries;
 
   /**
-   * Set to true to exclude read-only folders from the query results. This is
-   * designed for cases where you want to give the user the option of filing
-   * something into a list of folders. It only affects cases where the actual
-   * folder result node would appear in its parent folder and filters it out.
-   * It doesn't affect the query at all, and doesn't affect more complex
-   * queries (such as "folders with annotation X").
+   * DO NOT USE THIS API. IT'LL BE REMOVED IN BUG 1072833.
+   *
+   * Set to true to exclude live bookmarks from the query results.
    */
   attribute boolean excludeReadOnlyFolders;
 
   /**
    * When set, allows items with "place:" URIs to appear as containers,
    * with the container's contents filled in from the stored query.
    * If not set, these will appear as normal items. Doesn't do anything if
    * excludeQueries is set. Defaults to false.
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -542,17 +542,16 @@ function Livemark(aLivemarkInfo)
     this.lastModified = aLivemarkInfo.lastModified;
   }
   else {
     // Create a new livemark.
     this.id = PlacesUtils.bookmarks.createFolder(aLivemarkInfo.parentId,
                                                  aLivemarkInfo.title,
                                                  aLivemarkInfo.index,
                                                  aLivemarkInfo.guid);
-    PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
     this.writeFeedURI(aLivemarkInfo.feedURI);
     if (aLivemarkInfo.siteURI) {
       this.writeSiteURI(aLivemarkInfo.siteURI);
     }
     // Last modified time must be the last change.
     if (aLivemarkInfo.lastModified) {
       this.lastModified = aLivemarkInfo.lastModified;
       PlacesUtils.bookmarks.setItemLastModified(this.id, this.lastModified);
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -55,17 +55,17 @@ const int32_t nsNavBookmarks::kGetChildr
 const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 18;
 
 using namespace mozilla::places;
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
 
 #define BOOKMARKS_ANNO_PREFIX "bookmarks/"
 #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
-#define READ_ONLY_ANNO NS_LITERAL_CSTRING("placesInternal/READ_ONLY")
+#define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
 
 
 namespace {
 
 struct keywordSearchData
 {
   int64_t itemId;
   nsString keyword;
@@ -785,56 +785,28 @@ nsNavBookmarks::CreateFolder(int64_t aPa
   // will cause notifications to be sent to bookmark observers.
   int32_t localIndex = aIndex;
   nsresult rv = CreateContainerWithID(-1, aParent, aName, true, &localIndex,
                                       aGUID, aNewFolder);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsNavBookmarks::GetFolderReadonly(int64_t aFolder, bool* aResult)
+bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
 {
-  NS_ENSURE_ARG_MIN(aFolder, 1);
-  NS_ENSURE_ARG_POINTER(aResult);
-
-  nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
-  NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
-  nsresult rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, aResult);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
-}
-
-
-NS_IMETHODIMP
-nsNavBookmarks::SetFolderReadonly(int64_t aFolder, bool aReadOnly)
-{
-  NS_ENSURE_ARG_MIN(aFolder, 1);
-
   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
-  NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
-  nsresult rv;
-  if (aReadOnly) {
-    rv = annosvc->SetItemAnnotationInt32(aFolder, READ_ONLY_ANNO, 1, 0,
-                                         nsAnnotationService::EXPIRE_NEVER);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else {
-    bool hasAnno;
-    rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, &hasAnno);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (hasAnno) {
-      rv = annosvc->RemoveItemAnnotation(aFolder, READ_ONLY_ANNO);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-  return NS_OK;
+  NS_ENSURE_TRUE(annosvc, false);
+  bool isLivemark;
+  nsresult rv = annosvc->ItemHasAnnotation(aFolderId,
+                                           FEED_URI_ANNO,
+                                           &isLivemark);
+  NS_ENSURE_SUCCESS(rv, false);
+  return isLivemark;
 }
 
-
 nsresult
 nsNavBookmarks::CreateContainerWithID(int64_t aItemId,
                                       int64_t aParent,
                                       const nsACString& aTitle,
                                       bool aIsBookmarkFolder,
                                       int32_t* aIndex,
                                       const nsACString& aGUID,
                                       int64_t* aNewFolder)
@@ -1861,21 +1833,20 @@ nsNavBookmarks::ProcessFolderNodeRow(
          aOptions->ExcludeQueries()) ||
         (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
          nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT &&
          aOptions->ExcludeItems())) {
       return NS_OK;
     }
   }
   else if (itemType == TYPE_FOLDER) {
+    // ExcludeReadOnlyFolders currently means "ExcludeLivemarks" (to be fixed in
+    // bug 1072833)
     if (aOptions->ExcludeReadOnlyFolders()) {
-      // If the folder is read-only, skip it.
-      bool readOnly = false;
-      GetFolderReadonly(id, &readOnly);
-      if (readOnly)
+      if (IsLivemark(id))
         return NS_OK;
     }
 
     nsAutoCString title;
     rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
     NS_ENSURE_SUCCESS(rv, rv);
 
     node = new nsNavHistoryFolderResultNode(title, aOptions, id);
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -230,16 +230,25 @@ public:
                                 nsTArray<int64_t>& aDescendantFoldersArray);
 
 private:
   static nsNavBookmarks* gBookmarksService;
 
   ~nsNavBookmarks();
 
   /**
+   * Checks whether or not aFolderId points to a live bookmark.
+   *
+   * @param aFolderId
+   *        the item-id of the folder to check.
+   * @return true if aFolderId points to live bookmarks, false otherwise.
+   */
+  bool IsLivemark(int64_t aFolderId);
+
+  /**
    * Locates the root items in the bookmarks folder hierarchy assigning folder
    * ids to the root properties that are exposed through the service interface.
    */
   nsresult ReadRoots();
 
   nsresult AdjustIndices(int64_t aFolder,
                          int32_t aStartIndex,
                          int32_t aEndIndex,
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -317,38 +317,36 @@ NS_IMPL_RELEASE_INHERITED(nsNavHistoryCo
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode)
   NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
   NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
 NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
 
 nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
     const nsACString& aURI, const nsACString& aTitle,
-    const nsACString& aIconURI, uint32_t aContainerType, bool aReadOnly,
+    const nsACString& aIconURI, uint32_t aContainerType,
     nsNavHistoryQueryOptions* aOptions) :
   nsNavHistoryResultNode(aURI, aTitle, 0, 0, aIconURI),
   mResult(nullptr),
   mContainerType(aContainerType),
   mExpanded(false),
-  mChildrenReadOnly(aReadOnly),
   mOptions(aOptions),
   mAsyncCanceledState(NOT_CANCELED)
 {
 }
 
 nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
     const nsACString& aURI, const nsACString& aTitle,
     PRTime aTime,
-    const nsACString& aIconURI, uint32_t aContainerType, bool aReadOnly,
+    const nsACString& aIconURI, uint32_t aContainerType,
     nsNavHistoryQueryOptions* aOptions) :
   nsNavHistoryResultNode(aURI, aTitle, 0, aTime, aIconURI),
   mResult(nullptr),
   mContainerType(aContainerType),
   mExpanded(false),
-  mChildrenReadOnly(aReadOnly),
   mOptions(aOptions),
   mAsyncCanceledState(NOT_CANCELED)
 {
 }
 
 
 nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
 {
@@ -1712,26 +1710,16 @@ nsNavHistoryContainerResultNode::FindNod
       }
     }
   }
   NS_IF_ADDREF(*_retval);
   return NS_OK;
 }
 
 /**
- * @note Overridden for folders to query the bookmarks service directly.
- */
-NS_IMETHODIMP
-nsNavHistoryContainerResultNode::GetChildrenReadOnly(bool *aChildrenReadOnly)
-{
-  *aChildrenReadOnly = mChildrenReadOnly;
-  return NS_OK;
-}
-
-/**
  * HOW QUERY UPDATING WORKS
  *
  * Queries are different than bookmark folders in that we can not always do
  * dynamic updates (easily) and updates are more expensive.  Therefore, we do
  * NOT query if we are not open and want to see if we have any children (for
  * drawing a twisty) and always assume we will.
  *
  * When the container is opened, we execute the query and register the
@@ -1748,31 +1736,31 @@ NS_IMPL_ISUPPORTS_INHERITED(nsNavHistory
                             nsNavHistoryContainerResultNode,
                             nsINavHistoryQueryResultNode)
 
 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
     const nsACString& aTitle, const nsACString& aIconURI,
     const nsACString& aQueryURI) :
   nsNavHistoryContainerResultNode(aQueryURI, aTitle, aIconURI,
                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
-                                  true, nullptr),
+                                  nullptr),
   mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
   mHasSearchTerms(false),
   mContentsValid(false),
   mBatchChanges(0)
 {
 }
 
 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
     const nsACString& aTitle, const nsACString& aIconURI,
     const nsCOMArray<nsNavHistoryQuery>& aQueries,
     nsNavHistoryQueryOptions* aOptions) :
   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aIconURI,
                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
-                                  true, aOptions),
+                                  aOptions),
   mQueries(aQueries),
   mContentsValid(false),
   mBatchChanges(0),
   mTransitions(mQueries[0]->Transitions())
 {
   NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
 
   nsNavHistory* history = nsNavHistory::GetHistoryService();
@@ -1795,17 +1783,17 @@ nsNavHistoryQueryResultNode::nsNavHistor
 
 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
     const nsACString& aTitle, const nsACString& aIconURI,
     PRTime aTime,
     const nsCOMArray<nsNavHistoryQuery>& aQueries,
     nsNavHistoryQueryOptions* aOptions) :
   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime, aIconURI,
                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
-                                  true, aOptions),
+                                  aOptions),
   mQueries(aQueries),
   mContentsValid(false),
   mBatchChanges(0),
   mTransitions(mQueries[0]->Transitions())
 {
   NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
 
   nsNavHistory* history = nsNavHistory::GetHistoryService();
@@ -2983,17 +2971,17 @@ NS_IMPL_ISUPPORTS_INHERITED(nsNavHistory
                             nsNavHistoryContainerResultNode,
                             nsINavHistoryQueryResultNode)
 
 nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
     const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
     int64_t aFolderId) :
   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, EmptyCString(),
                                   nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
-                                  false, aOptions),
+                                  aOptions),
   mContentsValid(false),
   mQueryItemId(-1),
   mIsRegisteredFolderObserver(false)
 {
   mItemId = aFolderId;
 }
 
 nsNavHistoryFolderResultNode::~nsNavHistoryFolderResultNode()
@@ -3085,35 +3073,16 @@ nsNavHistoryFolderResultNode::GetHasChil
  */
 NS_IMETHODIMP
 nsNavHistoryFolderResultNode::GetItemId(int64_t* aItemId)
 {
   *aItemId = mQueryItemId == -1 ? mItemId : mQueryItemId;
   return NS_OK;
 }
 
-/**
- * Here, we override the getter and ignore the value stored in our object.
- * The bookmarks service can tell us whether this folder should be read-only
- * or not.
- *
- * It would be nice to put this code in the folder constructor, but the
- * database was complaining.  I believe it is because most folders are created
- * while enumerating the bookmarks table and having a statement open, and doing
- * another statement might make it unhappy in some cases.
- */
-NS_IMETHODIMP
-nsNavHistoryFolderResultNode::GetChildrenReadOnly(bool *aChildrenReadOnly)
-{
-  nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
-  NS_ENSURE_TRUE(bookmarks, NS_ERROR_UNEXPECTED);
-  return bookmarks->GetFolderReadonly(mItemId, aChildrenReadOnly);
-}
-
-
 NS_IMETHODIMP
 nsNavHistoryFolderResultNode::GetFolderItemId(int64_t* aItemId)
 {
   *aItemId = mItemId;
   return NS_OK;
 }
 
 /**
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -396,17 +396,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
 //
 //    This is the base class for all nodes that can have children. It is
 //    overridden for nodes that are dynamically populated such as queries and
 //    folders. It is used directly for simple containers such as host groups
 //    in history views.
 
 // derived classes each provide their own implementation of has children and
 // forward the rest to us using this macro
-#define NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN_AND_READONLY \
+#define NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN \
   NS_IMETHOD GetState(uint16_t* _state) \
     { return nsNavHistoryContainerResultNode::GetState(_state); } \
   NS_IMETHOD GetContainerOpen(bool *aContainerOpen) \
     { return nsNavHistoryContainerResultNode::GetContainerOpen(aContainerOpen); } \
   NS_IMETHOD SetContainerOpen(bool aContainerOpen) \
     { return nsNavHistoryContainerResultNode::SetContainerOpen(aContainerOpen); } \
   NS_IMETHOD GetChildCount(uint32_t *aChildCount) \
     { return nsNavHistoryContainerResultNode::GetChildCount(aChildCount); } \
@@ -425,22 +425,22 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
 
 class nsNavHistoryContainerResultNode : public nsNavHistoryResultNode,
                                         public nsINavHistoryContainerResultNode
 {
 public:
   nsNavHistoryContainerResultNode(
     const nsACString& aURI, const nsACString& aTitle,
     const nsACString& aIconURI, uint32_t aContainerType,
-    bool aReadOnly, nsNavHistoryQueryOptions* aOptions);
+    nsNavHistoryQueryOptions* aOptions);
   nsNavHistoryContainerResultNode(
     const nsACString& aURI, const nsACString& aTitle,
     PRTime aTime,
     const nsACString& aIconURI, uint32_t aContainerType,
-    bool aReadOnly, nsNavHistoryQueryOptions* aOptions);
+    nsNavHistoryQueryOptions* aOptions);
 
   virtual nsresult Refresh();
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYCONTAINERRESULTNODE_IID)
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
   NS_FORWARD_COMMON_RESULTNODE_TO_BASE
@@ -474,18 +474,16 @@ public:
 
   // When there are children, this stores the open state in the tree
   // this is set to the default in the constructor.
   bool mExpanded;
 
   // Filled in by the result type generator in nsNavHistory.
   nsCOMArray<nsNavHistoryResultNode> mChildren;
 
-  bool mChildrenReadOnly;
-
   nsCOMPtr<nsNavHistoryQueryOptions> mOptions;
 
   void FillStats();
   nsresult ReverseUpdateStats(int32_t aAccessCountChange);
 
   // Sorting methods.
   typedef nsCOMArray<nsNavHistoryResultNode>::nsCOMArrayComparatorFunc SortComparator;
   virtual uint16_t GetSortType();
@@ -639,20 +637,18 @@ public:
                               const nsCOMArray<nsNavHistoryQuery>& aQueries,
                               nsNavHistoryQueryOptions* aOptions);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_COMMON_RESULTNODE_TO_BASE
   NS_IMETHOD GetType(uint32_t* type)
     { *type = nsNavHistoryResultNode::RESULT_TYPE_QUERY; return NS_OK; }
   NS_IMETHOD GetUri(nsACString& aURI); // does special lazy creation
-  NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN_AND_READONLY
+  NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN
   NS_IMETHOD GetHasChildren(bool* aHasChildren);
-  NS_IMETHOD GetChildrenReadOnly(bool *aChildrenReadOnly)
-    { return nsNavHistoryContainerResultNode::GetChildrenReadOnly(aChildrenReadOnly); }
   NS_DECL_NSINAVHISTORYQUERYRESULTNODE
 
   bool CanExpand();
   bool IsContainersQuery();
 
   virtual nsresult OpenContainer();
 
   NS_DECL_BOOKMARK_HISTORY_OBSERVER_INTERNAL
@@ -720,19 +716,18 @@ public:
     if (mQueryItemId != -1) {
       *type = nsNavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT;
     } else {
       *type = nsNavHistoryResultNode::RESULT_TYPE_FOLDER;
     }
     return NS_OK;
   }
   NS_IMETHOD GetUri(nsACString& aURI);
-  NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN_AND_READONLY
+  NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN
   NS_IMETHOD GetHasChildren(bool* aHasChildren);
-  NS_IMETHOD GetChildrenReadOnly(bool *aChildrenReadOnly);
   NS_IMETHOD GetItemId(int64_t *aItemId);
   NS_DECL_NSINAVHISTORYQUERYRESULTNODE
 
   virtual nsresult OpenContainer();
 
   virtual nsresult OpenContainerAsync();
   NS_DECL_ASYNCSTATEMENTCALLBACK
 
--- a/toolkit/components/places/tests/queries/head_queries.js
+++ b/toolkit/components/places/tests/queries/head_queries.js
@@ -147,18 +147,16 @@ function task_populateDB(aArray)
                                                         qdata.annoExpiration);
             }
           }
 
           if (qdata.isFolder) {
             let folderId = PlacesUtils.bookmarks.createFolder(qdata.parentFolder,
                                                               qdata.title,
                                                               qdata.index);
-            if (qdata.readOnly)
-              PlacesUtils.bookmarks.setFolderReadonly(folderId, true);
           }
 
           if (qdata.isLivemark) {
             PlacesUtils.livemarks.addLivemark({ title: qdata.title
                                               , parentId: qdata.parentFolder
                                               , index: qdata.index
                                               , feedURI: uri(qdata.feedURI)
                                               , siteURI: uri(qdata.uri)
@@ -241,17 +239,16 @@ function queryData(obj) {
   this.feedURI = obj.feedURI ? obj.feedURI : "";
   this.index = obj.index ? obj.index : PlacesUtils.bookmarks.DEFAULT_INDEX;
   this.isFolder = obj.isFolder ? obj.isFolder : false;
   this.contractId = obj.contractId ? obj.contractId : "";
   this.lastModified = obj.lastModified ? obj.lastModified : today;
   this.dateAdded = obj.dateAdded ? obj.dateAdded : today;
   this.keyword = obj.keyword ? obj.keyword : "";
   this.visitCount = obj.visitCount ? obj.visitCount : 0;
-  this.readOnly = obj.readOnly ? obj.readOnly : false;
   this.isSeparator = obj.hasOwnProperty("isSeparator") && obj.isSeparator;
 
   // And now, the attribute for whether or not this object should appear in the
   // resulting query
   this.isInQuery = obj.isInQuery ? obj.isInQuery : false;
 }
 
 // All attributes are set in the constructor above
--- a/toolkit/components/places/tests/queries/test_async.js
+++ b/toolkit/components/places/tests/queries/test_async.js
@@ -308,17 +308,16 @@ let DataHelper = {
           isSeparator: true,
           parentFolder: dat.parent,
           index: PlacesUtils.bookmarks.DEFAULT_INDEX,
           isInQuery: true
         };
       case "folder":
         return {
           isFolder: true,
-          readOnly: false,
           parentFolder: dat.parent,
           index: PlacesUtils.bookmarks.DEFAULT_INDEX,
           title: dat.title,
           isInQuery: true
         };
       default:
         do_throw("Unknown data type when populating DB: " + type);
       }
deleted file mode 100644
--- a/toolkit/components/places/tests/queries/test_excludeReadOnlyFolders.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-// The test data for our database, note that the ordering of the results that
-// will be returned by the query (the isInQuery: true objects) is IMPORTANT.
-// see compareArrayToResult in head_queries.js for more info.
-var testData = [
-  // Normal folder
-  { isInQuery: true, isFolder: true, title: "Folder 1",
-    parentFolder: PlacesUtils.toolbarFolderId },
-
-  // Read only folder
-  { isInQuery: false, isFolder: true, title: "Folder 2 RO",
-    parentFolder: PlacesUtils.toolbarFolderId, readOnly: true }
-];
-
-function run_test()
-{
-  run_next_test();
-}
-
-add_task(function test_excludeReadOnlyFolders()
-{
-  yield task_populateDB(testData);
-
-  var query = PlacesUtils.history.getNewQuery();
-  query.setFolders([PlacesUtils.toolbarFolderId], 1);
-
-  // Options
-  var options = PlacesUtils.history.getNewQueryOptions();
-  options.excludeQueries = true;
-  options.excludeReadOnlyFolders = true;
-
-  // Results
-  var result = PlacesUtils.history.executeQuery(query, options);
-  var root = result.root;
-  root.containerOpen = true;
-
-  displayResultSet(root);
-  // The readonly folder should not be in our result set.
-  do_check_eq(1, root.childCount);
-  do_check_eq("Folder 1", root.getChild(0).title);
-
-  root.containerOpen = false;
-});
--- a/toolkit/components/places/tests/queries/test_querySerialization.js
+++ b/toolkit/components/places/tests/queries/test_querySerialization.js
@@ -409,27 +409,16 @@ const queryOptionSwitches = [
     desc:     "nsINavHistoryQueryOptions.excludeQueries",
     matches:  simplePropertyMatches,
     runs:     [
       function (aQuery, aQueryOptions) {
         aQueryOptions.excludeQueries = true;
       }
     ]
   },
-  // excludeReadOnlyFolders
-  {
-    property: "excludeReadOnlyFolders",
-    desc:     "nsINavHistoryQueryOptions.excludeReadOnlyFolders",
-    matches:  simplePropertyMatches,
-    runs:     [
-      function (aQuery, aQueryOptions) {
-        aQueryOptions.excludeReadOnlyFolders = true;
-      }
-    ]
-  },
   // expandQueries
   {
     property: "expandQueries",
     desc:     "nsINavHistoryQueryOptions.expandQueries",
     matches:  simplePropertyMatches,
     runs:     [
       function (aQuery, aQueryOptions) {
         aQueryOptions.expandQueries = true;
--- a/toolkit/components/places/tests/queries/xpcshell.ini
+++ b/toolkit/components/places/tests/queries/xpcshell.ini
@@ -3,17 +3,16 @@ head = head_queries.js
 tail =
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_415716.js]
 [test_abstime-annotation-domain.js]
 [test_abstime-annotation-uri.js]
 [test_async.js]
 [test_containersQueries_sorting.js]
-[test_excludeReadOnlyFolders.js]
 [test_history_queries_tags_liveUpdate.js]
 [test_history_queries_titles_liveUpdate.js]
 [test_onlyBookmarked.js]
 [test_querySerialization.js]
 [test_redirects.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_results-as-tag-contents-query.js]
--- a/toolkit/devtools/apps/tests/debugger-protocol-helper.js
+++ b/toolkit/devtools/apps/tests/debugger-protocol-helper.js
@@ -146,17 +146,17 @@ addMessageListener("addFrame", function 
 });
 
 addMessageListener("cleanup", function () {
   webappActorRequest({type: "unwatchApps"}, function () {
     gClient.close();
   });
 });
 
-let AppFramesMock = {
+let FramesMock = {
   list: function () {
     return Frames;
   },
   addObserver: function () {},
   removeObserver: function () {}
 };
 
-require("devtools/server/actors/webapps").setAppFramesMock(AppFramesMock);
+require("devtools/server/actors/webapps").setFramesMock(FramesMock);
--- a/toolkit/devtools/server/actors/childtab.js
+++ b/toolkit/devtools/server/actors/childtab.js
@@ -22,49 +22,68 @@ let { TabActor } = require("devtools/ser
  * @param chromeGlobal
  *        The content script global holding |content| and |docShell| properties for a tab.
  */
 function ContentActor(connection, chromeGlobal)
 {
   this._chromeGlobal = chromeGlobal;
   TabActor.call(this, connection, chromeGlobal);
   this.traits.reconfigure = false;
+  this._sendForm = this._sendForm.bind(this);
+  this._chromeGlobal.addMessageListener("debug:form", this._sendForm);
 }
 
 ContentActor.prototype = Object.create(TabActor.prototype);
 
 ContentActor.prototype.constructor = ContentActor;
 
 Object.defineProperty(ContentActor.prototype, "docShell", {
   get: function() {
     return this._chromeGlobal.docShell;
   },
   enumerable: true,
   configurable: true
 });
 
+Object.defineProperty(ContentActor.prototype, "title", {
+  get: function() {
+    return this.window.document.title;
+  },
+  enumerable: true,
+  configurable: true
+});
+
 ContentActor.prototype.exit = function() {
+  this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
+  this._sendForm = null;
   TabActor.prototype.exit.call(this);
 };
 
-// Override grip just to rename this._tabActorPool to this._tabActorPool2
+// Override form just to rename this._tabActorPool to this._tabActorPool2
 // in order to prevent it to be cleaned on detach.
 // We have to keep tab actors alive as we keep the ContentActor
 // alive after detach and reuse it for multiple debug sessions.
-ContentActor.prototype.grip = function () {
+ContentActor.prototype.form = function () {
   let response = {
-    'actor': this.actorID,
-    'title': this.title,
-    'url': this.url
+    "actor": this.actorID,
+    "title": this.title,
+    "url": this.url
   };
 
   // Walk over tab actors added by extensions and add them to a new ActorPool.
   let actorPool = new ActorPool(this.conn);
   this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
   if (!actorPool.isEmpty()) {
     this._tabActorPool2 = actorPool;
     this.conn.addActorPool(this._tabActorPool2);
   }
 
   this._appendExtraActors(response);
   return response;
 };
 
+/**
+ * On navigation events, our URL and/or title may change, so we update our
+ * counterpart in the parent process that participates in the tab list.
+ */
+ContentActor.prototype._sendForm = function() {
+  this._chromeGlobal.sendAsyncMessage("debug:form", this.form());
+};
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -13,29 +13,29 @@ Cu.import("resource://gre/modules/FileUt
 
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let { ActorPool } = require("devtools/server/actors/common");
 let { DebuggerServer } = require("devtools/server/main");
 let Services = require("Services");
 
-let AppFramesMock = null;
+let FramesMock = null;
 
-exports.setAppFramesMock = function (mock) {
-  AppFramesMock = mock;
-}
+exports.setFramesMock = function (mock) {
+  FramesMock = mock;
+};
 
-DevToolsUtils.defineLazyGetter(this, "AppFrames", () => {
+DevToolsUtils.defineLazyGetter(this, "Frames", () => {
   // Offer a way for unit test to provide a mock
-  if (AppFramesMock) {
-    return AppFramesMock;
+  if (FramesMock) {
+    return FramesMock;
   }
   try {
-    return Cu.import("resource://gre/modules/AppFrames.jsm", {}).AppFrames;
+    return Cu.import("resource://gre/modules/Frames.jsm", {}).Frames;
   } catch(e) {}
   return null;
 });
 
 function debug(aMsg) {
   /*
   Cc["@mozilla.org/consoleservice;1"]
     .getService(Ci.nsIConsoleService)
@@ -855,18 +855,20 @@ WebappsActor.prototype = {
 
     reg.close(app);
 
     return {};
   },
 
   _appFrames: function () {
     // Try to filter on b2g and mulet
-    if (AppFrames) {
-      return AppFrames.list();
+    if (Frames) {
+      return Frames.list().filter(frame => {
+        return frame.getAttribute('mozapp');
+      });
     } else {
       return [];
     }
   },
 
   listRunningApps: function (aRequest) {
     debug("listRunningApps\n");
 
@@ -950,37 +952,38 @@ WebappsActor.prototype = {
       }
 
       return { actor: actor };
     });
   },
 
   watchApps: function () {
     // For now, app open/close events are only implement on b2g
-    if (AppFrames) {
-      AppFrames.addObserver(this);
+    if (Frames) {
+      Frames.addObserver(this);
     }
     Services.obs.addObserver(this, "webapps-installed", false);
     Services.obs.addObserver(this, "webapps-uninstall", false);
 
     return {};
   },
 
   unwatchApps: function () {
-    if (AppFrames) {
-      AppFrames.removeObserver(this);
+    if (Frames) {
+      Frames.removeObserver(this);
     }
     Services.obs.removeObserver(this, "webapps-installed", false);
     Services.obs.removeObserver(this, "webapps-uninstall", false);
 
     return {};
   },
 
-  onAppFrameCreated: function (frame, isFirstAppFrame) {
-    if (!isFirstAppFrame) {
+  onFrameCreated: function (frame, isFirstAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp || !isFirstAppFrame) {
       return;
     }
 
     let manifestURL = frame.appManifestURL;
     // Only track app frames
     if (!manifestURL) {
       return;
     }
@@ -990,18 +993,19 @@ WebappsActor.prototype = {
         this.conn.send({ from: this.actorID,
                          type: "appOpen",
                          manifestURL: manifestURL
                        });
       }
     });
   },
 
-  onAppFrameDestroyed: function (frame, isLastAppFrame) {
-    if (!isLastAppFrame) {
+  onFrameDestroyed: function (frame, isLastAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp || !isLastAppFrame) {
       return;
     }
 
     let manifestURL = frame.appManifestURL;
     // Only track app frames
     if (!manifestURL) {
       return;
     }
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -262,69 +262,79 @@ BrowserTabList.prototype.constructor = B
  *         The currently selected xul:browser element, if any. Note that the
  *         browser window might not be loaded yet - the function will return
  *         |null| in such cases.
  */
 BrowserTabList.prototype._getSelectedBrowser = function(aWindow) {
   return aWindow.gBrowser ? aWindow.gBrowser.selectedBrowser : null;
 };
 
+/**
+ * Produces an iterable (in this case a generator) to enumerate all available
+ * browser tabs.
+ */
+BrowserTabList.prototype._getBrowsers = function*() {
+  // Iterate over all navigator:browser XUL windows.
+  for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
+    // For each tab in this XUL window, ensure that we have an actor for
+    // it, reusing existing actors where possible. We actually iterate
+    // over 'browser' XUL elements, and BrowserTabActor uses
+    // browser.contentWindow as the debuggee global.
+    for (let browser of this._getChildren(win)) {
+      yield browser;
+    }
+  }
+};
+
 BrowserTabList.prototype._getChildren = function(aWindow) {
   return aWindow.gBrowser.browsers;
 };
 
+BrowserTabList.prototype._isRemoteBrowser = function(browser) {
+  return browser.getAttribute("remote");
+};
+
 BrowserTabList.prototype.getList = function() {
   let topXULWindow = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+  let selectedBrowser = null;
+  if (topXULWindow) {
+    selectedBrowser = this._getSelectedBrowser(topXULWindow);
+  }
 
   // As a sanity check, make sure all the actors presently in our map get
   // picked up when we iterate over all windows' tabs.
   let initialMapSize = this._actorByBrowser.size;
   let foundCount = 0;
 
   // To avoid mysterious behavior if tabs are closed or opened mid-iteration,
   // we update the map first, and then make a second pass over it to yield
   // the actors. Thus, the sequence yielded is always a snapshot of the
   // actors that were live when we began the iteration.
 
   let actorPromises = [];
 
-  // Iterate over all navigator:browser XUL windows.
-  for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
-    let selectedBrowser = this._getSelectedBrowser(win);
-    if (!selectedBrowser) {
-      continue;
+  for (let browser of this._getBrowsers()) {
+    // Do we have an existing actor for this browser? If not, create one.
+    let actor = this._actorByBrowser.get(browser);
+    if (actor) {
+      actorPromises.push(actor.update());
+      foundCount++;
+    } else if (this._isRemoteBrowser(browser)) {
+      actor = new RemoteBrowserTabActor(this._connection, browser);
+      this._actorByBrowser.set(browser, actor);
+      actorPromises.push(actor.connect());
+    } else {
+      actor = new BrowserTabActor(this._connection, browser,
+                                  browser.getTabBrowser());
+      this._actorByBrowser.set(browser, actor);
+      actorPromises.push(promise.resolve(actor));
     }
 
-    // For each tab in this XUL window, ensure that we have an actor for
-    // it, reusing existing actors where possible. We actually iterate
-    // over 'browser' XUL elements, and BrowserTabActor uses
-    // browser.contentWindow as the debuggee global.
-    for (let browser of this._getChildren(win)) {
-      // Do we have an existing actor for this browser? If not, create one.
-      let actor = this._actorByBrowser.get(browser);
-      if (actor) {
-        actorPromises.push(promise.resolve(actor));
-        foundCount++;
-      } else if (browser.isRemoteBrowser) {
-        actor = new RemoteBrowserTabActor(this._connection, browser);
-        this._actorByBrowser.set(browser, actor);
-        let promise = actor.connect().then((form) => {
-          actor._form = form;
-          return actor;
-        });
-        actorPromises.push(promise);
-      } else {
-        actor = new BrowserTabActor(this._connection, browser, win.gBrowser);
-        this._actorByBrowser.set(browser, actor);
-        actorPromises.push(promise.resolve(actor));
-      }
-
-      // Set the 'selected' properties on all actors correctly.
-      actor.selected = (win === topXULWindow && browser === selectedBrowser);
-    }
+    // Set the 'selected' properties on all actors correctly.
+    actor.selected = browser === selectedBrowser;
   }
 
   if (this._testing && initialMapSize !== foundCount)
     throw Error("_actorByBrowser map contained actors for dead tabs");
 
   this._mustNotify = true;
   this._checkListening();
 
@@ -733,19 +743,28 @@ TabActor.prototype = {
     if (this.webNavigation.currentURI) {
       return this.webNavigation.currentURI.spec;
     }
     // Abrupt closing of the browser window may leave callbacks without a
     // currentURI.
     return null;
   },
 
+  /**
+   * This is called by BrowserTabList.getList for existing tab actors prior to
+   * calling |form| below.  It can be used to do any async work that may be
+   * needed to assemble the form.
+   */
+  update: function() {
+    return promise.resolve(this);
+  },
+
   form: function BTA_form() {
     dbg_assert(!this.exited,
-               "grip() shouldn't be called on exited browser actor.");
+               "form() shouldn't be called on exited browser actor.");
     dbg_assert(this.actorID,
                "tab should have an actorID.");
 
     let windowUtils = this.window
       .QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDOMWindowUtils);
 
     let response = {
@@ -956,17 +975,17 @@ TabActor.prototype = {
         parentID = window.parent
                          .QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils)
                          .outerWindowID;
       }
       return {
         id: id,
         url: window.location.href,
-        title: window.title,
+        title: window.document.title,
         parentID: parentID
       };
     });
   },
 
   _notifyDocShellsUpdate: function (docshells) {
     let windows = this._docShellsToWindows(docshells);
     this.conn.send({ from: this.actorID,
@@ -1612,28 +1631,51 @@ function RemoteBrowserTabActor(aConnecti
 {
   this._conn = aConnection;
   this._browser = aBrowser;
   this._form = null;
 }
 
 RemoteBrowserTabActor.prototype = {
   connect: function() {
-    return DebuggerServer.connectToChild(this._conn, this._browser);
+    let connect = DebuggerServer.connectToChild(this._conn, this._browser);
+    return connect.then(form => {
+      this._form = form;
+      return this;
+    });
+  },
+
+  get _mm() {
+    return this._browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader
+           .messageManager;
+  },
+
+  update: function() {
+    let deferred = promise.defer();
+    let onFormUpdate = msg => {
+      this._mm.removeMessageListener("debug:form", onFormUpdate);
+      this._form = msg.json;
+      deferred.resolve(this);
+    };
+    this._mm.addMessageListener("debug:form", onFormUpdate);
+    this._mm.sendAsyncMessage("debug:form");
+    return deferred.promise;
   },
 
   form: function() {
     return this._form;
   },
 
   exit: function() {
     this._browser = null;
   },
 };
 
+exports.RemoteBrowserTabActor = RemoteBrowserTabActor;
+
 function BrowserAddonList(aConnection)
 {
   this._connection = aConnection;
   this._actorByAddonId = new Map();
   this._onListChanged = null;
 }
 
 BrowserAddonList.prototype.getList = function() {
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -41,17 +41,17 @@ let chromeGlobal = this;
     let conn = DebuggerServer.connectToParent(prefix, mm);
     connections.set(id, conn);
 
     let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
     let actorPool = new ActorPool(conn);
     actorPool.addActor(actor);
     conn.addActorPool(actorPool);
 
-    sendAsyncMessage("debug:actor", {actor: actor.grip(), childID: id});
+    sendAsyncMessage("debug:actor", {actor: actor.form(), childID: id});
   });
 
   addMessageListener("debug:connect", onConnect);
 
   let onDisconnect = DevToolsUtils.makeInfallible(function (msg) {
     removeMessageListener("debug:disconnect", onDisconnect);
 
     // Call DebuggerServerConnection.close to destroy all child actors