merge m-c to fx-team; a=merge
authorTim Taubert <ttaubert@mozilla.com>
Mon, 13 Oct 2014 22:58:30 +0200
changeset 210115 9bfd8ab6b440c12adb946ec9606f299353141b07
parent 210098 78a4540b0a9caf78cd7ca923524925639bc740a8 (current diff)
parent 210114 4f831092e95831c355fef06627e2aa332a6df9bb (diff)
child 210151 c7f5a7b46fcdefe55d5fd160e9d9e017cc6ebf02
child 210162 f27341df2b4d801d72b8b6cbae2dc40e5057ebc6
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmerge
milestone36.0a1
merge m-c to fx-team; a=merge
browser/components/sessionstore/RevivableWindows.jsm
browser/components/sessionstore/test/browser_revive_windows.js
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -56,16 +56,20 @@ input[type=button] {
   top: 15px;
 }
 
 #newtab-intro-what:-moz-locale-dir(rtl) {
   left: 55px;
   right: auto;
 }
 
+#newtab-scrollbox[page-disabled] #newtab-intro-what {
+  display: none;
+}
+
 #newtab-intro-panel {
   color: #6a7b86;
   font-size: 15px;
   line-height: 19px;
   width: 520px;
 }
 
 #newtab-intro-panel h1 {
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -386,17 +386,18 @@ loop.conversation = (function(mozL10n) {
      * @param {Object} progressData The progress data from the websocket.
      * @param {String} previousState The previous state from the websocket.
      */
     _handleWebSocketProgress: function(progressData, previousState) {
       // We only care about the terminated state at the moment.
       if (progressData.state !== "terminated")
         return;
 
-      if (progressData.reason === "cancel") {
+      if (progressData.reason === "cancel" ||
+          progressData.reason === "closed") {
         this._abortIncomingCall();
         return;
       }
 
       if (progressData.reason === "timeout" &&
           (previousState === "init" || previousState === "alerting")) {
         this._abortIncomingCall();
       }
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -386,17 +386,18 @@ loop.conversation = (function(mozL10n) {
      * @param {Object} progressData The progress data from the websocket.
      * @param {String} previousState The previous state from the websocket.
      */
     _handleWebSocketProgress: function(progressData, previousState) {
       // We only care about the terminated state at the moment.
       if (progressData.state !== "terminated")
         return;
 
-      if (progressData.reason === "cancel") {
+      if (progressData.reason === "cancel" ||
+          progressData.reason === "closed") {
         this._abortIncomingCall();
         return;
       }
 
       if (progressData.reason === "timeout" &&
           (previousState === "init" || previousState === "alerting")) {
         this._abortIncomingCall();
       }
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -401,16 +401,56 @@ describe("loop.conversation", function()
                   sandbox.clock.tick(1);
 
                   sinon.assert.calledOnce(window.close);
                   done();
                 });
               });
             });
 
+            describe("progress - terminated - closed", function() {
+              it("should stop alerting", function(done) {
+                promise.then(function() {
+                  icView._websocket.trigger("progress", {
+                    state: "terminated",
+                    reason: "closed"
+                  });
+
+                  sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
+                  done();
+                });
+              });
+
+              it("should close the websocket", function(done) {
+                promise.then(function() {
+                  icView._websocket.trigger("progress", {
+                    state: "terminated",
+                    reason: "closed"
+                  });
+
+                  sinon.assert.calledOnce(icView._websocket.close);
+                  done();
+                });
+              });
+
+              it("should close the window", function(done) {
+                promise.then(function() {
+                  icView._websocket.trigger("progress", {
+                    state: "terminated",
+                    reason: "closed"
+                  });
+
+                  sandbox.clock.tick(1);
+
+                  sinon.assert.calledOnce(window.close);
+                  done();
+                });
+              });
+            });
+
             describe("progress - terminated - timeout (previousState = alerting)", function() {
               it("should stop alerting", function(done) {
                 promise.then(function() {
                   icView._websocket.trigger("progress", {
                     state: "terminated",
                     reason: "timeout"
                   }, "alerting");
 
deleted file mode 100644
--- a/browser/components/sessionstore/RevivableWindows.jsm
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["RevivableWindows"];
-
-// List of closed windows that we can revive when closing
-// windows in succession until the browser quits.
-let closedWindows = [];
-
-/**
- * This module keeps track of closed windows that are revivable. On Windows
- * and Linux we can revive windows before saving to disk - i.e. moving them
- * from state._closedWindows[] to state.windows[] so that they're opened
- * automatically on next startup. This feature lets us properly support
- * closing windows in succession until the browser quits.
- *
- * The length of the list is not capped by max_undo_windows unlike
- * state._closedWindows[].
- */
-this.RevivableWindows = Object.freeze({
-  // Returns whether there are windows to revive.
-  get isEmpty() {
-    return closedWindows.length == 0;
-  },
-
-  // Add a window to the list.
-  add(winState) {
-#ifndef XP_MACOSX
-    closedWindows.push(winState);
-#endif
-  },
-
-  // Get the list of revivable windows.
-  get() {
-    return [...closedWindows];
-  },
-
-  // Clear the list of revivable windows.
-  clear() {
-    closedWindows.length = 0;
-  }
-});
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -14,18 +14,16 @@ Cu.import("resource://gre/modules/Timer.
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RevivableWindows",
-  "resource:///modules/sessionstore/RevivableWindows.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 // Minimal interval between two save operations (in milliseconds).
@@ -202,34 +200,33 @@ let SessionSaverInternal = {
 
     // Make sure that we keep the previous session if we started with a single
     // private window and no non-private windows have been opened, yet.
     if (state.deferredInitialState) {
       state.windows = state.deferredInitialState.windows || [];
       delete state.deferredInitialState;
     }
 
-    // We want to revive closed windows that have been closed in succession
-    // without any user action in between closing those. This happens here in
-    // the SessionSaver because we only want to revive when saving to disk.
-    // On Mac OS X this list will always be empty.
-    let windowsToRevive = RevivableWindows.get();
-    state.windows.unshift(...windowsToRevive);
-    let revivedWindows = state._closedWindows.splice(0, windowsToRevive.length);
-#ifdef DEBUG
-    // Check that the windows to revive equal the windows
-    // that we removed from the list of closed windows.
-    let match = revivedWindows.every((win, idx) => {
-      return win == windowsToRevive[windowsToRevive.length - 1 - idx];
-    });
+#ifndef XP_MACOSX
+    // We want to restore closed windows that are marked with _shouldRestore.
+    // We're doing this here because we want to control this only when saving
+    // the file.
+    while (state._closedWindows.length) {
+      let i = state._closedWindows.length - 1;
 
-    if (!match) {
-      throw new Error("SessionStore: revived windows didn't match closed windows");
+      if (!state._closedWindows[i]._shouldRestore) {
+        // We only need to go until _shouldRestore
+        // is falsy since we're going in reverse.
+        break;
+      }
+
+      delete state._closedWindows[i]._shouldRestore;
+      state.windows.unshift(state._closedWindows.pop());
     }
-#endif DEBUG
+#endif
 
     stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
     return this._writeState(state);
   },
 
   /**
    * Saves the current session state. Collects data asynchronously and calls
    * _saveState() to collect data again (with a cache hit rate of hopefully
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -102,18 +102,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
   "resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
   "resource:///modules/sessionstore/GlobalState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RevivableWindows",
-  "resource:///modules/sessionstore/RevivableWindows.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RunState",
   "resource:///modules/sessionstore/RunState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   "resource:///modules/sessionstore/SessionSaver.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
@@ -698,19 +696,17 @@ let SessionStoreInternal = {
         this.onTabHide(win, aEvent.originalTarget);
         break;
       case "TabPinned":
       case "TabUnpinned":
       case "SwapDocShells":
         this.saveStateDelayed(win);
         break;
     }
-
-    // Any event handled here indicates a user action.
-    RevivableWindows.clear();
+    this._clearRestoringWindows();
   },
 
   /**
    * Generate a unique window identifier
    * @return string
    *         A unique string to identify a window
    */
   _generateWindowID: function ssi_generateWindowID() {
@@ -1012,16 +1008,22 @@ let SessionStoreInternal = {
 
       if (isFullyLoaded) {
         winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
         winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
                                                   tabbrowser.selectedTab);
         SessionCookies.update([winData]);
       }
 
+#ifndef XP_MACOSX
+      // Until we decide otherwise elsewhere, this window is part of a series
+      // of closing windows to quit.
+      winData._shouldRestore = true;
+#endif
+
       // Store the window's close date to figure out when each individual tab
       // was closed. This timestamp should allow re-arranging data based on how
       // recently something was closed.
       winData.closedAt = Date.now();
 
       // Save non-private windows if they have at
       // least one saveable tab or are the last window.
       if (!winData.isPrivate) {
@@ -1032,30 +1034,27 @@ let SessionStoreInternal = {
         let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
 
         // When closing windows one after the other until Firefox quits, we
         // will move those closed in series back to the "open windows" bucket
         // before writing to disk. If however there is only a single window
         // with tabs we deem not worth saving then we might end up with a
         // random closed or even a pop-up window re-opened. To prevent that
         // we explicitly allow saving an "empty" window state.
-        let numOpenWindows = Object.keys(this._windows).length;
-        let isLastWindow = numOpenWindows == 1 && RevivableWindows.isEmpty;
+        let isLastWindow =
+          Object.keys(this._windows).length == 1 &&
+          !this._closedWindows.some(win => win._shouldRestore || false);
 
         if (hasSaveableTabs || isLastWindow) {
           // we don't want to save the busy state
           delete winData.busy;
 
           this._closedWindows.unshift(winData);
           this._capClosedWindows();
         }
-
-        // Until we decide otherwise elsewhere, this window
-        // is part of a series of closing windows to quit.
-        RevivableWindows.add(winData);
       }
 
       // clear this window from the list
       delete this._windows[aWindow.__SSi];
 
       // save the state without this window to disk
       this.saveStateDelayed();
     }
@@ -1151,28 +1150,27 @@ let SessionStoreInternal = {
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
       if (ix in openWindows) {
         this._windows[ix]._closedTabs = [];
       } else {
         delete this._windows[ix];
       }
     }
-
     // also clear all data about closed windows
     this._closedWindows = [];
-    RevivableWindows.clear();
-
     // give the tabbrowsers a chance to clear their histories first
     var win = this._getMostRecentBrowserWindow();
     if (win) {
       win.setTimeout(() => SessionSaver.run(), 0);
     } else if (RunState.isRunning) {
       SessionSaver.run();
     }
+
+    this._clearRestoringWindows();
   },
 
   /**
    * On purge of domain data
    * @param aData
    *        String domain data
    */
   onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
@@ -1216,22 +1214,21 @@ let SessionStoreInternal = {
         // some duplication from restoreHistory - make sure we get the correct title
         let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
         if (activeIndex >= selectedTab.entries.length)
           activeIndex = selectedTab.entries.length - 1;
         this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
       }
     }
 
-    // Purging domain data indicates a user action.
-    RevivableWindows.clear();
-
     if (RunState.isRunning) {
       SessionSaver.run();
     }
+
+    this._clearRestoringWindows();
   },
 
   /**
    * On preference change
    * @param aData
    *        String preference changed
    */
   onPrefChange: function ssi_onPrefChange(aData) {
@@ -3345,16 +3342,31 @@ let SessionStoreInternal = {
       normalWindowIndex++;
     if (normalWindowIndex >= this._max_windows_undo)
       spliceTo = normalWindowIndex + 1;
 #endif
     this._closedWindows.splice(spliceTo, this._closedWindows.length);
   },
 
   /**
+   * Clears the set of windows that are "resurrected" before writing to disk to
+   * make closing windows one after the other until shutdown work as expected.
+   *
+   * This function should only be called when we are sure that there has been
+   * a user action that indicates the browser is actively being used and all
+   * windows that have been closed before are not part of a series of closing
+   * windows.
+   */
+  _clearRestoringWindows: function ssi_clearRestoringWindows() {
+    for (let i = 0; i < this._closedWindows.length; i++) {
+      delete this._closedWindows[i]._shouldRestore;
+    }
+  },
+
+  /**
    * Reset state to prepare for a new session state to be restored.
    */
   _resetRestoringState: function ssi_initRestoringState() {
     TabRestoreQueue.reset();
     this._tabsRestoringCount = 0;
   },
 
   /**
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -41,13 +41,12 @@ EXTRA_JS_MODULES.sessionstore = [
     'SessionWorker.jsm',
     'TabAttributes.jsm',
     'TabState.jsm',
     'TabStateCache.jsm',
     'Utils.jsm',
 ]
 
 EXTRA_PP_JS_MODULES.sessionstore += [
-    'RevivableWindows.jsm',
     'SessionSaver.jsm',
     'SessionStore.jsm',
 ]
 
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -76,17 +76,16 @@ skip-if = buildapp == 'mulet'
 [browser_frametree.js]
 [browser_frame_history.js]
 [browser_global_store.js]
 [browser_history_cap.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_pageStyle.js]
 [browser_privatetabs.js]
-[browser_revive_windows.js]
 [browser_scrollPositions.js]
 [browser_sessionHistory.js]
 skip-if = e10s
 [browser_sessionStorage.js]
 skip-if = e10s
 [browser_swapDocShells.js]
 skip-if = e10s # See bug 918634
 [browser_telemetry.js]
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_revive_windows.js
+++ /dev/null
@@ -1,160 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const IS_MAC = ("nsILocalFileMac" in Ci);
-const URL_PREFIX = "about:mozilla?t=browser_revive_windows&r=";
-const PREF_MAX_UNDO = "browser.sessionstore.max_windows_undo";
-
-const URL_MAIN_WINDOW = URL_PREFIX + Math.random();
-const URL_ADD_WINDOW1 = URL_PREFIX + Math.random();
-const URL_ADD_WINDOW2 = URL_PREFIX + Math.random();
-const URL_CLOSED_WINDOW = URL_PREFIX + Math.random();
-
-add_task(function* setup() {
-  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_MAX_UNDO));
-});
-
-/**
- * This test ensure that when closing windows in succession until the browser
- * quits we are able to revive more windows than we keep around for the
- * "Undo Close Window" feature.
- */
-add_task(function* test_revive_windows() {
-  // We can restore a single window max.
-  Services.prefs.setIntPref(PREF_MAX_UNDO, 1);
-
-  // Clear list of closed windows.
-  forgetClosedWindows();
-
-  let windows = [];
-
-  // Create three windows.
-  for (let i = 0; i < 3; i++) {
-    let win = yield promiseNewWindow();
-    windows.push(win);
-
-    let tab = win.gBrowser.addTab("about:mozilla");
-    yield promiseBrowserLoaded(tab.linkedBrowser);
-  }
-
-  // Create a private window.
-  // This window must not be revived.
-  {
-    let win = yield promiseNewWindow({private: true});
-    windows.push(win);
-
-    let tab = win.gBrowser.addTab("about:mozilla");
-    yield promiseBrowserLoaded(tab.linkedBrowser);
-  }
-
-  // Close all windows.
-  for (let win of windows) {
-    yield promiseWindowClosed(win);
-  }
-
-  is(ss.getClosedWindowCount(), 1, "one window restorable");
-
-  // Save to disk and read.
-  let state = JSON.parse(yield promiseRecoveryFileContents());
-
-  // Check number of windows.
-  if (IS_MAC) {
-    is(state.windows.length, 1, "one open window");
-    is(state._closedWindows.length, 1, "one closed window");
-  } else {
-    is(state.windows.length, 4, "four open windows");
-    is(state._closedWindows.length, 0, "closed windows");
-  }
-});
-
-/**
- * This test ensures that when closing windows one after the other until the
- * browser shuts down (on Windows and Linux) we revive closed windows in the
- * right order.
- */
-add_task(function* test_revive_windows_order() {
-  // We can restore up to three windows max.
-  Services.prefs.setIntPref(PREF_MAX_UNDO, 3);
-
-  // Clear list of closed windows.
-  forgetClosedWindows();
-
-  let tab = gBrowser.addTab(URL_MAIN_WINDOW);
-  yield promiseBrowserLoaded(tab.linkedBrowser);
-  TabState.flush(tab.linkedBrowser);
-  registerCleanupFunction(() => gBrowser.removeTab(tab));
-
-  let win0 = yield promiseNewWindow();
-  let tab0 = win0.gBrowser.addTab(URL_CLOSED_WINDOW);
-  yield promiseBrowserLoaded(tab0.linkedBrowser);
-
-  yield promiseWindowClosed(win0);
-  let data = ss.getClosedWindowData();
-  ok(data.contains(URL_CLOSED_WINDOW), "window is restorable");
-
-  let win1 = yield promiseNewWindow();
-  let tab1 = win1.gBrowser.addTab(URL_ADD_WINDOW1);
-  yield promiseBrowserLoaded(tab1.linkedBrowser);
-
-  let win2 = yield promiseNewWindow();
-  let tab2 = win2.gBrowser.addTab(URL_ADD_WINDOW2);
-  yield promiseBrowserLoaded(tab2.linkedBrowser);
-
-  // Close both windows so that |win1| will be opened first and would be
-  // behind |win2| that was closed later.
-  yield promiseWindowClosed(win1);
-  yield promiseWindowClosed(win2);
-
-  // Repeat the checks once.
-  for (let i = 0; i < 2; i++) {
-    info(`checking window data, iteration #${i}`);
-    let contents = yield promiseRecoveryFileContents();
-    let {windows, _closedWindows: closedWindows} = JSON.parse(contents);
-
-    if (IS_MAC) {
-      // Check number of windows.
-      is(windows.length, 1, "one open window");
-      is(closedWindows.length, 3, "three closed windows");
-
-      // Check open window.
-      ok(JSON.stringify(windows).contains(URL_MAIN_WINDOW),
-        "open window is correct");
-
-      // Check closed windows.
-      ok(JSON.stringify(closedWindows[0]).contains(URL_ADD_WINDOW2),
-        "correct first additional window");
-      ok(JSON.stringify(closedWindows[1]).contains(URL_ADD_WINDOW1),
-        "correct second additional window");
-      ok(JSON.stringify(closedWindows[2]).contains(URL_CLOSED_WINDOW),
-        "correct main window");
-    } else {
-      // Check number of windows.
-      is(windows.length, 3, "three open windows");
-      is(closedWindows.length, 1, "one closed window");
-
-      // Check closed window.
-      ok(JSON.stringify(closedWindows).contains(URL_CLOSED_WINDOW),
-        "closed window is correct");
-
-      // Check that windows are in the right order.
-      ok(JSON.stringify(windows[0]).contains(URL_ADD_WINDOW1),
-        "correct first additional window");
-      ok(JSON.stringify(windows[1]).contains(URL_ADD_WINDOW2),
-        "correct second additional window");
-      ok(JSON.stringify(windows[2]).contains(URL_MAIN_WINDOW),
-        "correct main window");
-    }
-  }
-});
-
-function promiseNewWindow(opts = {private: false}) {
-  return new Promise(resolve => whenNewWindowLoaded(opts, resolve));
-}
-
-function forgetClosedWindows() {
-  while (ss.getClosedWindowCount()) {
-    ss.forgetClosedWindow(0);
-  }
-}
--- a/browser/devtools/layoutview/test/browser.ini
+++ b/browser/devtools/layoutview/test/browser.ini
@@ -9,13 +9,14 @@ support-files =
 [browser_layoutview.js]
 [browser_layoutview_guides.js]
 # browser_layoutview_guides.js should be re-enabled when bug 1029451 is fixed.
 skip-if = true
 [browser_layoutview_rotate-labels-on-sides.js]
 [browser_layoutview_update-after-navigation.js]
 [browser_layoutview_update-after-reload.js]
 [browser_layoutview_update-in-iframes.js]
+skip-if = true # Bug 1020038 layout-view updates for iframe elements changes
 [browser_editablemodel.js]
 # [browser_editablemodel_allproperties.js]
 # Disabled for too many intermittent failures (bug 1009322)
 [browser_editablemodel_border.js]
 [browser_editablemodel_stylerules.js]
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -2755,17 +2755,17 @@ public class BrowserApp extends GeckoApp
             }
         }
 
         // Disable share menuitem for about:, chrome:, file:, and resource: URIs
         final boolean shareEnabled = RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_SHARE);
         share.setVisible(shareEnabled);
         share.setEnabled(StringUtils.isShareableUrl(url) && shareEnabled);
         MenuUtils.safeSetEnabled(aMenu, R.id.apps, RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_INSTALL_APPS));
-        MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_INSTALL_EXTENSIONS));
+        MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_INSTALL_EXTENSION));
         MenuUtils.safeSetEnabled(aMenu, R.id.downloads, RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_DOWNLOADS));
 
         // NOTE: Use MenuUtils.safeSetEnabled because some actions might
         // be on the BrowserToolbar context menu.
         if (Versions.feature11Plus) {
             MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
         }
         MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -76,16 +76,17 @@ import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.location.Location;
 import android.location.LocationListener;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.StrictMode;
 import android.provider.ContactsContract;
 import android.provider.MediaStore.Images.Media;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Base64;
@@ -973,16 +974,22 @@ public abstract class GeckoApp
 
                 while((byteRead = is.read(buf)) != -1) {
                     os.write(buf, 0, byteRead);
                 }
                 byte[] imgBuffer = os.toByteArray();
                 image = BitmapUtils.decodeByteArray(imgBuffer);
             }
             if (image != null) {
+                // Some devices don't have a DCIM folder and the Media.insertImage call will fail.
+                File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+                if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
+                    Toast.makeText((Context) this, R.string.set_image_path_fail, Toast.LENGTH_SHORT).show();
+                    return;
+                }
                 String path = Media.insertImage(getContentResolver(),image, null, null);
                 if (path == null) {
                     Toast.makeText((Context) this, R.string.set_image_path_fail, Toast.LENGTH_SHORT).show();
                     return;
                 }
                 final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
                 intent.setData(Uri.parse(path));
--- a/mobile/android/base/RestrictedProfiles.java
+++ b/mobile/android/base/RestrictedProfiles.java
@@ -50,24 +50,26 @@ public class RestrictedProfiles {
     }};
 
     /* This is a list of things we can restrict you from doing. Some of these are reflected in Android UserManager constants.
      * Others are specific to us.
      * These constants should be in sync with the ones from toolkit/components/parentalcontrols/nsIParentalControlServices.idl
      */
     public static enum Restriction {
         DISALLOW_DOWNLOADS(1, "no_download_files"),
-        DISALLOW_INSTALL_EXTENSIONS(2, "no_install_extensions"),
+        DISALLOW_INSTALL_EXTENSION(2, "no_install_extensions"),
         DISALLOW_INSTALL_APPS(3, "no_install_apps"), // UserManager.DISALLOW_INSTALL_APPS
         DISALLOW_BROWSE_FILES(4, "no_browse_files"),
         DISALLOW_SHARE(5, "no_share"),
         DISALLOW_BOOKMARK(6, "no_bookmark"),
         DISALLOW_ADD_CONTACTS(7, "no_add_contacts"),
         DISALLOW_SET_IMAGE(8, "no_set_image"),
-        DISALLOW_MODIFY_ACCOUNTS(9, "no_modify_accounts"); // UserManager.DISALLOW_MODIFY_ACCOUNTS
+        DISALLOW_MODIFY_ACCOUNTS(9, "no_modify_accounts"), // UserManager.DISALLOW_MODIFY_ACCOUNTS
+        DISALLOW_REMOTE_DEBUGGING(10, "no_remote_debugging"),
+        DISALLOW_IMPORT_SETTINGS(11, "no_import_settings");
 
         public final int id;
         public final String name;
 
         private Restriction(final int id, final String name) {
             this.id = id;
             this.name = name;
         }
--- a/mobile/android/base/home/HomePagerTabStrip.java
+++ b/mobile/android/base/home/HomePagerTabStrip.java
@@ -5,17 +5,20 @@
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.BounceAnimator;
 import org.mozilla.gecko.animation.BounceAnimator.Attributes;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.support.v4.view.PagerTabStrip;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
 import com.nineoldandroids.animation.AnimatorSet;
 import com.nineoldandroids.animation.ObjectAnimator;
 import com.nineoldandroids.animation.ValueAnimator;
@@ -32,40 +35,58 @@ class HomePagerTabStrip extends PagerTab
     private static final int ANIMATION_DELAY_MS = 250;
     private static final int ALPHA_MS = 10;
     private static final int BOUNCE1_MS = 350;
     private static final int BOUNCE2_MS = 200;
     private static final int BOUNCE3_MS = 100;
     private static final int BOUNCE4_MS = 100;
     private static final int INIT_OFFSET = 100;
 
+    private final Paint shadowPaint;
+    private final int shadowSize;
+
     public HomePagerTabStrip(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public HomePagerTabStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HomePagerTabStrip);
         int color = a.getColor(R.styleable.HomePagerTabStrip_tabIndicatorColor, 0x00);
         a.recycle();
 
         setTabIndicatorColor(color);
 
+        final Resources res = getResources();
+        shadowSize = res.getDimensionPixelSize(R.dimen.tabs_strip_shadow_size);
+
+        shadowPaint = new Paint();
+        shadowPaint.setColor(res.getColor(R.color.url_bar_shadow));
+        shadowPaint.setStrokeWidth(0.0f);
+
         getViewTreeObserver().addOnPreDrawListener(new PreDrawListener());
     }
 
     @Override
     public int getPaddingBottom() {
         // PagerTabStrip enforces a minimum bottom padding of 6dp which causes
         // misalignments when using 'center_vertical' gravity. Force padding bottom
         // to 0dp so that children are properly centered.
         return 0;
     }
 
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        final int height = getHeight();
+        canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
+    }
+
     private void animateTitles() {
         final View prevTextView = getChildAt(0);
         final View nextTextView = getChildAt(getChildCount() - 1);
 
         if (prevTextView == null || nextTextView == null) {
             return;
         }
 
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -3,18 +3,20 @@
  * 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.home;
 
 import org.mozilla.gecko.R;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
@@ -30,26 +32,45 @@ public class TabMenuStrip extends Horizo
 
     // Offset between the selected tab title and the edge of the screen,
     // except for the first and last tab in the tab strip.
     private static final int TITLE_OFFSET_DIPS = 24;
 
     private final int titleOffset;
     private final TabMenuStripLayout layout;
 
+    private final Paint shadowPaint;
+    private final int shadowSize;
+
     public TabMenuStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         // Disable the scroll bar.
         setHorizontalScrollBarEnabled(false);
 
-        titleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+        final Resources res = getResources();
+
+        titleOffset = (int) (TITLE_OFFSET_DIPS * res.getDisplayMetrics().density);
 
         layout = new TabMenuStripLayout(context, attrs);
         addView(layout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+        shadowSize = res.getDimensionPixelSize(R.dimen.tabs_strip_shadow_size);
+
+        shadowPaint = new Paint();
+        shadowPaint.setColor(res.getColor(R.color.url_bar_shadow));
+        shadowPaint.setStrokeWidth(0.0f);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        final int height = getHeight();
+        canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
     }
 
     @Override
     public void onAddPagerView(String title) {
         layout.onAddPagerView(title);
     }
 
     @Override
--- a/mobile/android/base/preferences/AndroidImportPreference.java
+++ b/mobile/android/base/preferences/AndroidImportPreference.java
@@ -2,29 +2,41 @@
  * 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.preferences;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.RestrictedProfiles;
+import org.mozilla.gecko.RestrictedProfiles.Restriction;
 
 import java.util.Set;
 
 import android.app.ProgressDialog;
 import android.content.Context;
+import android.preference.Preference;
 import android.util.AttributeSet;
 import android.util.Log;
 
 class AndroidImportPreference extends MultiPrefMultiChoicePreference {
     private static final String LOGTAG = "AndroidImport";
+    public static final String PREF_KEY = "android.not_a_preference.import_android";
     private static final String PREF_KEY_PREFIX = "import_android.data.";
     private final Context mContext;
 
+    public static class Handler implements GeckoPreferences.PrefHandler {
+        public boolean setupPref(Context context, Preference pref) {
+            return RestrictedProfiles.isAllowed(Restriction.DISALLOW_IMPORT_SETTINGS);
+        }
+
+        public void onChange(Context context, Preference pref, Object newValue) { }
+    }
+
     public AndroidImportPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
     }
 
     @Override
     protected void onDialogClosed(boolean positiveResult) {
         super.onDialogClosed(positiveResult);
--- a/mobile/android/base/preferences/ClearOnShutdownPref.java
+++ b/mobile/android/base/preferences/ClearOnShutdownPref.java
@@ -14,22 +14,23 @@ import org.mozilla.gecko.util.PrefUtils;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.preference.Preference;
 
 public class ClearOnShutdownPref implements GeckoPreferences.PrefHandler {
     public static final String PREF = GeckoPreferences.NON_PREF_PREFIX + "history.clear_on_exit";
 
     @Override
-    public void setupPref(Context context, Preference pref) {
+    public boolean setupPref(Context context, Preference pref) {
         // The pref is initialized asynchronously. Read the pref explicitly
         // here to make sure we have the data.
         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
         final Set<String> clearItems = PrefUtils.getStringSet(prefs, PREF, new HashSet<String>());
         ((ListCheckboxPreference) pref).setChecked(clearItems.size() > 0);
+        return true;
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public void onChange(Context context, Preference pref, Object newValue) {
         final Set<String> vals = (Set<String>) newValue;
         ((ListCheckboxPreference) pref).setChecked(vals.size() > 0);
     }
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -695,16 +695,22 @@ OnSharedPreferenceChangeListener
                     continue;
                 } else if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED &&
                            (PREFS_GEO_REPORTING.equals(key) ||
                             PREFS_GEO_LEARN_MORE.equals(key))) {
                     preferences.removePreference(pref);
                     i--;
                     continue;
                 } else if (PREFS_DEVTOOLS_REMOTE_ENABLED.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(RestrictedProfiles.Restriction.DISALLOW_REMOTE_DEBUGGING)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
+
                     final Context thisContext = this;
                     pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                         @Override
                         public boolean onPreferenceClick(Preference preference) {
                             // Display toast to remind setting up tcp forwarding.
                             if (((CheckBoxPreference) preference).isChecked()) {
                                 Toast.makeText(thisContext, R.string.devtools_remote_debugging_forward, Toast.LENGTH_SHORT).show();
                             }
@@ -739,17 +745,21 @@ OnSharedPreferenceChangeListener
                 } else if (PREFS_DISPLAY_TITLEBAR_MODE.equals(key) &&
                            NewTabletUI.isEnabled(this)) {
                     // New tablet always shows URLS, not titles.
                     preferences.removePreference(pref);
                     i--;
                     continue;
                 } else if (handlers.containsKey(key)) {
                     PrefHandler handler = handlers.get(key);
-                    handler.setupPref(this, pref);
+                    if (!handler.setupPref(this, pref)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
                 }
 
                 // Some Preference UI elements are not actually preferences,
                 // but they require a key to work correctly. For example,
                 // "Clear private data" requires a key for its state to be
                 // saved when the orientation changes. It uses the
                 // "android.not_a_preference.privacy.clear" key - which doesn't
                 // exist in Gecko - to satisfy this requirement.
@@ -1021,23 +1031,26 @@ OnSharedPreferenceChangeListener
         } else if (PREFS_SUGGESTED_SITES.equals(key)) {
             refreshSuggestedSites();
         } else if (PREFS_NEW_TABLET_UI.equals(key)) {
             Toast.makeText(this, R.string.new_tablet_restart, Toast.LENGTH_SHORT).show();
         }
     }
 
     public interface PrefHandler {
-        public void setupPref(Context context, Preference pref);
+        // Allows the pref to do any initialization it needs. Return false to have the pref removed
+        // from the prefs screen entirely.
+        public boolean setupPref(Context context, Preference pref);
         public void onChange(Context context, Preference pref, Object newValue);
     }
 
     @SuppressWarnings("serial")
     private final Map<String, PrefHandler> handlers = new HashMap<String, PrefHandler>() {{
         put(ClearOnShutdownPref.PREF, new ClearOnShutdownPref());
+        put(AndroidImportPreference.PREF_KEY, new AndroidImportPreference.Handler());
     }};
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         final String prefName = preference.getKey();
         Log.i(LOGTAG, "Changed " + prefName + " = " + newValue);
 
         Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, Method.SETTINGS, prefName);
--- a/mobile/android/base/resources/layout-large-v11/home_pager.xml
+++ b/mobile/android/base/resources/layout-large-v11/home_pager.xml
@@ -9,14 +9,14 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="match_parent"
                                   android:layout_height="match_parent"
                                   android:background="@android:color/white">
 
     <org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
-                                         android:layout_height="32dip"
+                                         android:layout_height="@dimen/tabs_strip_height"
                                          android:background="@color/background_light"
                                          android:layout_gravity="top"
                                          gecko:strip="@drawable/home_tab_menu_strip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ b/mobile/android/base/resources/layout/home_pager.xml
@@ -9,16 +9,16 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="match_parent"
                                   android:layout_height="match_parent"
                                   android:background="@android:color/white">
 
     <org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="match_parent"
-                                              android:layout_height="40dip"
+                                              android:layout_height="@dimen/tabs_strip_height"
                                               android:layout_gravity="top"
                                               android:gravity="center_vertical"
                                               android:background="@color/background_light"
                                               gecko:tabIndicatorColor="@color/text_color_highlight"
                                               android:textAppearance="@style/TextAppearance.Widget.HomePagerTabStrip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/values-land/styles.xml
+++ b/mobile/android/base/resources/values-land/styles.xml
@@ -66,14 +66,9 @@
     </style>
 
     <style name="TabsPanelItem.TextAppearance.Linkified.Resend">
         <item name="android:layout_height">match_parent</item>
         <item name="android:gravity">center</item>
         <item name="android:layout_gravity">center</item>
     </style>
 
-    <style name="Widget.Home.HomeList">
-        <item name="topDivider">true</item>
-        <item name="android:scrollbarStyle">outsideOverlay</item>
-    </style>
-
 </resources>
--- a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
@@ -14,21 +14,16 @@
     <style name="Widget.TopSitesGridView" parent="Widget.GridView">
         <item name="android:paddingLeft">55dp</item>
         <item name="android:paddingRight">55dp</item>
         <item name="android:paddingBottom">30dp</item>
         <item name="android:horizontalSpacing">56dp</item>
         <item name="android:verticalSpacing">20dp</item>
     </style>
 
-    <style name="Widget.Home.HomeList">
-        <item name="android:scrollbarStyle">outsideOverlay</item>
-        <item name="topDivider">true</item>
-    </style>
-
     <!-- Tabs panel -->
     <style name="TabsPanelFrame.RemoteTabs" parent="TabsPanelFrameBase">
         <item name="android:paddingLeft">0dp</item>
         <item name="android:paddingRight">0dp</item>
     </style>
 
     <style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
         <item name="android:paddingTop">12dp</item>
--- a/mobile/android/base/resources/values-xlarge-v11/styles.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/styles.xml
@@ -11,21 +11,16 @@
     -->
 
     <!-- TabWidget --> 
     <style name="TabWidget">
         <item name="android:layout_width">300dip</item>
         <item name="android:layout_height">48dip</item>
     </style>
 
-    <style name="Widget.Home.HomeList">
-        <item name="android:scrollbarStyle">outsideOverlay</item>
-        <item name="topDivider">true</item>
-    </style>
-
     <!-- Tabs panel -->
     <style name="TabsPanelFrame.RemoteTabs" parent="TabsPanelFrameBase">
         <item name="android:paddingLeft">212dp</item>
         <item name="android:paddingRight">212dp</item>
     </style>
 
     <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
         <item name="android:paddingTop">30dp</item>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -100,18 +100,20 @@
     <dimen name="searchpreferences_icon_size">32dp</dimen>
     <dimen name="tab_thumbnail_height">90dp</dimen>
     <dimen name="tab_thumbnail_width">160dp</dimen>
     <dimen name="tabs_counter_size">22sp</dimen>
     <dimen name="tabs_panel_indicator_width">60dp</dimen>
     <dimen name="tabs_panel_list_padding">16dip</dimen>
     <dimen name="tabs_list_divider_height">2dp</dimen>
     <dimen name="tabs_sidebar_width">200dp</dimen>
+    <dimen name="tabs_strip_height">40dp</dimen>
     <dimen name="tabs_strip_button_width">100dp</dimen>
     <dimen name="tabs_strip_button_padding">18dp</dimen>
+    <dimen name="tabs_strip_shadow_size">1dp</dimen>
     <dimen name="tabs_tray_horizontal_height">156dp</dimen>
     <dimen name="text_selection_handle_width">47dp</dimen>
     <dimen name="text_selection_handle_height">58dp</dimen>
     <dimen name="text_selection_handle_shadow">11dp</dimen>
     <dimen name="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
     <dimen name="url_bar_offset_left">32dp</dimen>
     <dimen name="history_tab_indicator_height">50dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -58,20 +58,16 @@
     <style name="Widget.GridView" parent="Widget.BaseGridView">
         <item name="android:verticalSpacing">0dip</item>
         <item name="android:horizontalSpacing">0dip</item>
         <item name="android:cacheColorHint">@android:color/transparent</item>
         <item name="android:listSelector">@drawable/action_bar_button</item>
     </style>
 
     <style name="Widget.Home.HomeList">
-        <item name="android:paddingTop">0dip</item>
-        <item name="android:paddingRight">0dip</item>
-        <item name="android:paddingLeft">0dip</item>
-        <item name="topDivider">true</item>
         <item name="android:scrollbarStyle">outsideOverlay</item>
     </style>
 
     <style name="Widget.ListItem">
         <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
         <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:paddingLeft">12dip</item>
@@ -224,17 +220,17 @@
     <style name="Widget.HomeBanner"/>
 
     <style name="Widget.Home" />
 
     <style name="Widget.Home.HeaderItem">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">32dp</item>
         <item name="android:textAppearance">@style/TextAppearance.Widget.Home.Header</item>
-        <item name="android:background">#fff5f7f9</item>
+        <item name="android:background">@color/background_light</item>
         <item name="android:focusable">false</item>
         <item name="android:gravity">center|left</item>
         <item name="android:paddingLeft">10dip</item>
         <item name="android:paddingRight">10dip</item>
     </style>
 
     <style name="Widget.Home.PageButton">
         <item name="android:layout_width">match_parent</item>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -460,17 +460,17 @@ var BrowserApp = {
     // Broadcast a UIReady message so add-ons know we are finished with startup
     let event = document.createEvent("Events");
     event.initEvent("UIReady", true, false);
     window.dispatchEvent(event);
 
     if (this._startupStatus)
       this.onAppUpdated();
 
-    if (!ParentalControls.isAllowed(ParentalControls.INSTALL_EXTENSIONS)) {
+    if (!ParentalControls.isAllowed(ParentalControls.INSTALL_EXTENSION)) {
       // Disable extension installs
       Services.prefs.setIntPref("extensions.enabledScopes", 1);
       Services.prefs.setIntPref("extensions.autoDisableScopes", 1);
       Services.prefs.setBoolPref("xpinstall.enabled", false);
     }
 
     // notify java that gecko has loaded
     Messaging.sendRequest({ type: "Gecko:Ready" });
--- a/mobile/android/search/java/org/mozilla/search/SearchWidget.java
+++ b/mobile/android/search/java/org/mozilla/search/SearchWidget.java
@@ -97,18 +97,23 @@ public class SearchWidget extends AppWid
             context.startActivity(redirect);
         }
 
         super.onReceive(context, intent);
     }
 
     // Utility to create the view for this widget and attach any event listeners to it
     private void addView(final AppWidgetManager manager, final Context context, final int id, final Bundle options) {
-        final int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
-        final boolean isKeyguard = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+        final boolean isKeyguard;
+        if (options != null) {
+            final int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
+            isKeyguard = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+        } else {
+            isKeyguard = false;
+        }
 
         final RemoteViews views;
         if (isKeyguard) {
             views = new RemoteViews(context.getPackageName(), R.layout.keyguard_widget);
         } else {
             views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
             addClickIntent(context, views, R.id.search_button, ACTION_LAUNCH_SEARCH);
         }
--- a/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
+++ b/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
@@ -6,31 +6,33 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIInterfaceRequestor;
 interface nsIArray;
 
-[scriptable, uuid(4bde6754-406a-45d1-b18e-dc685adc1db4)]
+[scriptable, uuid(e7bcc22c-e9fc-4e7d-88b9-7482399b322d)]
 interface nsIParentalControlsService : nsISupports
 {
   /**
    * Action types that can be blocked for users.
    */
   const short DOWNLOAD = 1; // Downloading files
   const short INSTALL_EXTENSION = 2; // Installing extensions
   const short INSTALL_APP = 3; // Installing webapps
   const short VISIT_FILE_URLS = 4; // Opening file:/// urls
   const short SHARE = 5; // Sharing
   const short BOOKMARK = 6; // Creating bookmarks
   const short ADD_CONTACT = 7; // Add contacts to the system database
   const short SET_IMAGE = 8; // Setting images as wall paper
   const short MODIFY_ACCOUNTS = 9; // Modifying system accounts
+  const short REMOTE_DEBUGGING = 10; // Remote debugging
+  const short IMPORT_SETTINGS = 11; // Importing settings from other apps
 
   /**
    * @returns true if the current user account has parental controls
    * restrictions enabled.
    */ 
   readonly attribute boolean parentalControlsEnabled;
 
   /**