merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 31 Jul 2015 11:59:12 +0200
changeset 287168 ca53d4297f02919e8b4b79f7571d339d371c52db
parent 287148 bbb30fa23b215300aee16e68c3c5dc0e61c2c9ed (current diff)
parent 287167 a25357fa76f9fd637c9cb521b3fcff36c2f969b1 (diff)
child 287257 e9389ca320fff24c030806e310ff8dfedef85cbe
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.0a1
first release with
nightly linux32
ca53d4297f02 / 42.0a1 / 20150731030206 / files
nightly linux64
ca53d4297f02 / 42.0a1 / 20150731030206 / files
nightly mac
ca53d4297f02 / 42.0a1 / 20150731030206 / files
nightly win32
ca53d4297f02 / 42.0a1 / 20150731030206 / files
nightly win64
ca53d4297f02 / 42.0a1 / 20150731030206 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
mobile/android/base/RestrictionProvider.java
--- a/browser/devtools/styleinspector/cssruleview.xhtml
+++ b/browser/devtools/styleinspector/cssruleview.xhtml
@@ -43,20 +43,20 @@
           <input id="ruleview-searchbox"
                  class="devtools-searchinput devtools-rule-searchbox"
                  type="search" placeholder="&filterStylesPlaceholder;"/>
           <button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></button>
         </div>
         <button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></button>
         <button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></button>
       </div>
-      <div id="pseudo-class-panel" class="devtools-toolbar" hidden="true">
-        <label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" />:hover</label>
-        <label><input id="pseudo-active-toggle" type="checkbox" value=":active" />:active</label>
-        <label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" />:focus</label>
+      <div id="pseudo-class-panel" class="devtools-toolbar" hidden="true" tabindex="-1">
+        <label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</label>
+        <label><input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</label>
+        <label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</label>
       </div>
     </div>
 
     <div id="ruleview-container" class="ruleview devtools-monospace">
     </div>
 
   </body>
 </html>
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -2293,19 +2293,26 @@ CssRuleView.prototype = {
 
   /**
    * Called when the pseudo class panel button is clicked and toggles
    * the display of the pseudo class panel.
    */
   _onTogglePseudoClassPanel: function() {
     if (this.pseudoClassPanel.hidden) {
       this.pseudoClassToggle.setAttribute("checked", "true");
+      this.hoverCheckbox.setAttribute("tabindex", "0");
+      this.activeCheckbox.setAttribute("tabindex", "0");
+      this.focusCheckbox.setAttribute("tabindex", "0");
     } else {
       this.pseudoClassToggle.removeAttribute("checked");
+      this.hoverCheckbox.setAttribute("tabindex", "-1");
+      this.activeCheckbox.setAttribute("tabindex", "-1");
+      this.focusCheckbox.setAttribute("tabindex", "-1");
     }
+
     this.pseudoClassPanel.hidden = !this.pseudoClassPanel.hidden;
   },
 
   /**
    * Called when a pseudo class checkbox is clicked and toggles
    * the pseudo class for the current selected element.
    */
   _onTogglePseudoClass: function(event) {
--- a/browser/devtools/styleinspector/test/browser_ruleview_pseudo_lock_options.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudo_lock_options.js
@@ -24,81 +24,112 @@ let TEST_URI = [
   "<div>test div</div>"
 ].join("\n");
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("div", inspector);
 
+  yield assertPseudoPanelClosed(view);
+
   info("Toggle the pseudo class panel open");
-  ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
   view.pseudoClassToggle.click();
-  ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
-  ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
-  ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
-  ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
+  yield assertPseudoPanelOpened(view);
 
   info("Toggle each pseudo lock and check that the pseudo lock is added");
-  yield togglePseudoClass(inspector, view, view.hoverCheckbox);
+  yield togglePseudoClass(inspector, view.hoverCheckbox);
   yield assertPseudoAdded(inspector, view, ":hover", 3, 1);
-  yield togglePseudoClass(inspector, view, view.hoverCheckbox);
+  yield togglePseudoClass(inspector, view.hoverCheckbox);
   yield assertPseudoRemoved(inspector, view, 2);
 
-  yield togglePseudoClass(inspector, view, view.activeCheckbox);
+  yield togglePseudoClass(inspector, view.activeCheckbox);
   yield assertPseudoAdded(inspector, view, ":active", 3, 1);
-  yield togglePseudoClass(inspector, view, view.activeCheckbox);
+  yield togglePseudoClass(inspector, view.activeCheckbox);
   yield assertPseudoRemoved(inspector, view, 2);
 
-  yield togglePseudoClass(inspector, view, view.focusCheckbox);
+  yield togglePseudoClass(inspector, view.focusCheckbox);
   yield assertPseudoAdded(inspector, view, ":focus", 3, 1);
-  yield togglePseudoClass(inspector, view, view.focusCheckbox);
+  yield togglePseudoClass(inspector, view.focusCheckbox);
   yield assertPseudoRemoved(inspector, view, 2);
 
   info("Toggle all pseudo lock and check that the pseudo lock is added");
-  yield togglePseudoClass(inspector, view, view.hoverCheckbox);
-  yield togglePseudoClass(inspector, view, view.activeCheckbox);
-  yield togglePseudoClass(inspector, view, view.focusCheckbox);
+  yield togglePseudoClass(inspector, view.hoverCheckbox);
+  yield togglePseudoClass(inspector, view.activeCheckbox);
+  yield togglePseudoClass(inspector, view.focusCheckbox);
   yield assertPseudoAdded(inspector, view, ":focus", 5, 1);
   yield assertPseudoAdded(inspector, view, ":active", 5, 2);
   yield assertPseudoAdded(inspector, view, ":hover", 5, 3);
-  yield togglePseudoClass(inspector, view, view.hoverCheckbox);
-  yield togglePseudoClass(inspector, view, view.activeCheckbox);
-  yield togglePseudoClass(inspector, view, view.focusCheckbox);
+  yield togglePseudoClass(inspector, view.hoverCheckbox);
+  yield togglePseudoClass(inspector, view.activeCheckbox);
+  yield togglePseudoClass(inspector, view.focusCheckbox);
   yield assertPseudoRemoved(inspector, view, 2);
 
   info("Select a null element");
   yield view.selectElement(null);
   ok(!view.hoverCheckbox.checked && view.hoverCheckbox.disabled,
     ":hover checkbox is unchecked and disabled");
   ok(!view.activeCheckbox.checked && view.activeCheckbox.disabled,
     ":active checkbox is unchecked and disabled");
   ok(!view.focusCheckbox.checked && view.focusCheckbox.disabled,
     ":focus checkbox is unchecked and disabled");
 
   info("Toggle the pseudo class panel close");
   view.pseudoClassToggle.click();
-  ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Closed");
+  yield assertPseudoPanelClosed(view);
 });
 
-function* togglePseudoClass(inspector, ruleView, pseudoClassOption) {
+function* togglePseudoClass(inspector, pseudoClassOption) {
   info("Toggle the pseudoclass, wait for it to be applied");
   let onRefresh = inspector.once("rule-view-refreshed");
   pseudoClassOption.click();
   yield onRefresh;
 }
 
-function* assertPseudoAdded(inspector, ruleView, pseudoClass, numRules,
+function* assertPseudoAdded(inspector, view, pseudoClass, numRules,
     childIndex) {
   info("Check that the ruleview contains the pseudo-class rule");
-  is(ruleView.element.children.length, numRules,
+  is(view.element.children.length, numRules,
     "Should have " + numRules + " rules.");
-  is(getRuleViewRuleEditor(ruleView, childIndex).rule.selectorText,
+  is(getRuleViewRuleEditor(view, childIndex).rule.selectorText,
     "div" + pseudoClass, "rule view is showing " + pseudoClass + " rule");
 }
 
-function* assertPseudoRemoved(inspector, ruleView, numRules) {
+function* assertPseudoRemoved(inspector, view, numRules) {
   info("Check that the ruleview no longer contains the pseudo-class rule");
-  is(ruleView.element.children.length, numRules,
+  is(view.element.children.length, numRules,
     "Should have " + numRules + " rules.");
-  is(getRuleViewRuleEditor(ruleView, 1).rule.selectorText, "div",
+  is(getRuleViewRuleEditor(view, 1).rule.selectorText, "div",
     "Second rule is div");
 }
+
+function* assertPseudoPanelOpened(view) {
+  info("Check the opened state of the pseudo class panel");
+
+  ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
+  ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
+  ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
+  ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
+
+  is(view.pseudoClassPanel.getAttribute("tabindex"), "-1",
+    "Pseudo Class Panel has a tabindex of -1");
+  is(view.hoverCheckbox.getAttribute("tabindex"), "0",
+    ":hover checkbox has a tabindex of 0");
+  is(view.activeCheckbox.getAttribute("tabindex"), "0",
+    ":active checkbox has a tabindex of 0");
+  is(view.focusCheckbox.getAttribute("tabindex"), "0",
+    ":focus checkbox has a tabindex of 0");
+}
+
+function* assertPseudoPanelClosed(view) {
+  info("Check the closed state of the pseudo clas panel");
+
+  ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
+
+  is(view.pseudoClassPanel.getAttribute("tabindex"), "-1",
+    "Pseudo Class Panel has a tabindex of -1");
+  is(view.hoverCheckbox.getAttribute("tabindex"), "-1",
+    ":hover checkbox has a tabindex of -1");
+  is(view.activeCheckbox.getAttribute("tabindex"), "-1",
+    ":active checkbox has a tabindex of -1");
+  is(view.focusCheckbox.getAttribute("tabindex"), "-1",
+    ":focus checkbox has a tabindex of -1");
+}
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -304,17 +304,17 @@
 
         <receiver android:name="org.mozilla.gecko.webapp.TaskKiller">
           <intent-filter>
              <action android:name="org.mozilla.webapp.TASK_REMOVED" />
              <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
         </receiver>
 
-        <receiver android:name="org.mozilla.gecko.RestrictionProvider">
+        <receiver android:name="org.mozilla.gecko.restrictions.RestrictionProvider">
           <intent-filter>
             <action android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
           </intent-filter>
         </receiver>
 
         <!-- Activity used for launching non-privileged WebApps via a URL -->
         <activity android:name="org.mozilla.gecko.Webapp"
                   android:label="@string/webapp_generic_name"
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -50,16 +50,17 @@ import org.mozilla.gecko.menu.GeckoMenuI
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.tabqueue.TabQueuePrompt;
 import org.mozilla.gecko.tabs.TabHistoryController;
 import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
 import org.mozilla.gecko.tabs.TabHistoryFragment;
@@ -1970,17 +1971,17 @@ public class BrowserApp extends GeckoApp
                                 getSupportFragmentManager().beginTransaction().disallowAddToBackStack().add(mpm, tag).commit();
                             }
                         } catch (Exception ex) {
                             Log.e(LOGTAG, "Error initializing media manager", ex);
                         }
                     }
                 }
 
-                if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
+                if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED && RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_LOCATION_SERVICE)) {
                     // Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
                     // Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
                     // Avoid any potential startup CPU/thread contention by delaying the pref broadcast.
                     final long oneSecondInMillis = 1000;
                     ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
                         @Override
                         public void run() {
                              GeckoPreferences.broadcastStumblerPref(BrowserApp.this);
@@ -2629,16 +2630,20 @@ public class BrowserApp extends GeckoApp
                 break;
 
             default:
                 super.onActivityResult(requestCode, resultCode, data);
         }
     }
 
     private void showFirstrunPager() {
+        // Do not show first run if we're in an Android Restricted Profile
+        if (RestrictedProfiles.isUserRestricted(this)) {
+            return;
+        }
         if (mFirstrunPane == null) {
             final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
             mFirstrunPane = (FirstrunPane) firstrunPagerStub.inflate();
             mFirstrunPane.load(getSupportFragmentManager());
             mFirstrunPane.registerOnFinishListener(new FirstrunPane.OnFinishListener() {
                 @Override
                 public void onFinish() {
                     BrowserApp.this.mFirstrunPane = null;
@@ -2685,17 +2690,17 @@ public class BrowserApp extends GeckoApp
                     final Tab currentTab = Tabs.getInstance().getSelectedTab();
                     if (currentTab != null) {
                         currentTab.setMostRecentHomePanel(panelId);
                     }
                 }
             });
 
             // Don't show the banner in guest mode.
-            if (!getProfile().inGuestMode()) {
+            if (!RestrictedProfiles.isUserRestricted()) {
                 final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
                 final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
                 mHomePager.setBanner(homeBanner);
 
                 // Remove the banner from the view hierarchy if it is dismissed.
                 homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
                     @Override
                     public void onDismiss() {
@@ -3281,22 +3286,22 @@ public class BrowserApp extends GeckoApp
         if (AboutPages.isAboutReader(url)) {
             String urlFromReader = ReaderModeUtils.getUrlFromAboutReader(url);
             if (urlFromReader != null) {
                 url = urlFromReader;
             }
         }
 
         // Disable share menuitem for about:, chrome:, file:, and resource: URIs
-        final boolean shareVisible = RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_SHARE);
+        final boolean shareVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_SHARE);
         share.setVisible(shareVisible);
         final boolean shareEnabled = StringUtils.isShareableUrl(url) && shareVisible;
         share.setEnabled(shareEnabled);
-        MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_INSTALL_EXTENSION));
-        MenuUtils.safeSetEnabled(aMenu, R.id.downloads, RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_DOWNLOADS));
+        MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_INSTALL_EXTENSION));
+        MenuUtils.safeSetEnabled(aMenu, R.id.downloads, RestrictedProfiles.isAllowed(this, 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());
         MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
@@ -3351,19 +3356,22 @@ public class BrowserApp extends GeckoApp
                             shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
                         }
                     }
                 }
             }
         }
 
         // Hide tools menu if restriction is active
-        final boolean toolsVisible = RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_TOOLS_MENU);
+        final boolean toolsVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_TOOLS_MENU);
         MenuUtils.safeSetVisible(aMenu, R.id.tools, toolsVisible);
 
+        final boolean privateTabVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING);
+        MenuUtils.safeSetVisible(aMenu, R.id.new_private_tab, privateTabVisible);
+
         // Disable save as PDF for about:home and xul pages.
         saveAsPDF.setEnabled(!(isAboutHome(tab) ||
                                tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
                                tab.getContentType().startsWith("video/")));
 
         // Disable find in page for about:home, since it won't work on Java content.
         findInPage.setEnabled(!isAboutHome(tab));
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1204,22 +1204,16 @@ public class GeckoAppShell
                                    final String action,
                                    final String title) {
 
         // The resultant chooser can return non-exported activities in 4.1 and earlier.
         // https://code.google.com/p/android/issues/detail?id=29535
         final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
 
         if (intent != null) {
-            // Setting category on file:// URIs breaks about:downloads (Bug 1176018)
-            if (!targetURI.startsWith("file:")) {
-                // Only handle applications which can accept arbitrary data from a browser.
-                intent.addCategory(Intent.CATEGORY_BROWSABLE);
-            }
-
             // Some applications use this field to return to the same browser after processing the
             // Intent. While there is some danger (e.g. denial of service), other major browsers already
             // use it and so it's the norm.
             intent.putExtra(Browser.EXTRA_APPLICATION_ID, AppConstants.ANDROID_PACKAGE_NAME);
         }
 
         return intent;
     }
@@ -1240,25 +1234,28 @@ public class GeckoAppShell
             return intent;
         }
 
         if (!isUriSafeForScheme(uri)) {
             return null;
         }
 
         final String scheme = uri.getScheme();
-        if ("intent".equals(scheme)) {
+        if ("intent".equals(scheme) || "android-app".equals(scheme)) {
             final Intent intent;
             try {
-                intent = Intent.parseUri(targetURI, Intent.URI_INTENT_SCHEME);
+                intent = Intent.parseUri(targetURI, 0);
             } catch (final URISyntaxException e) {
                 Log.e(LOGTAG, "Unable to parse URI - " + e);
                 return null;
             }
 
+            // Only open applications which can accept arbitrary data from a browser.
+            intent.addCategory(Intent.CATEGORY_BROWSABLE);
+
             // Prevent site from explicitly opening our internal activities, which can leak data.
             intent.setComponent(null);
             nullIntentSelector(intent);
 
             return intent;
         }
 
         // Compute our most likely intent, then check to see if there are any
--- a/mobile/android/base/RestrictedProfiles.java
+++ b/mobile/android/base/RestrictedProfiles.java
@@ -1,322 +1,124 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.restrictions.DefaultConfiguration;
+import org.mozilla.gecko.restrictions.GuestProfileConfiguration;
+import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
+import org.mozilla.gecko.restrictions.Restriction;
+import org.mozilla.gecko.restrictions.RestrictionConfiguration;
+
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.Log;
 
 @RobocopTarget
 public class RestrictedProfiles {
     private static final String LOGTAG = "GeckoRestrictedProfiles";
 
-    private static volatile Boolean inGuest = null;
-
-    @SuppressWarnings("serial")
-    private static final List<String> BANNED_SCHEMES = new ArrayList<String>() {{
-        add("file");
-        add("chrome");
-        add("resource");
-        add("jar");
-        add("wyciwyg");
-    }};
-
-    private static final String ABOUT_ADDONS = "about:addons";
+    private static RestrictionConfiguration configuration;
 
-    /**
-     * This is a hack to allow non-GeckoApp activities to safely call into
-     * RestrictedProfiles without reworking this class or GeckoProfile.
-     *
-     * It can be removed after Bug 1077590 lands.
-     */
-    public static void initWithProfile(GeckoProfile profile) {
-        inGuest = profile.inGuestMode();
-    }
-
-    private static boolean getInGuest() {
-        if (inGuest == null) {
-            inGuest = GeckoAppShell.getGeckoInterface().getProfile().inGuestMode();
+    private static RestrictionConfiguration getConfiguration(Context context) {
+        if (configuration == null) {
+            configuration = createConfiguration(context);
         }
 
-        return inGuest;
+        return configuration;
     }
 
-    @SuppressWarnings("serial")
-    private static final List<String> BANNED_URLS = new ArrayList<String>() {{
-        add("about:config");
-    }};
-
-    /* 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 enum Restriction {
-        // These restrictions have no strings assigned because they are only used in guest mode and not shown in the
-        // restricted profiles settings UI
-        DISALLOW_DOWNLOADS(1, "no_download_files", 0, 0),
-        DISALLOW_BROWSE_FILES(4, "no_browse_files", 0, 0),
-        DISALLOW_SHARE(5, "no_share", 0, 0),
-        DISALLOW_BOOKMARK(6, "no_bookmark", 0, 0),
-        DISALLOW_ADD_CONTACTS(7, "no_add_contacts", 0, 0),
-        DISALLOW_SET_IMAGE(8, "no_set_image", 0, 0),
-        DISALLOW_MODIFY_ACCOUNTS(9, "no_modify_accounts", 0, 0), // UserManager.DISALLOW_MODIFY_ACCOUNTS
-        DISALLOW_REMOTE_DEBUGGING(10, "no_remote_debugging", 0, 0),
-
-        // These restrictions are used for restricted profiles and therefore need to have strings assigned for the profile
-        // settings UI.
-        DISALLOW_INSTALL_EXTENSION(2, "no_install_extensions", R.string.restriction_disallow_addons_title, R.string.restriction_disallow_addons_description),
-        DISALLOW_INSTALL_APPS(3, "no_install_apps", R.string.restriction_disallow_apps_title, R.string.restriction_disallow_apps_description), // UserManager.DISALLOW_INSTALL_APPS
-        DISALLOW_IMPORT_SETTINGS(11, "no_report_site_issue", R.string.restriction_disallow_import_settings_title, R.string.restriction_disallow_import_settings_description),
-        DISALLOW_TOOLS_MENU(12, "no_tools_menu", R.string.restriction_disallow_tools_menu_title, R.string.restriction_disallow_tools_menu_description),
-        DISALLOW_REPORT_SITE_ISSUE(13, "no_report_site_issue", R.string.restriction_disallow_report_site_issue_title, R.string.restriction_disallow_report_site_issue_description);
-
-        public final int id;
-        public final String name;
-        public final int titleResource;
-        public final int descriptionResource;
-
-        Restriction(final int id, final String name, int titleResource, int descriptionResource) {
-            this.id = id;
-            this.name = name;
-            this.titleResource = titleResource;
-            this.descriptionResource = descriptionResource;
+    public static synchronized RestrictionConfiguration createConfiguration(Context context) {
+        if (configuration != null) {
+            // This method is synchronized and another thread might already have created the configuration.
+            return configuration;
         }
 
-        public String getTitle(Context context) {
-            if (titleResource == 0) {
-                return toString();
-            }
-
-            return context.getResources().getString(titleResource);
-        }
-
-        public String getDescription(Context context) {
-            if (descriptionResource == 0) {
-                return name;
-            }
-
-            return context.getResources().getString(descriptionResource);
+        if (isGuestProfile()) {
+            return new GuestProfileConfiguration();
+        } else if(isRestrictedProfile(context)) {
+            return new RestrictedProfileConfiguration(context);
+        } else {
+            return new DefaultConfiguration();
         }
     }
 
-    static List<Restriction> GUEST_RESTRICTIONS = Arrays.asList(
-        Restriction.DISALLOW_DOWNLOADS,
-        Restriction.DISALLOW_INSTALL_EXTENSION,
-        Restriction.DISALLOW_INSTALL_APPS,
-        Restriction.DISALLOW_BROWSE_FILES,
-        Restriction.DISALLOW_SHARE,
-        Restriction.DISALLOW_BOOKMARK,
-        Restriction.DISALLOW_ADD_CONTACTS,
-        Restriction.DISALLOW_SET_IMAGE,
-        Restriction.DISALLOW_MODIFY_ACCOUNTS,
-        Restriction.DISALLOW_REMOTE_DEBUGGING,
-        Restriction.DISALLOW_IMPORT_SETTINGS
-    );
+    private static boolean isGuestProfile() {
+        return GeckoAppShell.getGeckoInterface().getProfile().inGuestMode();
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+    private static boolean isRestrictedProfile(Context context) {
+        if (Versions.preJBMR2) {
+            // Early versions don't support restrictions at all
+            return false;
+        }
 
-    // Restricted profiles will automatically have these restrictions by default
-    static List<Restriction> RESTRICTED_PROFILE_RESTRICTIONS = Arrays.asList(
-        Restriction.DISALLOW_INSTALL_EXTENSION,
-        Restriction.DISALLOW_INSTALL_APPS,
-        Restriction.DISALLOW_TOOLS_MENU,
-        Restriction.DISALLOW_REPORT_SITE_ISSUE,
-        Restriction.DISALLOW_IMPORT_SETTINGS
-    );
+        final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        Bundle restrictions = mgr.getApplicationRestrictions(context.getPackageName());
+
+        for (String key : restrictions.keySet()) {
+            if (restrictions.getBoolean(key)) {
+                // At least one restriction is enabled -> We are a restricted profile
+                return true;
+            }
+        }
+
+        return false;
+    }
 
     private static Restriction geckoActionToRestriction(int action) {
         for (Restriction rest : Restriction.values()) {
             if (rest.id == action) {
                 return rest;
             }
         }
 
         throw new IllegalArgumentException("Unknown action " + action);
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-    private static Bundle getRestrictions(final Context context) {
-        final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        return mgr.getUserRestrictions();
-    }
-
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-    private static Bundle getAppRestrictions(final Context context) {
-        final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        return mgr.getApplicationRestrictions(context.getPackageName());
-    }
-
-    /**
-     * This method does the system version check for you.
-     *
-     * Returns false if the system doesn't support restrictions,
-     * or the provided value is not present in the set of user
-     * restrictions.
-     *
-     * Returns true otherwise.
-     */
-    private static boolean getRestriction(final Context context, final Restriction restriction) {
-        // Early versions don't support restrictions at all,
-        // so no action can be restricted.
-        if (Versions.preJBMR2) {
-            return false;
-        }
-
-        if (!isUserRestricted(context)) {
-            return false;
-        }
-
-        return getAppRestrictions(context).getBoolean(restriction.name, RESTRICTED_PROFILE_RESTRICTIONS.contains(restriction));
-    }
-
     private static boolean canLoadUrl(final Context context, final String url) {
-        // Null URLs are always permitted.
-        if (url == null) {
-            return true;
-        }
-
-        try {
-            // If we're not in guest mode, and the system restriction isn't in place, everything is allowed.
-            if (!getInGuest() &&
-                !getRestriction(context, Restriction.DISALLOW_BROWSE_FILES)) {
-                return true;
-            }
-        } catch (IllegalArgumentException ex) {
-            Log.i(LOGTAG, "Invalid action", ex);
-        }
-
-        final Uri u = Uri.parse(url);
-        final String scheme = u.getScheme();
-        if (BANNED_SCHEMES.contains(scheme)) {
-            return false;
-        }
-
-        for (String banned : BANNED_URLS) {
-            if (url.startsWith(banned)) {
-                return false;
-            }
-        }
-
-        // TODO: The UserManager should support blacklisting URLs by the device owner.
-        return true;
+        return getConfiguration(context).canLoadUrl(url);
     }
 
     @WrapElementForJNI
     public static boolean isUserRestricted() {
         return isUserRestricted(GeckoAppShell.getContext());
     }
 
     public static boolean isUserRestricted(final Context context) {
-        // Guest mode is supported in all Android versions.
-        if (getInGuest()) {
-            return true;
-        }
-
-        if (Versions.preJBMR2) {
-            return false;
-        }
-
-        Bundle restrictions = getRestrictions(context);
-        for (String key : restrictions.keySet()) {
-            if (restrictions.getBoolean(key)) {
-                // At least one restriction is enabled -> We are a restricted profile
-                return true;
-            }
-        }
-
-        return false;
+        return getConfiguration(context).isRestricted();
     }
 
-    public static boolean isAllowed(final Context context, final Restriction action) {
-        return isAllowed(context, action, null);
+    public static boolean isAllowed(final Context context, final Restriction restriction) {
+        return getConfiguration(context).isAllowed(restriction);
     }
 
     @WrapElementForJNI
     public static boolean isAllowed(int action, String url) {
-        return isAllowed(GeckoAppShell.getContext(), action, url);
-    }
-
-    private static boolean isAllowed(final Context context, int action, String url) {
         final Restriction restriction;
         try {
             restriction = geckoActionToRestriction(action);
         } catch (IllegalArgumentException ex) {
             // Unknown actions represent a coding error, so we
             // refuse the action and log.
             Log.e(LOGTAG, "Unknown action " + action + "; check calling code.");
             return false;
         }
 
-        return isAllowed(context, restriction, url);
-    }
-
-    private static boolean isAllowed(final Context context, final Restriction restriction, String url) {
-        if (getInGuest()) {
-            if (Restriction.DISALLOW_BROWSE_FILES == restriction) {
-                return canLoadUrl(context, url);
-            }
-
-            return !GUEST_RESTRICTIONS.contains(restriction);
-        }
-
-        // Disallow browsing about:addons if 'disallow install extension' restriction is enforced
-        if (restriction == Restriction.DISALLOW_BROWSE_FILES
-            && url.toLowerCase().startsWith(ABOUT_ADDONS)
-            && !isAllowed(context, Restriction.DISALLOW_INSTALL_EXTENSION)) {
-            return false;
-        }
-
-        // NOTE: Restrictions hold the opposite intention, so we need to flip it.
-        return !getRestriction(context, restriction);
-    }
-
-    @WrapElementForJNI
-    public static String getUserRestrictions() {
-        return getUserRestrictions(GeckoAppShell.getContext());
-    }
+        final Context context = GeckoAppShell.getContext();
 
-    private static String getUserRestrictions(final Context context) {
-        // Guest mode is supported in all Android versions
-        if (getInGuest()) {
-            StringBuilder builder = new StringBuilder("{ ");
-
-            for (Restriction restriction : Restriction.values()) {
-                builder.append("\"" + restriction.name + "\": true, ");
-            }
-
-            builder.append(" }");
-            return builder.toString();
+        if (Restriction.DISALLOW_BROWSE_FILES == restriction) {
+            return canLoadUrl(context, url);
+        } else {
+            return isAllowed(context, restriction);
         }
-
-        if (Versions.preJBMR2) {
-            return "{}";
-        }
-
-        final JSONObject json = new JSONObject();
-        final Bundle restrictions = getRestrictions(context);
-        final Set<String> keys = restrictions.keySet();
-
-        for (String key : keys) {
-            try {
-                json.put(key, restrictions.get(key));
-            } catch (JSONException e) {
-            }
-        }
-
-        return json.toString();
     }
 }
deleted file mode 100644
--- a/mobile/android/base/RestrictionProvider.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.RestrictionEntry;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Broadcast receiver providing supported restrictions to the system.
- */
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-public class RestrictionProvider extends BroadcastReceiver {
-    @Override
-    public void onReceive(final Context context, final Intent intent) {
-        if (AppConstants.Versions.preJBMR2) {
-            // This broadcast does not make any sense prior to Jelly Bean MR2.
-            return;
-        }
-
-        final PendingResult result = goAsync();
-
-        new Thread() {
-            @Override
-            public void run() {
-                final Bundle oldRestrictions = intent.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
-                final Bundle extras = new Bundle();
-
-                ArrayList<RestrictionEntry> entries = initRestrictions(context, oldRestrictions);
-                extras.putParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST, entries);
-
-                result.setResult(Activity.RESULT_OK, null, extras);
-                result.finish();
-            }
-        }.start();
-    }
-
-    private ArrayList<RestrictionEntry> initRestrictions(Context context, Bundle oldRestrictions) {
-        ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
-
-        for (RestrictedProfiles.Restriction restriction : RestrictedProfiles.RESTRICTED_PROFILE_RESTRICTIONS) {
-            RestrictionEntry entry = createRestrictionEntryWithDefaultValue(context, restriction,
-                    oldRestrictions.getBoolean(restriction.name, true));
-            entries.add(entry);
-        }
-
-        return entries;
-    }
-
-    private RestrictionEntry createRestrictionEntryWithDefaultValue(Context context, RestrictedProfiles.Restriction restriction, boolean defaultValue) {
-        RestrictionEntry entry = new RestrictionEntry(restriction.name, defaultValue);
-
-        entry.setTitle(restriction.getTitle(context));
-        entry.setDescription(restriction.getDescription(context));
-
-        return entry;
-    }
-}
--- a/mobile/android/base/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/home/HomeConfigPrefsBackend.java
@@ -16,16 +16,17 @@ import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
 import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelType;
 import org.mozilla.gecko.home.HomeConfig.State;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.support.v4.content.LocalBroadcastManager;
@@ -75,17 +76,17 @@ class HomeConfigPrefsBackend implements 
         panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
         panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
 
         final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
         final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
 
         // We disable Synced Tabs for guest mode profiles.
         final PanelConfig remoteTabsEntry;
-        if (RestrictedProfiles.isAllowed(mContext, RestrictedProfiles.Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
+        if (RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
             remoteTabsEntry = createBuiltinPanelConfig(mContext, PanelType.REMOTE_TABS);
         } else {
             remoteTabsEntry = null;
         }
 
         panelConfigs.add(historyEntry);
         panelConfigs.add(recentTabsEntry);
         if (remoteTabsEntry != null) {
--- a/mobile/android/base/home/HomeFragment.java
+++ b/mobile/android/base/home/HomeFragment.java
@@ -2,33 +2,33 @@
  * 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.home;
 
 import java.util.EnumSet;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ReaderModeUtils;
+import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UIAsyncTask;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -146,16 +146,20 @@ public abstract class HomeFragment exten
         // Hide the "Remove" menuitem if this item not removable.
         if (!info.canRemove()) {
             menu.findItem(R.id.home_remove).setVisible(false);
         }
 
         if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
             menu.findItem(R.id.home_share).setVisible(false);
         }
+
+        if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_PRIVATE_BROWSING)) {
+            menu.findItem(R.id.home_open_private_tab).setVisible(false);
+        }
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         // onContextItemSelected() is first dispatched to the activity and
         // then dispatched to its fragments. Since fragments cannot "override"
         // menu item selection handling, it's better to avoid menu id collisions
         // between the activity and its fragments.
--- a/mobile/android/base/home/RemoteTabsSplitPlaneFragment.java
+++ b/mobile/android/base/home/RemoteTabsSplitPlaneFragment.java
@@ -1,18 +1,20 @@
 package org.mozilla.gecko.home;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.database.DataSetObserver;
 import android.os.Bundle;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.ListAdapter;
 import android.widget.TextView;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.RemoteClientsDialogFragment;
@@ -192,16 +194,20 @@ public class RemoteTabsSplitPlaneFragmen
         mClientList.setAdapter(mClientsAdapter);
 
         mObserver = new RemoteTabDataSetObserver();
         mAdapter.registerDataSetObserver(mObserver);
 
         // Now the adapter is wrapped; we can remove our footer view.
         mClientList.removeFooterView(mFooterView);
 
+        // Register touch handler to conditionally enable swipe refresh layout.
+        mClientList.setOnTouchListener(new ListTouchListener(mClientList));
+        mTabList.setOnTouchListener(new ListTouchListener(mTabList));
+
         // Create callbacks before the initial loader is started
         mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
     protected void updateUiFromClients(List<RemoteClient> clients, List<RemoteClient> hiddenClients) {
         if (getView() == null) {
@@ -357,9 +363,40 @@ public class RemoteTabsSplitPlaneFragmen
 
             // Update the background based on the state of the selected client.
             final RemoteClient client = getItem(position);
             final boolean isSelected = client.guid.equals(sState.selectedClient);
             adapter.updateClientsItemView(isSelected, context, view, getItem(position));
             return view;
         }
     }
+
+    /**
+     * OnTouchListener implementation for ListView that enables swipe to refresh on the touch down event iff list cannot scroll up.
+     * This implementation does not consume the <code>MotionEvent</code>.
+     */
+    private class ListTouchListener implements View.OnTouchListener {
+        private final AbsListView listView;
+
+        public ListTouchListener(AbsListView listView) {
+            this.listView = listView;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            final int action = event.getAction();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    // Enable swipe to refresh iff the first item is visible and is at the top.
+                    mRefreshLayout.setEnabled(listView.getCount() <= 0
+                    	    || (listView.getFirstVisiblePosition() <= 0 && listView.getChildAt(0).getTop() >= 0));
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    mRefreshLayout.setEnabled(true);
+                    break;
+            }
+
+            // Event is not handled here, it will be consumed in enclosing SwipeRefreshLayout.
+            return false;
+        }
+    }
 }
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -13,31 +13,33 @@ import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
 import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.tiles.TilesRecorder;
 import org.mozilla.gecko.tiles.Tile;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -364,16 +366,20 @@ public class TopSitesPanel extends HomeF
             menu.findItem(R.id.home_open_private_tab).setVisible(false);
             menu.findItem(R.id.top_sites_pin).setVisible(false);
             menu.findItem(R.id.top_sites_unpin).setVisible(false);
         }
 
         if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
             menu.findItem(R.id.home_share).setVisible(false);
         }
+
+        if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_PRIVATE_BROWSING)) {
+            menu.findItem(R.id.home_open_private_tab).setVisible(false);
+        }
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         if (super.onContextItemSelected(item)) {
             // HomeFragment was able to handle to selected item.
             return true;
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -688,9 +688,16 @@ just addresses the organization to follo
 <!ENTITY restriction_disallow_report_site_issue_title "Disallow \'Report site issue\'">
 <!ENTITY restriction_disallow_report_site_issue_description "Hide \'Report site issue\' menu item.">
 <!ENTITY restriction_disallow_import_settings_title "Disallow importing settings">
 <!ENTITY restriction_disallow_import_settings_description "Do not allow to import settings from other system browsers.">
 <!ENTITY restriction_disallow_addons_title "Disallow add-ons">
 <!ENTITY restriction_disallow_addons_description "Disallow installation of add-ons.">
 <!ENTITY restriction_disallow_apps_title "Disallow apps">
 <!ENTITY restriction_disallow_apps_description "Disallow installing apps from Firefox Marketplace.">
-
+<!ENTITY restriction_disallow_devtools_title "Disallow developer tools">
+<!ENTITY restriction_disallow_devtools_description "Disallow usage of developer tools.">
+<!ENTITY restriction_disallow_customize_home_title "Disallow customizing home">
+<!ENTITY restriction_disallow_customize_home_description "Disallow customizing home panels.">
+<!ENTITY restriction_disallow_private_browsing_title "Disallow private browsing">
+<!ENTITY restriction_disallow_private_browsing_description "Disallow private browsing mode.">
+<!ENTITY restriction_disallow_location_services_title "Disallow location services">
+<!ENTITY restriction_disallow_location_services_description "Disallow sharing of location data to improve geolocation service.">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -429,17 +429,22 @@ gbjar.sources += [
     'prompts/PromptService.java',
     'prompts/TabInput.java',
     'ReaderModeUtils.java',
     'ReadingListHelper.java',
     'RemoteClientsDialogFragment.java',
     'RemoteTabsExpandableListAdapter.java',
     'Restarter.java',
     'RestrictedProfiles.java',
-    'RestrictionProvider.java',
+    'restrictions/DefaultConfiguration.java',
+    'restrictions/GuestProfileConfiguration.java',
+    'restrictions/RestrictedProfileConfiguration.java',
+    'restrictions/Restriction.java',
+    'restrictions/RestrictionConfiguration.java',
+    'restrictions/RestrictionProvider.java',
     'ServiceNotificationClient.java',
     'SessionParser.java',
     'SharedPreferencesHelper.java',
     'SiteIdentity.java',
     'SmsManager.java',
     'sqlite/ByteBufferInputStream.java',
     'sqlite/MatrixBlobCursor.java',
     'sqlite/SQLiteBridge.java',
--- a/mobile/android/base/preferences/AndroidImportPreference.java
+++ b/mobile/android/base/preferences/AndroidImportPreference.java
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.RestrictedProfiles;
-import org.mozilla.gecko.RestrictedProfiles.Restriction;
+import org.mozilla.gecko.restrictions.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;
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 import android.os.Build;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
@@ -32,16 +33,17 @@ import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.InputOptionsUtils;
 import org.mozilla.gecko.widget.FloatingHintEditText;
 
@@ -125,16 +127,19 @@ OnSharedPreferenceChangeListener
     private static final String PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED = "devtools.remote.wifi.enabled";
     private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
     private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
     private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
     public static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
     public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
     public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
     private static final String PREFS_DEVTOOLS = NON_PREF_PREFIX + "devtools.enabled";
+    private static final String PREFS_CUSTOMIZE_HOME = NON_PREF_PREFIX + "customize_home";
+    private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
+    private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
 
     private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
 
 
     // This isn't a Gecko pref, even if it looks like one.
     private static final String PREFS_BROWSER_LOCALE = "locale";
 
     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
@@ -304,19 +309,16 @@ OnSharedPreferenceChangeListener
             return;
         }
 
         onLocaleChanged(currentLocale);
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        // Make sure RestrictedProfiles is ready.
-        RestrictedProfiles.initWithProfile(GeckoProfile.get(this));
-
         // Apply the current user-selected locale, if necessary.
         checkLocale();
 
         // Track this so we can decide whether to show locale options.
         // See also the workaround below for Bug 1015209.
         localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
 
         // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
@@ -472,25 +474,28 @@ OnSharedPreferenceChangeListener
         return GeckoPreferenceFragment.class.getName().equals(fragmentName);
     }
 
     @Override
     public void onBuildHeaders(List<Header> target) {
         if (onIsMultiPane()) {
             loadHeadersFromResource(R.xml.preference_headers, target);
 
-            // If locale switching is disabled, remove the section
-            // entirely. This logic will need to be extended when
-            // content language selection (Bug 881510) is implemented.
-            if (!localeSwitchingIsEnabled) {
-                for (Header header : target) {
-                    if (header.id == R.id.pref_header_language) {
-                        target.remove(header);
-                        break;
-                    }
+            Iterator<Header> iterator = target.iterator();
+
+            while (iterator.hasNext()) {
+                Header header = iterator.next();
+
+                if (header.id == R.id.pref_header_language && !localeSwitchingIsEnabled) {
+                    // If locale switching is disabled, remove the section
+                    // entirely. This logic will need to be extended when
+                    // content language selection (Bug 881510) is implemented.
+                    iterator.remove();
+                } else if (header.id == R.id.pref_header_devtools && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DEVELOPER_TOOLS)) {
+                    iterator.remove();
                 }
             }
         }
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         if (!hasFocus || mInitialized)
@@ -698,16 +703,24 @@ OnSharedPreferenceChangeListener
                 }
                 if (PREFS_DEVTOOLS.equals(key) &&
                     RestrictedProfiles.isUserRestricted(this)) {
                     preferences.removePreference(pref);
                     i--;
                     continue;
                 }
 
+                if (PREFS_CUSTOMIZE_HOME.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_CUSTOMIZE_HOME)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
+                }
+
                 setupPreferences((PreferenceGroup) pref, prefs);
             } else {
                 pref.setOnPreferenceChangeListener(this);
                 if (PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
                     if (!AppConstants.MOZ_UPDATER) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
@@ -722,17 +735,17 @@ OnSharedPreferenceChangeListener
                     // Remove UI for reflow on release builds.
                     if (AppConstants.RELEASE_BUILD) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_OPEN_URLS_IN_PRIVATE.equals(key)) {
                     // Remove UI for opening external links in private browsing on non-Nightly builds.
-                    if (!AppConstants.NIGHTLY_BUILD) {
+                    if (!AppConstants.NIGHTLY_BUILD || !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_TELEMETRY_ENABLED.equals(key)) {
                     if (!AppConstants.MOZ_TELEMETRY_REPORTING) {
                         preferences.removePreference(pref);
                         i--;
@@ -748,29 +761,29 @@ OnSharedPreferenceChangeListener
                 } else if (PREFS_CRASHREPORTER_ENABLED.equals(key)) {
                     if (!AppConstants.MOZ_CRASHREPORTER) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_GEO_REPORTING.equals(key) ||
                            PREFS_GEO_LEARN_MORE.equals(key)) {
-                    if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
+                    if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED || !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_LOCATION_SERVICE)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_DEVTOOLS_REMOTE_USB_ENABLED.equals(key)) {
-                    if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_REMOTE_DEBUGGING)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_REMOTE_DEBUGGING)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED.equals(key)) {
-                    if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_REMOTE_DEBUGGING)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_REMOTE_DEBUGGING)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                     if (!InputOptionsUtils.supportsQrCodeReader(getApplicationContext())) {
                         // WiFi debugging requires a QR code reader
                         pref.setEnabled(false);
                         pref.setSummary(getString(R.string.pref_developer_remotedebugging_wifi_disabled_summary));
@@ -783,17 +796,17 @@ OnSharedPreferenceChangeListener
                     // callback, but since this pref doesn't live in Gecko, we
                     // need to handle it separately.
                     ListPreference listPref = (ListPreference) pref;
                     CharSequence selectedEntry = listPref.getEntry();
                     listPref.setSummary(selectedEntry);
                     continue;
                 } else if (PREFS_SYNC.equals(key)) {
                     // Don't show sync prefs while in guest mode.
-                    if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) {
                     pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                         @Override
                         public boolean onPreferenceClick(Preference preference) {
@@ -832,16 +845,28 @@ OnSharedPreferenceChangeListener
                     }
                 } else if (PREFS_QRCODE_ENABLED.equals(key)) {
                     if (!InputOptionsUtils.supportsQrCodeReader(getApplicationContext())) {
                         // Remove UI for qr code input on non nightly builds
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
+                } else if (PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
+                } else if (PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
+                        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.
--- a/mobile/android/base/resources/layout-v11/new_tablet_tabs_item_cell.xml
+++ b/mobile/android/base/resources/layout-v11/new_tablet_tabs_item_cell.xml
@@ -44,22 +44,24 @@
                      android:baselineAlignBottom="true"
                      android:background="@android:color/transparent"
                      android:contentDescription="@string/close_tab"
                      android:src="@drawable/new_tablet_tab_item_close_button"
                      android:duplicateParentState="true"/>
 
     </LinearLayout>
 
-    <RelativeLayout android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
-                    android:background="@drawable/tab_thumbnail"
-                    android:duplicateParentState="true">
+    <org.mozilla.gecko.widget.TabThumbnailWrapper
+            android:id="@+id/wrapper"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
+            android:background="@drawable/tab_thumbnail"
+            android:duplicateParentState="true">
 
         <org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
                                                 android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
                                                 android:layout_height="@dimen/new_tablet_tab_thumbnail_height"
                                                 />
 
-    </RelativeLayout>
+    </org.mozilla.gecko.widget.TabThumbnailWrapper>
 
 </org.mozilla.gecko.tabs.TabsLayoutItemView>
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ b/mobile/android/base/resources/layout/home_pager.xml
@@ -13,11 +13,11 @@
                                   android:layout_height="match_parent"
                                   android:background="@android:color/white">
 
     <org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
                                          android:layout_height="@dimen/tabs_strip_height"
                                          android:background="@color/about_page_header_grey"
                                          android:layout_gravity="top"
                                          gecko:strip="@drawable/home_tab_menu_strip"
-                                         gecko:tabContentStart="72dp" />
+                                         gecko:tabContentStart="@dimen/tab_strip_content_start" />
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/home_remote_tabs_split_plane_panel.xml
+++ b/mobile/android/base/resources/layout/home_remote_tabs_split_plane_panel.xml
@@ -11,18 +11,17 @@
     <ViewStub android:id="@id/home_empty_view_stub"
               android:layout="@layout/home_empty_panel"
               android:layout_width="match_parent"
               android:layout_height="match_parent"/>
 
     <org.mozilla.gecko.widget.GeckoSwipeRefreshLayout
             android:id="@id/remote_tabs_refresh_layout"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:enabled="false">
+            android:layout_height="match_parent">
 
         <LinearLayout android:layout_width="match_parent"
                       android:layout_height="match_parent"
                       android:orientation="horizontal">
 
             <org.mozilla.gecko.home.HomeListView
                 android:id="@+id/clients_list"
                 style="@style/Widget.RemoteTabsListView"
--- a/mobile/android/base/resources/layout/tabs_item_cell.xml
+++ b/mobile/android/base/resources/layout/tabs_item_cell.xml
@@ -10,22 +10,24 @@
                                            android:layout_width="wrap_content"
                                            android:layout_height="wrap_content"
                                            android:paddingTop="6dip"
                                            android:paddingBottom="6dip"
                                            android:paddingLeft="1dip"
                                            android:paddingRight="1dip"
                                            android:gravity="center">
 
-    <RelativeLayout android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_margin="6dip"
-                    android:padding="4dip"
-                    android:background="@drawable/tab_thumbnail"
-                    android:duplicateParentState="true">
+    <org.mozilla.gecko.widget.TabThumbnailWrapper
+            android:id="@+id/wrapper"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="6dip"
+            android:padding="4dip"
+            android:background="@drawable/tab_thumbnail"
+            android:duplicateParentState="true">
 
         <org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
                                                 android:layout_width="@dimen/tab_thumbnail_width"
                                                 android:layout_height="@dimen/tab_thumbnail_height"/>
 
         <LinearLayout android:layout_width="@dimen/tab_thumbnail_width"
                       android:layout_height="wrap_content"
                       android:orientation="horizontal"
@@ -50,11 +52,11 @@
                          android:layout_height="match_parent"
                          android:background="@drawable/action_bar_button_inverse"
                          android:scaleType="center"
                          android:contentDescription="@string/close_tab"
                          android:src="@drawable/tab_close"/>
 
         </LinearLayout>
 
-    </RelativeLayout>
+    </org.mozilla.gecko.widget.TabThumbnailWrapper>
 
 </org.mozilla.gecko.tabs.TabsLayoutItemView>
--- a/mobile/android/base/resources/values-large-v11/dimens.xml
+++ b/mobile/android/base/resources/values-large-v11/dimens.xml
@@ -26,9 +26,11 @@
     <dimen name="reading_list_row_height">96dp</dimen>
     <dimen name="reading_list_row_padding_right">15dp</dimen>
 
     <dimen name="overlay_prompt_container_width">360dp</dimen>
 
     <!-- Should be closer to 0.83 (140/168) but various roundings mean that 0.9 works better -->
     <item name="thumbnail_aspect_ratio" format="float" type="dimen">0.9</item>
 
+    <item name="tab_strip_content_start" type="dimen">72dp</item>
+
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -210,9 +210,11 @@
 
     <!-- This is a 4:7 ratio (as per UX decision). -->
     <item name="thumbnail_aspect_ratio" format="float" type="dimen">0.571</item>
 
     <!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
     <item name="match_parent" type="dimen">-1</item>
     <item name="wrap_content" type="dimen">-2</item>
 
+    <item name="tab_strip_content_start" type="dimen">12dp</item>
+
 </resources>
--- a/mobile/android/base/resources/xml-v11/preference_headers.xml
+++ b/mobile/android/base/resources/xml-v11/preference_headers.xml
@@ -36,14 +36,15 @@
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:title="@string/pref_header_vendor">
         <extra android:name="resource"
                android:value="preferences_vendor"/>
     </header>
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
-            android:title="@string/pref_header_devtools">
+            android:title="@string/pref_header_devtools"
+            android:id="@+id/pref_header_devtools">
         <extra android:name="resource"
                android:value="preferences_devtools"/>
     </header>
 
 </preference-headers>
--- a/mobile/android/base/resources/xml-v11/preferences_customize.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml
@@ -4,17 +4,18 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- Changes should be mirrored to preferences_customize_tablet.xml. -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:enabled="false">
 
-    <PreferenceScreen android:title="@string/pref_category_home"
+    <PreferenceScreen android:key="android.not_a_preference.customize_home"
+                      android:title="@string/pref_category_home"
                       android:summary="@string/pref_category_home_summary"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
             <extra android:name="resource"
                    android:value="preferences_home" />
     </PreferenceScreen>
 
      <PreferenceScreen android:title="@string/pref_category_search"
                        android:summary="@string/pref_category_search_summary"
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
@@ -11,17 +11,18 @@
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:title="@string/pref_category_customize"
                   android:enabled="false">
 
     <org.mozilla.gecko.preferences.SyncPreference android:key="android.not_a_preference.sync"
                                                   android:title="@string/pref_sync"
                                                   android:persistent="false" />
 
-    <PreferenceScreen android:title="@string/pref_category_home"
+    <PreferenceScreen android:key="android.not_a_preference.customize_home"
+                      android:title="@string/pref_category_home"
                       android:summary="@string/pref_category_home_summary"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
             <extra android:name="resource"
                    android:value="preferences_home" />
     </PreferenceScreen>
 
     <PreferenceScreen android:title="@string/pref_category_search"
                       android:summary="@string/pref_category_search_summary"
--- a/mobile/android/base/resources/xml/preference_headers.xml
+++ b/mobile/android/base/resources/xml/preference_headers.xml
@@ -5,9 +5,12 @@
 
 <!-- This file is a stub to allow IDs to be used in code
      even for a version-limited build. -->
 
 <preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:id="@+id/pref_header_language">
     </header>
+    <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
+            android:id="@+id/pref_header_devtools">
+    </header>
 </preference-headers>
--- a/mobile/android/base/resources/xml/preferences_customize.xml
+++ b/mobile/android/base/resources/xml/preferences_customize.xml
@@ -2,17 +2,19 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:enabled="false">
 
-    <PreferenceScreen android:title="@string/pref_category_home"
+    <PreferenceScreen
+                      android:key="android.not_a_preference.customize_home"
+                      android:title="@string/pref_category_home"
                       android:summary="@string/pref_category_home_summary" >
         <intent android:action="android.intent.action.VIEW"
                 android:targetPackage="@string/android_package_name"
                 android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
             <extra
                 android:name="resource"
                 android:value="preferences_home" />
         </intent>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/DefaultConfiguration.java
@@ -0,0 +1,27 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+/**
+ * Default implementation of RestrictionConfiguration interface. Used whenever no restrictions are enforced for the
+ * current profile.
+ */
+public class DefaultConfiguration implements RestrictionConfiguration {
+    @Override
+    public boolean isAllowed(Restriction restriction) {
+        return true;
+    }
+
+    @Override
+    public boolean canLoadUrl(String url) {
+        return true;
+    }
+
+    @Override
+    public boolean isRestricted() {
+        return false;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/GuestProfileConfiguration.java
@@ -0,0 +1,75 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+import android.net.Uri;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * RestrictionConfiguration implementation for guest profiles.
+ */
+public class GuestProfileConfiguration implements RestrictionConfiguration {
+    static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
+            Restriction.DISALLOW_DOWNLOADS,
+            Restriction.DISALLOW_INSTALL_EXTENSION,
+            Restriction.DISALLOW_INSTALL_APPS,
+            Restriction.DISALLOW_BROWSE_FILES,
+            Restriction.DISALLOW_SHARE,
+            Restriction.DISALLOW_BOOKMARK,
+            Restriction.DISALLOW_ADD_CONTACTS,
+            Restriction.DISALLOW_SET_IMAGE,
+            Restriction.DISALLOW_MODIFY_ACCOUNTS,
+            Restriction.DISALLOW_REMOTE_DEBUGGING,
+            Restriction.DISALLOW_IMPORT_SETTINGS
+    );
+
+    @SuppressWarnings("serial")
+    private static final List<String> BANNED_SCHEMES = Arrays.asList(
+            "file",
+            "chrome",
+            "resource",
+            "jar",
+            "wyciwyg"
+    );
+
+    private static final List<String> BANNED_URLS = Arrays.asList(
+            "about:config"
+    );
+
+    @Override
+    public boolean isAllowed(Restriction restriction) {
+        return !DEFAULT_RESTRICTIONS.contains(restriction);
+    }
+
+    @Override
+    public boolean canLoadUrl(String url) {
+        // Null URLs are always permitted.
+        if (url == null) {
+            return true;
+        }
+
+        final Uri u = Uri.parse(url);
+        final String scheme = u.getScheme();
+        if (BANNED_SCHEMES.contains(scheme)) {
+            return false;
+        }
+
+        for (String banned : BANNED_URLS) {
+            if (url.startsWith(banned)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean isRestricted() {
+        return true;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/RestrictedProfileConfiguration.java
@@ -0,0 +1,81 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserManager;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class RestrictedProfileConfiguration implements RestrictionConfiguration {
+    static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
+            Restriction.DISALLOW_INSTALL_EXTENSION,
+            Restriction.DISALLOW_INSTALL_APPS,
+            Restriction.DISALLOW_TOOLS_MENU,
+            Restriction.DISALLOW_REPORT_SITE_ISSUE,
+            Restriction.DISALLOW_IMPORT_SETTINGS,
+            Restriction.DISALLOW_DEVELOPER_TOOLS,
+            Restriction.DISALLOW_CUSTOMIZE_HOME,
+            Restriction.DISALLOW_PRIVATE_BROWSING,
+            Restriction.DISALLOW_LOCATION_SERVICE
+    );
+
+    private static final String ABOUT_ADDONS = "about:addons";
+    private static final String ABOUT_PRIVATE_BROWSING = "about:privatebrowsing";
+
+    private Context context;
+
+    public RestrictedProfileConfiguration(Context context) {
+        this.context = context.getApplicationContext();
+    }
+
+    @Override
+    public boolean isAllowed(Restriction restriction) {
+        boolean isAllowed = !getAppRestrictions(context).getBoolean(restriction.name, DEFAULT_RESTRICTIONS.contains(restriction));
+
+        if (isAllowed) {
+            // If this restriction is not enforced by the app setup then check wether this is a restriction that is
+            // enforced by the system.
+            isAllowed = !getUserRestrictions(context).getBoolean(restriction.name, false);
+        }
+
+        return isAllowed;
+    }
+
+    @Override
+    public boolean canLoadUrl(String url) {
+        if (!isAllowed(Restriction.DISALLOW_INSTALL_EXTENSION) && url.toLowerCase().startsWith(ABOUT_ADDONS)) {
+            return false;
+        }
+
+        if (!isAllowed(Restriction.DISALLOW_PRIVATE_BROWSING) && url.toLowerCase().startsWith(ABOUT_PRIVATE_BROWSING)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean isRestricted() {
+        return true;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+    private Bundle getAppRestrictions(final Context context) {
+        final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        return mgr.getApplicationRestrictions(context.getPackageName());
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+    private Bundle getUserRestrictions(final Context context) {
+        final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        return mgr.getUserRestrictions();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/Restriction.java
@@ -0,0 +1,118 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+import org.mozilla.gecko.R;
+
+import android.content.Context;
+
+/**
+ * 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/nsIParentalControlsService.idl
+ */
+public enum Restriction {
+    DISALLOW_DOWNLOADS(
+            1, "no_download_files", 0, 0),
+
+    DISALLOW_INSTALL_EXTENSION(
+            2, "no_install_extensions",
+            R.string.restriction_disallow_addons_title,
+            R.string.restriction_disallow_addons_description),
+
+    // UserManager.DISALLOW_INSTALL_APPS
+    DISALLOW_INSTALL_APPS(
+            3, "no_install_apps",
+            R.string.restriction_disallow_apps_title,
+            R.string.restriction_disallow_apps_description),
+
+    DISALLOW_BROWSE_FILES(
+            4, "no_browse_files", 0, 0),
+
+    DISALLOW_SHARE(
+            5, "no_share", 0, 0),
+
+    DISALLOW_BOOKMARK(
+            6, "no_bookmark", 0, 0),
+
+    DISALLOW_ADD_CONTACTS(
+            7, "no_add_contacts", 0, 0),
+
+    DISALLOW_SET_IMAGE(
+            8, "no_set_image", 0, 0),
+
+    // UserManager.DISALLOW_MODIFY_ACCOUNTS
+    DISALLOW_MODIFY_ACCOUNTS(
+            9, "no_modify_accounts", 0, 0),
+
+    DISALLOW_REMOTE_DEBUGGING(
+            10, "no_remote_debugging", 0, 0),
+
+    DISALLOW_IMPORT_SETTINGS(
+            11, "no_report_site_issue",
+            R.string.restriction_disallow_import_settings_title,
+            R.string.restriction_disallow_import_settings_description),
+
+    DISALLOW_TOOLS_MENU(
+            12, "no_tools_menu",
+            R.string.restriction_disallow_tools_menu_title,
+            R.string.restriction_disallow_tools_menu_description),
+
+    DISALLOW_REPORT_SITE_ISSUE(
+            13, "no_report_site_issue",
+            R.string.restriction_disallow_report_site_issue_title,
+            R.string.restriction_disallow_report_site_issue_description),
+
+    DISALLOW_DEVELOPER_TOOLS(
+            14, "no_developer_tools",
+            R.string.restriction_disallow_devtools_title,
+            R.string.restriction_disallow_devtools_description
+    ),
+
+    DISALLOW_CUSTOMIZE_HOME(
+            15, "no_customize_home",
+            R.string.restriction_disallow_customize_home_title,
+            R.string.restriction_disallow_customize_home_description
+    ),
+
+    DISALLOW_PRIVATE_BROWSING(
+            16, "no_private_browsing",
+            R.string.restriction_disallow_private_browsing_title,
+            R.string.restriction_disallow_private_browsing_description
+    ),
+
+    DISALLOW_LOCATION_SERVICE(
+            17, "no_location_service",
+            R.string.restriction_disallow_location_services_title,
+            R.string.restriction_disallow_location_services_description
+    );
+
+    public final int id;
+    public final String name;
+    public final int titleResource;
+    public final int descriptionResource;
+
+    Restriction(final int id, final String name, int titleResource, int descriptionResource) {
+        this.id = id;
+        this.name = name;
+        this.titleResource = titleResource;
+        this.descriptionResource = descriptionResource;
+    }
+
+    public String getTitle(Context context) {
+        if (titleResource == 0) {
+            return toString();
+        }
+        return context.getResources().getString(titleResource);
+    }
+
+    public String getDescription(Context context) {
+        if (descriptionResource == 0) {
+            return name;
+        }
+        return context.getResources().getString(descriptionResource);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/RestrictionConfiguration.java
@@ -0,0 +1,26 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+/**
+ * Interface for classes that RestrictedProfiles will delegate to for making decisions.
+ */
+public interface RestrictionConfiguration {
+    /**
+     * Is the user allowed to perform this action?
+     */
+    boolean isAllowed(Restriction restriction);
+
+    /**
+     * Is the user allowed to load the given URL?
+     */
+    boolean canLoadUrl(String url);
+
+    /**
+     * Is this user restricted in any way?
+     */
+    boolean isRestricted();
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/restrictions/RestrictionProvider.java
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.restrictions;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
+import org.mozilla.gecko.restrictions.Restriction;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.RestrictionEntry;
+import android.os.Build;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Broadcast receiver providing supported restrictions to the system.
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class RestrictionProvider extends BroadcastReceiver {
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (AppConstants.Versions.preJBMR2) {
+            // This broadcast does not make any sense prior to Jelly Bean MR2.
+            return;
+        }
+
+        final PendingResult result = goAsync();
+
+        new Thread() {
+            @Override
+            public void run() {
+                final Bundle oldRestrictions = intent.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
+                final Bundle extras = new Bundle();
+
+                ArrayList<RestrictionEntry> entries = initRestrictions(context, oldRestrictions);
+                extras.putParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST, entries);
+
+                result.setResult(Activity.RESULT_OK, null, extras);
+                result.finish();
+            }
+        }.start();
+    }
+
+    private ArrayList<RestrictionEntry> initRestrictions(Context context, Bundle oldRestrictions) {
+        ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
+
+        for (Restriction restriction : RestrictedProfileConfiguration.DEFAULT_RESTRICTIONS) {
+            RestrictionEntry entry = createRestrictionEntryWithDefaultValue(context, restriction,
+                    oldRestrictions.getBoolean(restriction.name, true));
+            entries.add(entry);
+        }
+
+        return entries;
+    }
+
+    private RestrictionEntry createRestrictionEntryWithDefaultValue(Context context, Restriction restriction, boolean defaultValue) {
+        RestrictionEntry entry = new RestrictionEntry(restriction.name, defaultValue);
+
+        entry.setTitle(restriction.getTitle(context));
+        entry.setDescription(restriction.getDescription(context));
+
+        return entry;
+    }
+}
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -544,16 +544,24 @@
   <string name="restriction_disallow_report_site_issue_title">&restriction_disallow_report_site_issue_title;</string>
   <string name="restriction_disallow_report_site_issue_description">&restriction_disallow_report_site_issue_description;</string>
   <string name="restriction_disallow_import_settings_title">&restriction_disallow_import_settings_title;</string>
   <string name="restriction_disallow_import_settings_description">&restriction_disallow_import_settings_description;</string>
   <string name="restriction_disallow_addons_title">&restriction_disallow_addons_title;</string>
   <string name="restriction_disallow_addons_description">&restriction_disallow_addons_description;</string>
   <string name="restriction_disallow_apps_title">&restriction_disallow_apps_title;</string>
   <string name="restriction_disallow_apps_description">&restriction_disallow_apps_description;</string>
+  <string name="restriction_disallow_devtools_title">&restriction_disallow_devtools_title;</string>
+  <string name="restriction_disallow_devtools_description">&restriction_disallow_devtools_description;</string>
+  <string name="restriction_disallow_customize_home_title">&restriction_disallow_customize_home_title;</string>
+  <string name="restriction_disallow_customize_home_description">&restriction_disallow_customize_home_description;</string>
+  <string name="restriction_disallow_private_browsing_title">&restriction_disallow_private_browsing_title;</string>
+  <string name="restriction_disallow_private_browsing_description">&restriction_disallow_private_browsing_description;</string>
+  <string name="restriction_disallow_location_services_title">&restriction_disallow_location_services_title;</string>
+  <string name="restriction_disallow_location_services_description">&restriction_disallow_location_services_description;</string>
 
   <!-- Miscellaneous -->
   <string name="ellipsis">&ellipsis;</string>
 
   <string name="colon">&colon;</string>
 
   <string name="percent">&percent;</string>
 
--- a/mobile/android/base/tabs/TabsPanel.java
+++ b/mobile/android/base/tabs/TabsPanel.java
@@ -4,24 +4,26 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tabs;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.RestrictedProfiles;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.lwt.LightweightThemeDrawable;
+import org.mozilla.gecko.restrictions.Restriction;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.gecko.widget.IconTabWidget;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -134,16 +136,20 @@ public class TabsPanel extends LinearLay
             }
         });
 
         mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget);
 
         mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal);
         mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private);
 
+        if (!RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_PRIVATE_BROWSING)) {
+            mTabWidget.setVisibility(View.GONE);
+        }
+
         mTabWidget.setTabSelectionListener(this);
 
         mMenuButton = (ImageButton) findViewById(R.id.menu);
         mMenuButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View view) {
                 showMenu();
             }
@@ -161,17 +167,18 @@ public class TabsPanel extends LinearLay
         }
     }
 
     public void showMenu() {
         final Menu menu = mPopupMenu.getMenu();
 
         // Each panel has a "+" shortcut button, so don't show it for that panel.
         menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
-        menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
+        menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS
+                && RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_PRIVATE_BROWSING));
 
         // Only show "Clear * tabs" for current panel.
         menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
         menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
 
         mPopupMenu.show();
     }
 
--- a/mobile/android/base/widget/TabThumbnailWrapper.java
+++ b/mobile/android/base/widget/TabThumbnailWrapper.java
@@ -1,17 +1,17 @@
 package org.mozilla.gecko.widget;
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
 import org.mozilla.gecko.R;
 
 
-public class TabThumbnailWrapper extends FrameLayout {
+public class TabThumbnailWrapper extends RelativeLayout {
     private boolean mRecording;
     private static final int[] STATE_RECORDING = { R.attr.state_recording };
 
     public TabThumbnailWrapper(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
 
     public TabThumbnailWrapper(Context context, AttributeSet attrs) {
--- a/mobile/android/base/widget/ThemedEditText.java
+++ b/mobile/android/base/widget/ThemedEditText.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedEditText extends android.widget.EditText
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedImageButton.java
+++ b/mobile/android/base/widget/ThemedImageButton.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedImageButton extends android.widget.ImageButton
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedImageView.java
+++ b/mobile/android/base/widget/ThemedImageView.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedImageView extends android.widget.ImageView
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedLinearLayout.java
+++ b/mobile/android/base/widget/ThemedLinearLayout.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedLinearLayout extends android.widget.LinearLayout
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedRelativeLayout.java
+++ b/mobile/android/base/widget/ThemedRelativeLayout.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedRelativeLayout extends android.widget.RelativeLayout
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedTextSwitcher.java
+++ b/mobile/android/base/widget/ThemedTextSwitcher.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedTextSwitcher extends android.widget.TextSwitcher
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedTextView.java
+++ b/mobile/android/base/widget/ThemedTextView.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedTextView extends android.widget.TextView
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedView.java
+++ b/mobile/android/base/widget/ThemedView.java
@@ -2,18 +2,18 @@
 
 /* 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.widget;
 
 import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightTheme;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class ThemedView extends android.view.View
                                      implements LightweightTheme.OnChangeListener {
--- a/mobile/android/base/widget/ThemedView.java.frag
+++ b/mobile/android/base/widget/ThemedView.java.frag
@@ -3,17 +3,17 @@
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.LightweightTheme;
+import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 
 public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
--- a/mobile/android/chrome/content/aboutLogins.js
+++ b/mobile/android/chrome/content/aboutLogins.js
@@ -33,16 +33,21 @@ function copyStringAndToast(string, noti
   } catch (e) {
     debug("Error copying from about:logins");
     gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("loginsDetails.copyFailed"), "short");
   }
 }
 
 // Delay filtering while typing in MS
 const FILTER_DELAY = 500;
+/* Constants for usage telemetry */
+const LOGINS_LIST_VIEWED = 0;
+const LOGIN_VIEWED = 1;
+const LOGIN_EDITED = 2;
+const LOGIN_PW_TOGGLED = 3;
 
 let Logins = {
   _logins: [],
   _filterTimer: null,
   _selectedLogin: null,
 
   _getLogins: function() {
     let logins;
@@ -157,16 +162,17 @@ let Logins = {
       let item = this._createItemForLogin(login);
       newList.appendChild(item);
     });
 
     list.parentNode.replaceChild(newList, list);
   },
 
   _showList: function () {
+    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGINS_LIST_VIEWED);
     let loginsListPage = document.getElementById("logins-list-page");
     loginsListPage.classList.remove("hidden");
 
     let editLoginPage = document.getElementById("edit-login-page");
     editLoginPage.classList.add("hidden");
 
     // If the Show/Hide password button has been flipped, reset it
     if (this._isPasswordBtnInHideMode()) {
@@ -179,16 +185,17 @@ let Logins = {
     if (event.state) {
       this._showEditLoginDialog(event.state.id);
     } else {
       this._selectedLogin = null;
       this._showList();
     }
   },
   _showEditLoginDialog: function (login) {
+    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_VIEWED);
     let listPage = document.getElementById("logins-list-page");
     listPage.classList.add("hidden");
 
     let editLoginPage = document.getElementById("edit-login-page");
     editLoginPage.classList.remove("hidden");
 
     let usernameField = document.getElementById("username");
     usernameField.value = login.username;
@@ -205,16 +212,17 @@ let Logins = {
       headerText.textContent = login.hostname;
     }
     else {
       headerText.textContent = gStringBundle.GetStringFromName("editLogin.fallbackTitle");
     }
   },
 
   _onSaveEditLogin: function() {
+    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_EDITED);
     let newUsername = document.getElementById("username").value;
     let newPassword = document.getElementById("password").value;
     let newDomain  = document.getElementById("hostname").value;
     let origUsername = this._selectedLogin.username;
     let origPassword = this._selectedLogin.password;
     let origDomain = this._selectedLogin.hostname;
 
     try {
@@ -243,16 +251,17 @@ let Logins = {
       gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("editLogin.couldNotSave"), "short");
       return;
     }
     gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("editLogin.saved1"), "short");
     this._showList();
   },
 
   _onPasswordBtn: function () {
+    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_PW_TOGGLED);
     this._updatePasswordBtn(this._isPasswordBtnInHideMode());
   },
 
   _updatePasswordBtn: function (aShouldShow) {
     let passwordField = document.getElementById("password");
     let button = document.getElementById("password-btn");
     let show = gStringBundle.GetStringFromName("password-btn.show");
     let hide = gStringBundle.GetStringFromName("password-btn.hide");
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -685,37 +685,45 @@ var BrowserApp = {
           button: {
             icon: "drawable://switch_button_icon",
             label: buttonLabel,
             callback: () => { BrowserApp.selectTab(tab); },
           }
         });
       });
 
-    NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"),
-      NativeWindow.contextmenus.linkOpenableContext,
-      function(aTarget) {
-        UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab");
-        UITelemetry.addEvent("loadurl.1", "contextmenu", null);
-
-        let url = NativeWindow.contextmenus._getLinkURL(aTarget);
-        ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
-        let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true });
-
-        let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
-        let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
-        let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
-        NativeWindow.toast.show(label, "long", {
-          button: {
-            icon: "drawable://switch_button_icon",
-            label: buttonLabel,
-            callback: () => { BrowserApp.selectTab(tab); },
-          }
+    let showOpenInPrivateTab = true;
+    if ("@mozilla.org/parental-controls-service;1" in Cc) {
+      let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService);
+      showOpenInPrivateTab = pc.isAllowed(Ci.nsIParentalControlsService.PRIVATE_BROWSING);
+    }
+
+    if (showOpenInPrivateTab) {
+      NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"),
+        NativeWindow.contextmenus.linkOpenableContext,
+        function (aTarget) {
+          UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab");
+          UITelemetry.addEvent("loadurl.1", "contextmenu", null);
+
+          let url = NativeWindow.contextmenus._getLinkURL(aTarget);
+          ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
+          let tab = BrowserApp.addTab(url, {selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true});
+
+          let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
+          let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
+          let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
+          NativeWindow.toast.show(label, "long", {
+            button: {
+              icon: "drawable://switch_button_icon",
+              label: buttonLabel,
+              callback: () => { BrowserApp.selectTab(tab); },
+            }
+          });
         });
-      });
+    }
 
     NativeWindow.contextmenus.add(stringGetter("contextmenu.addToReadingList"),
       NativeWindow.contextmenus.linkOpenableContext,
       function(aTarget) {
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
         Messaging.sendRequestForResult({
             type: "Reader:AddToList",
             title: truncate(url, MAX_TITLE_LENGTH),
--- a/mobile/android/tests/browser/robocop/testRestrictedProfiles.js
+++ b/mobile/android/tests/browser/robocop/testRestrictedProfiles.js
@@ -24,32 +24,9 @@ add_task(function test_isUserRestricted(
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.INSTALL_APP));
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.VISIT_FILE_URLS));
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.SHARE));
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.BOOKMARK));
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.INSTALL_EXTENSION));
   do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.MODIFY_ACCOUNTS));
 });
 
-add_task(function test_getUserRestrictions() {
-  // In an admin profile, like the tests: {}
-  // In a restricted profile: {"no_modify_accounts":true,"no_share_location":true}
-  let restrictions = "{}";
-
-  var jenv = null;
-  try {
-    jenv = JNI.GetForThread();
-    var profile = JNI.LoadClass(jenv, "org.mozilla.gecko.RestrictedProfiles", {
-      static_methods: [
-        { name: "getUserRestrictions", sig: "()Ljava/lang/String;" },
-      ],
-    });
-    restrictions = JNI.ReadString(jenv, profile.getUserRestrictions());
-  } finally {
-    if (jenv) {
-      JNI.UnloadClasses(jenv);
-    }
-  }
-
-  do_check_eq(restrictions, "{}");
-});
-
 run_next_test();
--- a/mobile/android/themes/core/aboutLogins.css
+++ b/mobile/android/themes/core/aboutLogins.css
@@ -208,16 +208,17 @@ body {
   align-items: center;
   text-align: center;
   justify-content: center;
 }
 
 .empty-text {
   color: #363B40;
   font-size: 25px;
+  font-weight: lighter;
   margin-bottom: 20px;
 }
 
 .empty-hint {
   color: #777777;
   font-size: 20px;
 }
 
--- a/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
+++ b/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
@@ -6,17 +6,17 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIInterfaceRequestor;
 interface nsIArray;
 
-[scriptable, uuid(406808a5-2838-4c29-9e39-5bfb885c89bf)]
+[scriptable, uuid(4ee714a7-e9a8-43ed-a061-60155b63e290)]
 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
@@ -25,16 +25,18 @@ interface nsIParentalControlsService : n
   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
   const short TOOLS_MENU = 12; // Hide tools menu entry
   const short REPORT_SITE_ISSUE = 13; // Hide "Report Site Issue" menu entry
+  const short PRIVATE_BROWSING = 16; // Disallow usage of private browsing
+  const short LOCATION_SERVICE = 17; // Sharing of location data to location service
 
   /**
    * @returns true if the current user account has parental controls
    * restrictions enabled.
    */
   readonly attribute boolean parentalControlsEnabled;
 
   /**
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3382,17 +3382,17 @@
     "kind": "exponential",
     "high": "2000",
     "n_buckets": 10,
     "cpp_guard": "ANDROID",
     "extended_statistics_ok": true,
     "description": "Number of thumbnails stored in the browser DB *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
   },
   "FENNEC_READING_LIST_COUNT": {
-    "expires_in_version": "40",
+    "expires_in_version": "50",
     "kind": "exponential",
     "high": "1000",
     "n_buckets": 10,
     "cpp_guard": "ANDROID",
     "extended_statistics_ok": true,
     "description": "Number of reading list items stored in the browser DB *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
   },
   "PLACES_SORTED_BOOKMARKS_PERC": {
@@ -8226,16 +8226,22 @@
   "PWMGR_ABOUT_LOGINS_GET_ALL_LOGINS_MS": {
     "expires_in_version": "55",
     "kind": "exponential",
     "high": 60000,
     "n_buckets": 30,
     "extended_statistics_ok": true,
     "description": "How long getAllLogins() on about:logins takes for mobile users"
   },
+  "PWMGR_ABOUT_LOGINS_USAGE": {
+    "expires_in_version": "55",
+    "kind": "enumerated",
+    "n_values": 12,
+    "description": "Usage of about:logins 0= list of logins viewed, 1=a login's specifics page was viewed, 2=user edited login credentials 3=user toggled the show/hide button"
+  },
   "PWMGR_BLOCKLIST_NUM_SITES": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 100,
     "n_buckets" : 10,
     "extended_statistics_ok": true,
     "description": "The number of sites for which the user has explicitly rejected saving logins"
   },
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -760,24 +760,16 @@ constexpr char GeckoJavaSampler::Unpause
 
 void GeckoJavaSampler::UnpauseJavaProfiling()
 {
     return mozilla::jni::Method<UnpauseJavaProfiling_t>::Call(nullptr, nullptr);
 }
 
 constexpr char RestrictedProfiles::name[];
 
-constexpr char RestrictedProfiles::GetUserRestrictions_t::name[];
-constexpr char RestrictedProfiles::GetUserRestrictions_t::signature[];
-
-mozilla::jni::String::LocalRef RestrictedProfiles::GetUserRestrictions()
-{
-    return mozilla::jni::Method<GetUserRestrictions_t>::Call(nullptr, nullptr);
-}
-
 constexpr char RestrictedProfiles::IsAllowed_t::name[];
 constexpr char RestrictedProfiles::IsAllowed_t::signature[];
 
 bool RestrictedProfiles::IsAllowed(int32_t a0, mozilla::jni::String::Param a1)
 {
     return mozilla::jni::Method<IsAllowed_t>::Call(nullptr, nullptr, a0, a1);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1814,33 +1814,16 @@ public:
 
     static constexpr char name[] =
             "org/mozilla/gecko/RestrictedProfiles";
 
 protected:
     RestrictedProfiles(jobject instance) : Class(instance) {}
 
 public:
-    struct GetUserRestrictions_t {
-        typedef RestrictedProfiles Owner;
-        typedef mozilla::jni::String::LocalRef ReturnType;
-        typedef mozilla::jni::String::Param SetterType;
-        typedef mozilla::jni::Args<> Args;
-        static constexpr char name[] = "getUserRestrictions";
-        static constexpr char signature[] =
-                "()Ljava/lang/String;";
-        static const bool isStatic = true;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    static mozilla::jni::String::LocalRef GetUserRestrictions();
-
-public:
     struct IsAllowed_t {
         typedef RestrictedProfiles Owner;
         typedef bool ReturnType;
         typedef bool SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "isAllowed";