Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 14 Oct 2014 16:59:12 -0700
changeset 210324 c76eb2abb768775a1300591b6fd18f38f65ca1b3
parent 210323 01f04d75519d2a99328df5f1583f3446cca98683 (current diff)
parent 210295 7246195fb751da0975f4a5abff617ff42e2ba712 (diff)
child 210371 62f0b771583c09fa24583da75ab08115fa04ce79
push id9365
push userkwierso@gmail.com
push dateWed, 15 Oct 2014 00:37:58 +0000
treeherderfx-team@a974e5f136ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
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