Bug 1001309 - Provide a way to clear private data on Exit. r=liuche, bnicholson, mcomella
authorWes Johnston <wjohnston@mozilla.com>
Thu, 19 Jun 2014 00:04:00 -0700
changeset 194962 00b81ba0835a987a2e46805a3b6c24387351c02a
parent 194961 0773f2e66747884b5b6c36eb184ed69731e927bb
child 195026 d407b2eb63213272418b49bfee87631d60f213df
push id7817
push userwjohnston@mozilla.com
push dateSat, 19 Jul 2014 00:09:15 +0000
treeherderfx-team@00b81ba0835a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersliuche, bnicholson, mcomella
bugs1001309
milestone33.0a1
Bug 1001309 - Provide a way to clear private data on Exit. r=liuche, bnicholson, mcomella
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApp.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/preferences/AndroidImportPreference.java
mobile/android/base/preferences/ClearOnShutdownPref.java
mobile/android/base/preferences/GeckoPreferences.java
mobile/android/base/preferences/ListCheckboxPreference.java
mobile/android/base/preferences/MultiChoicePreference.java
mobile/android/base/preferences/MultiPrefMultiChoicePreference.java
mobile/android/base/preferences/PrivateDataPreference.java
mobile/android/base/resources/layout/preference_checkbox.xml
mobile/android/base/resources/values/arrays.xml
mobile/android/base/resources/values/attrs.xml
mobile/android/base/resources/xml-v11/preferences_customize.xml
mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
mobile/android/base/resources/xml/preferences_customize.xml
mobile/android/base/resources/xml/preferences_privacy.xml
mobile/android/base/strings.xml.in
mobile/android/base/tests/StringHelper.java
mobile/android/base/tests/testSettingsMenuItems.java
mobile/android/base/util/JSONUtils.java
mobile/android/base/util/PrefUtils.java
mobile/android/chrome/content/browser.js
mobile/android/modules/Sanitizer.jsm
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -6,19 +6,21 @@
 package org.mozilla.gecko;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.lang.Class;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.Set;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.animation.PropertyAnimator;
@@ -47,31 +49,33 @@ import org.mozilla.gecko.home.BrowserSea
 import org.mozilla.gecko.home.HomeBanner;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
 import org.mozilla.gecko.preferences.GeckoPreferences;
+import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.tabspanel.TabsPanel;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.ToolbarProgressView;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
+import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.ButtonToast;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -2399,19 +2403,21 @@ public class BrowserApp extends GeckoApp
         MenuItem share = aMenu.findItem(R.id.share);
         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
         MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
         MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
         MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
         MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
 
-        // Only show the "Quit" menu item on pre-ICS or television devices.
+        // Only show the "Quit" menu item on pre-ICS, television devices, or if the user has explicitly enabled the clear on shutdown pref.
         // In ICS+, it's easy to kill an app through the task switcher.
-        aMenu.findItem(R.id.quit).setVisible(Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
+        final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
+        final Set<String> clearItems = PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
+        aMenu.findItem(R.id.quit).setVisible(clearItems.size() > 0 || Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
 
         if (tab == null || tab.getURL() == null) {
             bookmark.setEnabled(false);
             back.setEnabled(false);
             forward.setEnabled(false);
             share.setEnabled(false);
             saveAsPDF.setEnabled(false);
             findInPage.setEnabled(false);
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -36,27 +37,29 @@ import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PluginLayer;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.health.StubbedHealthRecorder;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.menu.MenuPanel;
 import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
+import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.webapp.EventListener;
 import org.mozilla.gecko.webapp.UninstallListener;
 import org.mozilla.gecko.widget.ButtonToast;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -431,20 +434,33 @@ public abstract class GeckoApp
 
         return super.onMenuOpened(featureId, menu);
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == R.id.quit) {
             if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
-                GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null));
+                final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
+                final Set<String> clearSet = PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
+
+                final JSONObject clearObj = new JSONObject();
+                for (String clear : clearSet) {
+                    try {
+                        clearObj.put(clear, true);
+                    } catch(JSONException ex) {
+                        Log.i(LOGTAG, "Error adding clear object " + clear);
+                    }
+                }
+
+                GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", clearObj.toString()));
             } else {
                 GeckoAppShell.systemExit();
             }
+
             return true;
         }
 
         return super.onOptionsItemSelected(item);
     }
 
     @Override
     public void onOptionsMenuClosed(Menu menu) {
@@ -564,16 +580,17 @@ public abstract class GeckoApp
             final NativeJSObject[] permissions = message.getObjectArray("permissions");
             showSiteSettingsDialog(host, permissions);
 
         } else if ("PrivateBrowsing:Data".equals(event)) {
             mPrivateBrowsingSession = message.optString("session", null);
 
         } else if ("Sanitize:ClearHistory".equals(event)) {
             handleClearHistory();
+            callback.sendSuccess(true);
 
         } else if ("Session:StatePurged".equals(event)) {
             onStatePurged();
 
         } else if ("Share:Text".equals(event)) {
             String text = message.getString("text");
             GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -140,17 +140,21 @@
 <!ENTITY pref_donottrack_menu "Tracking">
 <!ENTITY pref_donottrack_disallow_tracking "Tell sites that I do not want to be tracked">
 <!ENTITY pref_donottrack_allow_tracking "Tell sites that I want to be tracked">
 <!ENTITY pref_donottrack_no_pref "Do not tell sites anything about my tracking preferences">
 
 <!ENTITY pref_char_encoding "Character encoding">
 <!ENTITY pref_char_encoding_on "Show menu">
 <!ENTITY pref_char_encoding_off "Don\'t show menu">
-<!ENTITY pref_clear_private_data "Clear private data">
+<!ENTITY pref_clear_private_data2 "Clear now">
+<!ENTITY pref_clear_private_data_category "Clear private data">
+<!ENTITY pref_clear_on_exit_title "Always clear when quitting">
+<!ENTITY pref_clear_on_exit_summary "&brandShortName; will automatically clear your data whenever you select &quot;Quit&quot; from the main menu">
+<!ENTITY pref_clear_on_exit_dialog_title "Select which data to clear">
 <!ENTITY pref_plugins "Plugins">
 <!ENTITY pref_plugins_enabled "Enabled">
 <!ENTITY pref_plugins_tap_to_play "Tap to play">
 <!ENTITY pref_plugins_disabled "Disabled">
 <!ENTITY pref_text_size "Text size">
 <!ENTITY pref_reflow_on_zoom4 "Text reflow">
 <!ENTITY pref_restore "Tabs">
 <!ENTITY pref_restore_always "Always restore">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -66,16 +66,17 @@ gujar.sources += [
     'util/INIParser.java',
     'util/INISection.java',
     'util/JSONUtils.java',
     'util/MenuUtils.java',
     'util/NativeEventListener.java',
     'util/NativeJSContainer.java',
     'util/NativeJSObject.java',
     'util/NonEvictingLruCache.java',
+    'util/PrefUtils.java',
     'util/ProxySelector.java',
     'util/RawResource.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UiAsyncTask.java',
     'util/WebActivityMapper.java',
 ]
 gujar.extra_jars = [
@@ -329,25 +330,28 @@ gbjar.sources += [
     'NotificationHandler.java',
     'NotificationHelper.java',
     'NotificationService.java',
     'NSSBridge.java',
     'OrderedBroadcastHelper.java',
     'preferences/AlignRightLinkPreference.java',
     'preferences/AndroidImport.java',
     'preferences/AndroidImportPreference.java',
+    'preferences/ClearOnShutdownPref.java',
     'preferences/CustomListCategory.java',
     'preferences/CustomListPreference.java',
     'preferences/FontSizePreference.java',
     'preferences/GeckoPreferenceFragment.java',
     'preferences/GeckoPreferences.java',
     'preferences/LinkPreference.java',
+    'preferences/ListCheckboxPreference.java',
     'preferences/LocaleListPreference.java',
     'preferences/ModifiableHintPreference.java',
     'preferences/MultiChoicePreference.java',
+    'preferences/MultiPrefMultiChoicePreference.java',
     'preferences/PanelsPreference.java',
     'preferences/PanelsPreferenceCategory.java',
     'preferences/PrivateDataPreference.java',
     'preferences/SearchEnginePreference.java',
     'preferences/SearchPreferenceCategory.java',
     'preferences/SyncPreference.java',
     'PrefsHelper.java',
     'PrivateTab.java',
--- a/mobile/android/base/preferences/AndroidImportPreference.java
+++ b/mobile/android/base/preferences/AndroidImportPreference.java
@@ -3,22 +3,24 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import java.util.Set;
+
 import android.app.ProgressDialog;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
 
-class AndroidImportPreference extends MultiChoicePreference {
+class AndroidImportPreference extends MultiPrefMultiChoicePreference {
     static final private String LOGTAG = "AndroidImport";
     private static final String PREF_KEY_PREFIX = "import_android.data.";
     private Context mContext;
 
     public AndroidImportPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
     }
@@ -28,29 +30,25 @@ class AndroidImportPreference extends Mu
         super.onDialogClosed(positiveResult);
 
         if (!positiveResult)
             return;
 
         boolean bookmarksChecked = false;
         boolean historyChecked = false;
 
-        CharSequence keys[] = getEntryKeys();
-        boolean values[] = getValues();
+        Set<String> values = getValues();
 
-        for (int i = 0; i < keys.length; i++) {
-            // Privacy pref checkbox values are stored in Android prefs to
+        for (String value : values) {
+            // Import checkbox values are stored in Android prefs to
             // remember their check states. The key names are import_android.data.X
-            String key = keys[i].toString().substring(PREF_KEY_PREFIX.length());
-            boolean value = values[i];
-
-            if (key.equals("bookmarks") && value) {
+            String key = value.substring(PREF_KEY_PREFIX.length());
+            if ("bookmarks".equals(key)) {
                 bookmarksChecked = true;
-            }
-            if (key.equals("history") && value) {
+            } else if ("history".equals(key)) {
                 historyChecked = true;
             }
         }
 
         runImport(bookmarksChecked, historyChecked);
     }
 
     protected void runImport(final boolean doBookmarks, final boolean doHistory) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/ClearOnShutdownPref.java
@@ -0,0 +1,36 @@
+/* -*- 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.preferences;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.util.PrefUtils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.Preference;
+
+public class ClearOnShutdownPref implements GeckoPreferences.PrefHandler {
+    public static final String PREF = GeckoPreferences.NON_PREF_PREFIX + "history.clear_on_exit";
+
+    @Override
+    public void setupPref(Context context, Preference pref) {
+        // The pref is initialized asynchronously. Read the pref explicitly
+        // here to make sure we have the data.
+        final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
+        final Set<String> clearItems = PrefUtils.getStringSet(prefs, PREF, new HashSet<String>());
+        ((ListCheckboxPreference) pref).setChecked(clearItems.size() > 0);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void onChange(Context context, Preference pref, Object newValue) {
+        final Set<String> vals = (Set<String>) newValue;
+        ((ListCheckboxPreference) pref).setChecked(vals.size() > 0);
+    }
+}
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -1,18 +1,20 @@
 /* -*- 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.preferences;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.DataReportingNotification;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoActivityStatus;
@@ -87,17 +89,17 @@ OnSharedPreferenceChangeListener
 {
     private static final String LOGTAG = "GeckoPreferences";
 
     // We have a white background, which makes transitions on
     // some devices look bad. Don't use transitions on those
     // devices.
     private static final boolean NO_TRANSITIONS = HardwareUtils.IS_KINDLE_DEVICE;
 
-    private static final String NON_PREF_PREFIX = "android.not_a_preference.";
+    public static final String NON_PREF_PREFIX = "android.not_a_preference.";
     public static final String INTENT_EXTRA_RESOURCES = "resource";
     public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled";
 
     private static boolean sIsCharEncodingEnabled = false;
     private boolean mInitialized = false;
     private int mPrefsRequestId = 0;
     private PanelsPreferenceCategory mPanelsPreferenceCategory;
 
@@ -722,16 +724,19 @@ OnSharedPreferenceChangeListener
                     pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                         @Override
                         public boolean onPreferenceClick(Preference preference) {
                             Intent dialogIntent = new Intent(GeckoPreferences.this, HomePanelPicker.class);
                             startActivityForResultChoosingTransition(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL);
                             return true;
                         }
                     });
+                } else if (handlers.containsKey(key)) {
+                    PrefHandler handler = handlers.get(key);
+                    handler.setupPref(this, pref);
                 }
 
                 // 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.
@@ -991,19 +996,30 @@ OnSharedPreferenceChangeListener
         if (PREFS_BROWSER_LOCALE.equals(key)) {
             onLocaleSelected(BrowserLocaleManager.getLanguageTag(lastLocale),
                              sharedPreferences.getString(key, null));
         } else if (PREFS_SUGGESTED_SITES.equals(key)) {
             refreshSuggestedSites();
         }
     }
 
+    public interface PrefHandler {
+        public void setupPref(Context context, Preference pref);
+        public void onChange(Context context, Preference pref, Object newValue);
+    }
+
+    @SuppressWarnings("serial")
+    private Map<String, PrefHandler> handlers = new HashMap<String, PrefHandler>() {{
+        put(ClearOnShutdownPref.PREF, new ClearOnShutdownPref());
+    }};
+
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         final String prefName = preference.getKey();
+        Log.i(LOGTAG, "Changed " + prefName + " = " + newValue);
         if (PREFS_MP_ENABLED.equals(prefName)) {
             showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
 
             // We don't want the "use master password" pref to change until the
             // user has gone through the dialog.
             return false;
         }
 
@@ -1025,16 +1041,19 @@ OnSharedPreferenceChangeListener
             // The healthreport pref only lives in Android, so we do not persist
             // to Gecko, but we do broadcast intent to the health report
             // background uploader service, which will start or stop the
             // repeated background upload attempts.
             broadcastHealthReportUploadPref(this, ((Boolean) newValue).booleanValue());
         } else if (PREFS_GEO_REPORTING.equals(prefName)) {
             // Translate boolean value to int for geo reporting pref.
             newValue = ((Boolean) newValue) ? 1 : 0;
+        } else if (handlers.containsKey(prefName)) {
+            PrefHandler handler = handlers.get(prefName);
+            handler.onChange(this, preference, newValue);
         }
 
         // Send Gecko-side pref changes to Gecko
         if (isGeckoPref(prefName)) {
             PrefsHelper.setPref(prefName, newValue);
         }
 
         if (preference instanceof ListPreference) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/ListCheckboxPreference.java
@@ -0,0 +1,58 @@
+/* -*- 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.preferences;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+
+import org.mozilla.gecko.R;
+
+/**
+  * This preference shows a checkbox on its left hand side, but will show a menu when clicked.
+  * Its used for preferences like "Clear on Exit" that have a boolean on-off state, but that represent
+  * multiple boolean options inside.
+  **/
+class ListCheckboxPreference extends MultiChoicePreference implements Checkable {
+    private static final String LOGTAG = "GeckoListCheckboxPreference";
+    private boolean checked;
+
+    public ListCheckboxPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setWidgetLayoutResource(R.layout.preference_checkbox);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return checked;
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        View checkboxView = view.findViewById(R.id.checkbox);
+        if (checkboxView != null && checkboxView instanceof Checkable) {
+            ((Checkable) checkboxView).setChecked(checked);
+        }
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        boolean changed = checked != this.checked;
+        this.checked = checked;
+        if (changed) {
+            notifyDependencyChange(shouldDisableDependents());
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public void toggle() {
+        checked = !checked;
+    }
+}
--- a/mobile/android/base/preferences/MultiChoicePreference.java
+++ b/mobile/android/base/preferences/MultiChoicePreference.java
@@ -2,88 +2,93 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
+import android.content.SharedPreferences;
 import android.preference.DialogPreference;
 import android.util.AttributeSet;
-import android.widget.Button;
 
-class MultiChoicePreference extends DialogPreference {
+import java.util.HashSet;
+import java.util.Set;
+
+class MultiChoicePreference extends DialogPreference implements DialogInterface.OnMultiChoiceClickListener {
     private static final String LOGTAG = "GeckoMultiChoicePreference";
 
     private boolean mValues[];
     private boolean mPrevValues[];
-    private CharSequence mEntryKeys[];
+    private CharSequence mEntryValues[];
     private CharSequence mEntries[];
     private CharSequence mInitialValues[];
 
     public MultiChoicePreference(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiChoicePreference);
         mEntries = a.getTextArray(R.styleable.MultiChoicePreference_entries);
-        mEntryKeys = a.getTextArray(R.styleable.MultiChoicePreference_entryKeys);
+        mEntryValues = a.getTextArray(R.styleable.MultiChoicePreference_entryValues);
         mInitialValues = a.getTextArray(R.styleable.MultiChoicePreference_initialValues);
         a.recycle();
 
         loadPersistedValues();
     }
 
     public MultiChoicePreference(Context context) {
         this(context, null);
     }
 
     /**
      * Sets the human-readable entries to be shown in the list. This will be
      * shown in subsequent dialogs.
      * <p>
      * Each entry must have a corresponding index in
-     * {@link #setEntryKeys(CharSequence[])} and
+     * {@link #setEntryValues(CharSequence[])} and
      * {@link #setInitialValues(CharSequence[])}.
-     * 
+     *
      * @param entries The entries.
      */
     public void setEntries(CharSequence[] entries) {
         mEntries = entries.clone();
     }
     
     /**
      * @param entriesResId The entries array as a resource.
      */
     public void setEntries(int entriesResId) {
         setEntries(getContext().getResources().getTextArray(entriesResId));
     }
 
     /**
-     * Sets the preference keys for preferences shown in the list.
+     * Sets the preference values for preferences shown in the list.
      *
-     * @param entryKeys The entry keys.
+     * @param entryValues The entry values.
      */
-    public void setEntryKeys(CharSequence[] entryKeys) {
-        mEntryKeys = entryKeys.clone();
+    public void setEntryValues(CharSequence[] entryValues) {
+        mEntryValues = entryValues.clone();
         loadPersistedValues();
     }
 
     /**
-     * @param entryKeysResId The entryKeys array as a resource.
+     * Entry values define a separate pref for each row in the dialog.
+     *
+     * @param entryValuesResId The entryValues array as a resource.
      */
-    public void setEntryKeys(int entryKeysResId) {
-        setEntryKeys(getContext().getResources().getTextArray(entryKeysResId));
+    public void setEntryValues(int entryValuesResId) {
+        setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
     }
 
     /**
      * The array of initial entry values in this list. Each entryValue
      * corresponds to an entryKey. These values are used if a) the preference
      * isn't persisted, or b) the preference is persisted but hasn't yet been
      * set.
      *
@@ -106,74 +111,78 @@ class MultiChoicePreference extends Dial
      * 
      * @return The array of entries.
      */
     public CharSequence[] getEntries() {
         return mEntries.clone();
     }
 
     /**
-     * The list of keys corresponding to each preference.
+     * The list of values corresponding to each preference.
      * 
-     * @return The array of keys.
+     * @return The array of values.
      */
-    public CharSequence[] getEntryKeys() {
-        return mEntryKeys.clone();
+    public CharSequence[] getEntryValues() {
+        return mEntryValues.clone();
     }
 
     /**
      * The list of initial values for each preference. Each string in this list
      * should be either "true" or "false".
      * 
      * @return The array of initial values.
      */
     public CharSequence[] getInitialValues() {
         return mInitialValues.clone();
     }
 
+    public void setValue(final int i, final boolean value) {
+        mValues[i] = value;
+        mPrevValues = mValues.clone();
+    }
+
     /**
      * The list of values for each preference. These values are updated after
      * the dialog has been displayed.
      * 
      * @return The array of values.
      */
-    public boolean[] getValues() {
-        return mValues.clone();
+    public Set<String> getValues() {
+        final Set<String> values = new HashSet<String>();
+
+        if (mValues == null) {
+            return values;
+        }
+
+        for (int i = 0; i < mValues.length; i++) {
+            if (mValues[i]) {
+                values.add(mEntryValues[i].toString());
+            }
+        }
+
+        return values;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which, boolean val) {
     }
 
     @Override
     protected void onPrepareDialogBuilder(Builder builder) {
-        if (mEntries == null || mEntryKeys == null || mInitialValues == null) {
+        if (mEntries == null || mInitialValues == null || mEntryValues == null) {
             throw new IllegalStateException(
-                    "MultiChoicePreference requires entries, entryKeys, and initialValues arrays.");
-        }
-
-        if (mEntries.length != mEntryKeys.length || mEntryKeys.length != mInitialValues.length) {
-            throw new IllegalStateException(
-                    "MultiChoicePreference entries, entryKeys, and initialValues arrays must be the same length");
+                    "MultiChoicePreference requires entries, entryValues, and initialValues arrays.");
         }
 
-        builder.setMultiChoiceItems(mEntries, mValues, new DialogInterface.OnMultiChoiceClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which, boolean val) {
-                // mValues is automatically updated when checkboxes are clicked
+        if (mEntries.length != mEntryValues.length || mEntries.length != mInitialValues.length) {
+            throw new IllegalStateException(
+                    "MultiChoicePreference entries, entryValues, and initialValues arrays must be the same length");
+        }
 
-                // enable positive button only if at least one item is checked
-                boolean enabled = false;
-                for (int i = 0; i < mValues.length; i++) {
-                    if (mValues[i]) {
-                        enabled = true;
-                        break;
-                    }
-                }
-                Button button = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
-                if (button.isEnabled() != enabled)
-                    button.setEnabled(enabled);
-            }
-        });
+        builder.setMultiChoiceItems(mEntries, mValues, this);
     }
 
     @Override
     protected void onDialogClosed(boolean positiveResult) {
         if (mPrevValues == null || mInitialValues == null) {
             // Initialization is done asynchronously, so these values may not
             // have been set before the dialog was closed.
             return;
@@ -182,70 +191,81 @@ class MultiChoicePreference extends Dial
         if (!positiveResult) {
             // user cancelled; reset checkbox values to their previous state
             mValues = mPrevValues.clone();
             return;
         } else {
             mPrevValues = mValues.clone();
         }
 
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mEntryKeys.length; i++) {
-                    String key = mEntryKeys[i].toString();
-                    persistBoolean(key, mValues[i]);
-                }
-            }
-        });
+        if (!callChangeListener(getValues())) {
+            return;
+        }
+
+        persist();
     }
 
-    protected boolean persistBoolean(String key, boolean value) {
+    /* Persists the current data stored by this pref to SharedPreferences. */
+    public boolean persist() {
         if (isPersistent()) {
-            if (value == getPersistedBoolean(!value)) {
-                // It's already there, so the same as persisting
-                return true;
-            }
-            
-            GeckoSharedPrefs.forApp(getContext())
-                            .edit().putBoolean(key, value).commit();
-            return true;
+            final SharedPreferences.Editor edit = GeckoSharedPrefs.forProfile(getContext()).edit();
+            final boolean res = persist(edit);
+            edit.apply();
+            return res;
         }
+
         return false;
     }
 
-    protected boolean getPersistedBoolean(String key, boolean defaultReturnValue) {
-        if (!isPersistent())
-            return defaultReturnValue;
-        
-        return GeckoSharedPrefs.forApp(getContext())
-                               .getBoolean(key, defaultReturnValue);
+    /* Internal persist method. Take an edit so that multiple prefs can be persisted in a single commit. */
+    protected boolean persist(SharedPreferences.Editor edit) {
+        if (isPersistent()) {
+            Set<String> vals = getValues();
+            PrefUtils.putStringSet(edit, getKey(), vals);
+            return true;
+        }
+
+        return false;
+    }
+
+    /* Returns a list of EntryValues that are currently enabled. */
+    public Set<String> getPersistedStrings(Set<String> defaultVal) {
+        if (!isPersistent()) {
+            return defaultVal;
+        }
+
+        final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
+        return PrefUtils.getStringSet(prefs, getKey(), defaultVal);
     }
 
     /**
      * Loads persistent prefs from shared preferences. If the preferences
      * aren't persistent or haven't yet been stored, they will be set to their
      * initial values.
      */
-    private void loadPersistedValues() {
-        if (mEntryKeys == null || mInitialValues == null)
-            return;
+    protected void loadPersistedValues() {
+        final int entryCount = mInitialValues.length;
+        mValues = new boolean[entryCount];
 
-        final int entryCount = mEntryKeys.length;
-        if (entryCount != mEntries.length || entryCount != mInitialValues.length) {
+        if (entryCount != mEntries.length || entryCount != mEntryValues.length) {
             throw new IllegalStateException(
-                    "MultiChoicePreference entryKeys and initialValues arrays must be the same length");
+                    "MultiChoicePreference entryValues and initialValues arrays must be the same length");
         }
 
-        mValues = new boolean[entryCount];
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
+                final Set<String> stringVals = getPersistedStrings(null);
+
                 for (int i = 0; i < entryCount; i++) {
-                    String key = mEntryKeys[i].toString();
-                    boolean initialValue = mInitialValues[i].equals("true");
-                    mValues[i] = getPersistedBoolean(key, initialValue);
+                    if (stringVals != null) {
+                        mValues[i] = stringVals.contains(mEntryValues[i]);
+                    } else {
+                        final boolean defaultVal = mInitialValues[i].equals("true");
+                        mValues[i] = defaultVal;
+                    }
                 }
+
                 mPrevValues = mValues.clone();
             }
         });
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/MultiPrefMultiChoicePreference.java
@@ -0,0 +1,116 @@
+/* -*- 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.preferences;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.content.SharedPreferences;
+import android.widget.Button;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import java.util.Set;
+
+/* Provides backwards compatibility for some old multi-choice pref types used by Gecko.
+ * This will import the old data from the old prefs the first time it is run.
+ */
+class MultiPrefMultiChoicePreference extends MultiChoicePreference {
+    private static final String LOGTAG = "GeckoMultiPrefPreference";
+    private static final String IMPORT_SUFFIX = "_imported_";
+    private final CharSequence[] keys;
+
+    public MultiPrefMultiChoicePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiPrefMultiChoicePreference);
+        keys = a.getTextArray(R.styleable.MultiPrefMultiChoicePreference_entryKeys);
+        a.recycle();
+
+        loadPersistedValues();
+    }
+
+    // Helper method for reading a boolean pref.
+    private boolean getPersistedBoolean(SharedPreferences prefs, String key, boolean defaultReturnValue) {
+        if (!isPersistent()) {
+            return defaultReturnValue;
+        }
+
+        return prefs.getBoolean(key, defaultReturnValue);
+    }
+
+    // Overridden to do a one time import for the old preference type to the new one.
+    @Override
+    protected synchronized void loadPersistedValues() {
+        // This will load the new pref if it exists.
+        super.loadPersistedValues();
+
+        // First check if we've already done the import the old data. If so, nothing to load.
+        final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
+        final boolean imported = getPersistedBoolean(prefs, getKey() + IMPORT_SUFFIX, false);
+        if (imported) {
+            return;
+        }
+
+        // Load the data we'll need to find the old style prefs
+        final CharSequence[] init = getInitialValues();
+        final CharSequence[] entries = getEntries();
+        if (keys == null || init == null) {
+            return;
+        }
+
+        final int entryCount = keys.length;
+        if (entryCount != entries.length || entryCount != init.length) {
+            throw new IllegalStateException("MultiChoicePreference entryKeys and initialValues arrays must be the same length");
+        }
+
+        // Now iterate through the entries on a background thread.
+        final SharedPreferences.Editor edit = prefs.edit();
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    // Use one editor to batch as many changes as we can.
+                    for (int i = 0; i < entryCount; i++) {
+                        String key = keys[i].toString();
+                        boolean initialValue = "true".equals(init[i]);
+                        boolean val = getPersistedBoolean(prefs, key, initialValue);
+
+                        // Save the pref and remove the old preference.
+                        setValue(i, val);
+                        edit.remove(key);
+                    }
+
+                    persist(edit);
+                    edit.putBoolean(getKey() + IMPORT_SUFFIX, true);
+                    edit.apply();
+                } catch(Exception ex) {
+                    Log.i(LOGTAG, "Err", ex);
+                }
+            }
+        });
+    }
+
+
+    @Override
+    public void onClick(DialogInterface dialog, int which, boolean val) {
+        // enable positive button only if at least one item is checked
+        boolean enabled = false;
+        final Set<String> values = getValues();
+
+        enabled = (values.size() > 0);
+        final Button button = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
+        if (button.isEnabled() != enabled) {
+            button.setEnabled(enabled);
+        }
+    }
+
+}
--- a/mobile/android/base/preferences/PrivateDataPreference.java
+++ b/mobile/android/base/preferences/PrivateDataPreference.java
@@ -8,51 +8,52 @@ package org.mozilla.gecko.preferences;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.Set;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
 
-class PrivateDataPreference extends MultiChoicePreference {
+class PrivateDataPreference extends MultiPrefMultiChoicePreference {
     private static final String LOGTAG = "GeckoPrivateDataPreference";
     private static final String PREF_KEY_PREFIX = "private.data.";
 
     public PrivateDataPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
     protected void onDialogClosed(boolean positiveResult) {
         super.onDialogClosed(positiveResult);
 
-        if (!positiveResult)
+        if (!positiveResult) {
             return;
+        }
 
         Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.DIALOG, "settings");
 
-        CharSequence keys[] = getEntryKeys();
-        boolean values[] = getValues();
-        JSONObject json = new JSONObject();
+        final Set<String> values = getValues();
+        final JSONObject json = new JSONObject();
 
-        for (int i = 0; i < keys.length; i++) {
+        for (String value : values) {
             // Privacy pref checkbox values are stored in Android prefs to
             // remember their check states. The key names are private.data.X,
             // where X is a string from Gecko sanitization. This prefix is
             // removed here so we can send the values to Gecko, which then does
             // the sanitization for each key.
-            String key = keys[i].toString().substring(PREF_KEY_PREFIX.length());
-            boolean value = values[i];
+            final String key = value.substring(PREF_KEY_PREFIX.length());
             try {
-                json.put(key, value);
+                json.put(key, true);
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSON error", e);
             }
         }
 
         // clear private data in gecko
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Sanitize:ClearData", json.toString()));
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/preference_checkbox.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
+     inside android.R.layout.preference. -->
+<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/checkbox"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:focusable="false"
+    android:clickable="false" />
--- a/mobile/android/base/resources/values/arrays.xml
+++ b/mobile/android/base/resources/values/arrays.xml
@@ -64,54 +64,78 @@
       <item>1</item>
       <item>2</item>
       <item>0</item>
     </string-array>
     <string-array name="pref_import_android_entries">
         <item>@string/bookmarks_title</item>
         <item>@string/history_title</item>
     </string-array>
-    <string-array name="pref_import_android_values">
+    <string-array name="pref_import_android_defaults">
         <item>true</item>
         <item>true</item>
     </string-array>
+    <string-array name="pref_import_android_values">
+        <item>android_import.data.bookmarks</item>
+        <item>android_import.data.history</item>
+    </string-array>
     <string-array name="pref_import_android_keys">
         <item>android_import.data.bookmarks</item>
         <item>android_import.data.history</item>
     </string-array>
     <string-array name="pref_private_data_entries">
         <item>@string/pref_private_data_history2</item>
         <item>@string/pref_private_data_downloadFiles2</item>
         <item>@string/pref_private_data_formdata</item>
         <item>@string/pref_private_data_cookies2</item>
         <item>@string/pref_private_data_passwords</item>
         <item>@string/pref_private_data_cache</item>
         <item>@string/pref_private_data_offlineApps</item>
         <item>@string/pref_private_data_siteSettings</item>
     </string-array>
-    <string-array name="pref_private_data_values">
+    <string-array name="pref_private_data_defaults">
         <item>true</item>
         <item>true</item>
         <item>true</item>
         <item>true</item>
         <item>true</item>
         <item>true</item>
         <item>true</item>
         <item>true</item>
     </string-array>
+    <string-array name="pref_private_data_values">
+        <item>private.data.history</item>
+        <item>private.data.downloadFiles</item>
+        <item>private.data.formdata</item>
+        <item>private.data.cookies_sessions</item>
+        <item>private.data.passwords</item>
+        <item>private.data.cache</item>
+        <item>private.data.offlineApps</item>
+        <item>private.data.siteSettings</item>
+    </string-array>
     <string-array name="pref_private_data_keys">
         <item>private.data.history</item>
         <item>private.data.downloadFiles</item>
         <item>private.data.formdata</item>
         <item>private.data.cookies_sessions</item>
         <item>private.data.passwords</item>
         <item>private.data.cache</item>
         <item>private.data.offlineApps</item>
         <item>private.data.siteSettings</item>
     </string-array>
+    <string-array name="pref_clear_on_exit_defaults">
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+        <item>false</item>
+    </string-array>
     <string-array name="pref_restore_entries">
         <item>@string/pref_restore_always</item>
         <item>@string/pref_restore_quit</item>
     </string-array>
     <string-array name="pref_restore_values">
         <item>always</item>
         <item>quit</item>
     </string-array>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -79,18 +79,22 @@
     </declare-styleable>
 
     <declare-styleable name="FlowLayout">
         <attr name="spacing" format="dimension"/>
     </declare-styleable>
 
     <declare-styleable name="MultiChoicePreference">
         <attr name="entries" format="string"/>
+        <attr name="entryValues" format="string"/>
+        <attr name="initialValues" format="string"/>
+    </declare-styleable>
+
+    <declare-styleable name="MultiPrefMultiChoicePreference">
         <attr name="entryKeys" format="string"/>
-        <attr name="initialValues" format="string"/>
     </declare-styleable>
 
     <declare-styleable name="BrowserToolbarCurve">
         <attr name="curveTowards">
             <flag name="none" value="0x00" />
             <flag name="left" value="0x01" />
             <flag name="right" value ="0x02" />
         </attr>
--- a/mobile/android/base/resources/xml-v11/preferences_customize.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml
@@ -28,18 +28,19 @@
                     android:defaultValue="quit"
                     android:entries="@array/pref_restore_entries"
                     android:entryValues="@array/pref_restore_values"
                     android:persistent="true" />
 
     <org.mozilla.gecko.preferences.AndroidImportPreference
                   android:key="android.not_a_preference.import_android"
                   gecko:entries="@array/pref_import_android_entries"
+                  gecko:entryValues="@array/pref_import_android_values"
+                  gecko:initialValues="@array/pref_import_android_defaults"
                   gecko:entryKeys="@array/pref_import_android_keys"
-                  gecko:initialValues="@array/pref_import_android_values"
                   android:title="@string/pref_import_android"
                   android:positiveButtonText="@string/bookmarkhistory_button_import"
                   android:negativeButtonText="@string/button_cancel"
                   android:persistent="false" />
 
    <ListPreference android:key="app.update.autodownload"
                    android:title="@string/pref_update_autodownload"
                    android:entries="@array/pref_update_autodownload_entries"
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
@@ -35,18 +35,18 @@
                     android:defaultValue="quit"
                     android:entries="@array/pref_restore_entries"
                     android:entryValues="@array/pref_restore_values"
                     android:persistent="true" />
 
     <org.mozilla.gecko.preferences.AndroidImportPreference
             android:key="android.not_a_preference.import_android"
             gecko:entries="@array/pref_import_android_entries"
-            gecko:entryKeys="@array/pref_import_android_keys"
-            gecko:initialValues="@array/pref_import_android_values"
+            gecko:entryValues="@array/pref_import_android_values"
+            gecko:initialValues="@array/pref_import_android_defaults"
             android:title="@string/pref_import_android"
             android:positiveButtonText="@string/bookmarkhistory_button_import"
             android:negativeButtonText="@string/button_cancel"
             android:persistent="false" />
 
     <ListPreference android:key="app.update.autodownload"
                     android:title="@string/pref_update_autodownload"
                     android:entries="@array/pref_update_autodownload_entries"
--- a/mobile/android/base/resources/xml/preferences_customize.xml
+++ b/mobile/android/base/resources/xml/preferences_customize.xml
@@ -36,18 +36,19 @@
                     android:entries="@array/pref_restore_entries"
                     android:entryValues="@array/pref_restore_values"
                     android:persistent="true" />
 
 
     <org.mozilla.gecko.preferences.AndroidImportPreference
                   android:key="android.not_a_preference.import_android"
                   gecko:entries="@array/pref_import_android_entries"
+                  gecko:entryValues="@array/pref_import_android_values"
+                  gecko:initialValues="@array/pref_import_android_defaults"
                   gecko:entryKeys="@array/pref_import_android_keys"
-                  gecko:initialValues="@array/pref_import_android_values"
                   android:title="@string/pref_import_android"
                   android:positiveButtonText="@string/bookmarkhistory_button_import"
                   android:negativeButtonText="@string/button_cancel"
                   android:persistent="false" />
 
 
    <ListPreference android:key="app.update.autodownload"
                    android:title="@string/pref_update_autodownload"
--- a/mobile/android/base/resources/xml/preferences_privacy.xml
+++ b/mobile/android/base/resources/xml/preferences_privacy.xml
@@ -26,20 +26,38 @@
                         android:persistent="false" />
 
     <CheckBoxPreference android:key="privacy.masterpassword.enabled"
                         android:title="@string/pref_use_master_password"
                         android:defaultValue="false"
                         android:persistent="false" />
 
     <!-- keys prefixed with "android.not_a_preference." are not synced with Gecko -->
-    <org.mozilla.gecko.preferences.PrivateDataPreference
-                        android:key="android.not_a_preference.privacy.clear"
-                        android:title="@string/pref_clear_private_data"
-                        android:persistent="true"
-                        android:positiveButtonText="@string/button_clear_data"
-                        gecko:entries="@array/pref_private_data_entries"
-                        gecko:entryKeys="@array/pref_private_data_keys"
-                        gecko:initialValues="@array/pref_private_data_values" />
+    <PreferenceCategory android:title="@string/pref_clear_private_data_category">
+
+        <org.mozilla.gecko.preferences.PrivateDataPreference
+                            android:key="android.not_a_preference.privacy.clear"
+                            android:title="@string/pref_clear_private_data"
+                            android:persistent="true"
+                            android:positiveButtonText="@string/button_clear_data"
+                            gecko:entries="@array/pref_private_data_entries"
+                            gecko:entryValues="@array/pref_private_data_values"
+                            gecko:entryKeys="@array/pref_private_data_keys"
+                            gecko:initialValues="@array/pref_private_data_defaults" />
+
+        <!-- This pref is persisted in both Gecko and Java -->
+        <org.mozilla.gecko.preferences.ListCheckboxPreference
+                            android:key="android.not_a_preference.history.clear_on_exit"
+                            gecko:entries="@array/pref_private_data_entries"
+                            gecko:entryValues="@array/pref_private_data_values"
+                            gecko:initialValues="@array/pref_clear_on_exit_defaults"
+
+                            android:title="@string/pref_clear_on_exit_title"
+                            android:summary="@string/pref_clear_on_exit_summary"
+
+                            android:dialogTitle="@string/pref_clear_on_exit_dialog_title"
+                            android:positiveButtonText="@string/button_set"/>
+
+    </PreferenceCategory>
 
 </PreferenceScreen>
 
 
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -160,17 +160,21 @@
   <string name="pref_donottrack_menu">&pref_donottrack_menu;</string>
   <string name="pref_donottrack_disallow_tracking">&pref_donottrack_disallow_tracking;</string>
   <string name="pref_donottrack_allow_tracking">&pref_donottrack_allow_tracking;</string>
   <string name="pref_donottrack_no_pref">&pref_donottrack_no_pref;</string>
 
   <string name="pref_char_encoding">&pref_char_encoding;</string>
   <string name="pref_char_encoding_on">&pref_char_encoding_on;</string>
   <string name="pref_char_encoding_off">&pref_char_encoding_off;</string>
-  <string name="pref_clear_private_data">&pref_clear_private_data;</string>
+  <string name="pref_clear_private_data">&pref_clear_private_data2;</string>
+  <string name="pref_clear_private_data_category">&pref_clear_private_data_category;</string>
+  <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title;</string>
+  <string name="pref_clear_on_exit_summary">&pref_clear_on_exit_summary;</string>
+  <string name="pref_clear_on_exit_dialog_title">&pref_clear_on_exit_dialog_title;</string>
   <string name="pref_plugins">&pref_plugins;</string>
   <string name="pref_plugins_enabled">&pref_plugins_enabled;</string>
   <string name="pref_plugins_tap_to_play">&pref_plugins_tap_to_play;</string>
   <string name="pref_plugins_disabled">&pref_plugins_disabled;</string>
   <string name="pref_text_size">&pref_text_size;</string>
   <string name="pref_font_size_tiny">&pref_font_size_tiny;</string>
   <string name="pref_font_size_small">&pref_font_size_small;</string>
   <string name="pref_font_size_medium">&pref_font_size_medium;</string>
--- a/mobile/android/base/tests/StringHelper.java
+++ b/mobile/android/base/tests/StringHelper.java
@@ -100,17 +100,16 @@ public class StringHelper {
     public static final String DISPLAY_SECTION_LABEL = "Display";
     public static final String PRIVACY_SECTION_LABEL = "Privacy";
     public static final String MOZILLA_SECTION_LABEL = "Mozilla";
     public static final String DEVELOPER_TOOLS_SECTION_LABEL = "Developer tools";
 
     // Option labels
     // Customize
     public static final String SYNC_LABEL = "Sync";
-    public static final String SEARCH_SETTINGS_LABEL = "Search settings";
     public static final String IMPORT_FROM_ANDROID_LABEL = "Import from Android";
     public static final String TABS_LABEL = "Tabs";
 
     // Display
     public static final String TEXT_SIZE_LABEL = "Text size";
     public static final String TITLE_BAR_LABEL = "Title bar";
     public static final String TEXT_REFLOW_LABEL = "Text reflow";
     public static final String CHARACTER_ENCODING_LABEL = "Character encoding";
@@ -120,17 +119,17 @@ public class StringHelper {
     public static final String SHOW_PAGE_TITLE_LABEL = "Show page title";
     public static final String SHOW_PAGE_ADDRESS_LABEL = "Show page address";
 
     // Privacy
     public static final String TRACKING_LABEL = "Tracking";
     public static final String COOKIES_LABEL = "Cookies";
     public static final String REMEMBER_PASSWORDS_LABEL = "Remember passwords";
     public static final String MASTER_PASWSWORD_LABEL = "Use master password";
-    public static final String CLEAR_PRIVATE_DATA_LABEL = "Clear private data";
+    public static final String CLEAR_PRIVATE_DATA_LABEL = "Clear now";
 
     // Mozilla
     public static final String ABOUT_LABEL = "About (Fennec|Nightly|Aurora|Firefox Beta|Firefox)";
     public static final String FAQS_LABEL = "FAQs";
     public static final String FEEDBACK_LABEL = "Give feedback";
     public static final String PRODUCT_ANNOUNCEMENTS_LABEL = "Show product announcements";
     public static final String LOCATION_SERVICES_LABEL = "Mozilla location services";
     public static final String HELTH_REPORT_LABEL = "(Fennec|Nightly|Aurora|Firefox Beta|Firefox) Health Report";
--- a/mobile/android/base/tests/testSettingsMenuItems.java
+++ b/mobile/android/base/tests/testSettingsMenuItems.java
@@ -24,61 +24,61 @@ public class testSettingsMenuItems exten
      *
      * where defaultValue is optional, and there can be multiple options.
      *
      * These menu items are the ones that are always present - to test menu items that differ
      * based on build (e.g., release vs. nightly), add the items in <code>addConditionalSettings</code>.
      */
 
     // Customize menu items.
-    String[] PATH_CUSTOMIZE = { "Customize" };
+    String[] PATH_CUSTOMIZE = { StringHelper.CUSTOMIZE_SECTION_LABEL };
     String[][] OPTIONS_CUSTOMIZE = {
         { "Home" },
         { "Search", "", "Show search suggestions", "Installed search engines"},
-        { "Tabs", "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME },
-        { "Import from Android", "", "Bookmarks", "History", "Import" },
+        { StringHelper.TABS_LABEL, "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME },
+        { StringHelper.IMPORT_FROM_ANDROID_LABEL, "", "Bookmarks", "History", "Import" },
     };
 
     // Home panel menu items.
-    String[] PATH_HOME = { "Customize", "Home" };
+    String[] PATH_HOME = { StringHelper.CUSTOMIZE_SECTION_LABEL, "Home" };
     String[][] OPTIONS_HOME = {
       { "Panels" },
       { "Automatic updates", "Enabled", "Enabled", "Only over Wi-Fi" },
     };
 
     // Display menu items.
-    String[] PATH_DISPLAY = { "Display" };
+    String[] PATH_DISPLAY = { StringHelper.DISPLAY_SECTION_LABEL };
     String[][] OPTIONS_DISPLAY = {
-        { "Text size" },
-        { "Title bar", "Show page title", "Show page title", "Show page address" },
+        { StringHelper.TEXT_SIZE_LABEL },
+        { StringHelper.TITLE_BAR_LABEL, "Show page title", "Show page title", "Show page address" },
         { "Advanced" },
-        { "Character encoding", "Don't show menu", "Show menu", "Don't show menu" },
-        { "Plugins", "Tap to play", "Enabled", "Tap to play", "Disabled" },
+        { StringHelper.CHARACTER_ENCODING_LABEL, "Don't show menu", "Show menu", "Don't show menu" },
+        { StringHelper.PLUGINS_LABEL, "Tap to play", "Enabled", "Tap to play", "Disabled" },
     };
 
     // Privacy menu items.
-    String[] PATH_PRIVACY = { "Privacy" };
+    String[] PATH_PRIVACY = { StringHelper.PRIVACY_SECTION_LABEL };
     String[][] OPTIONS_PRIVACY = {
-        { "Tracking", "Do not tell sites anything about my tracking preferences", "Tell sites that I do not want to be tracked", "Tell sites that I want to be tracked", "Do not tell sites anything about my tracking preferences" },
-        { "Cookies", "Enabled", "Enabled, excluding 3rd party", "Disabled" },
-        { "Remember passwords" },
-        { "Use master password" },
-        { "Clear private data", "", "Browsing history", "Downloads", "Form & search history", "Cookies & active logins", "Saved passwords", "Cache", "Offline website data", "Site settings", "Clear data" },
+        { StringHelper.TRACKING_LABEL, "Do not tell sites anything about my tracking preferences", "Tell sites that I do not want to be tracked", "Tell sites that I want to be tracked", "Do not tell sites anything about my tracking preferences" },
+        { StringHelper.COOKIES_LABEL, "Enabled", "Enabled, excluding 3rd party", "Disabled" },
+        { StringHelper.REMEMBER_PASSWORDS_LABEL },
+        { StringHelper.MASTER_PASWSWORD_LABEL },
+        { StringHelper.CLEAR_PRIVATE_DATA_LABEL, "", "Browsing history", "Downloads", "Form & search history", "Cookies & active logins", "Saved passwords", "Cache", "Offline website data", "Site settings", "Clear data" },
     };
 
     // Mozilla/vendor menu items.
-    String[] PATH_MOZILLA = { "Mozilla" };
+    String[] PATH_MOZILLA = { StringHelper.MOZILLA_SECTION_LABEL };
     String[][] OPTIONS_MOZILLA = {
         { "About " + BRAND_NAME },
-        { "FAQs" },
-        { "Give feedback" },
-        { "Show product announcements" },
+        { StringHelper.FAQS_LABEL },
+        { StringHelper.FEEDBACK_LABEL },
+        { StringHelper.PRODUCT_ANNOUNCEMENTS_LABEL },
         { "Data choices" },
         { BRAND_NAME + " Health Report", "Shares data with Mozilla about your browser health and helps you understand your browser performance" },
-        { "View my Health Report" },
+        { StringHelper.MY_HEALTH_REPORT_LABEL },
     };
 
     /*
      * This sets up a hierarchy of settings to test.
      *
      * The keys are String arrays representing the path through menu items
      * (the single-item arrays being top-level categories), and each value
      * is a List of menu items contained within each category.
--- a/mobile/android/base/util/JSONUtils.java
+++ b/mobile/android/base/util/JSONUtils.java
@@ -1,24 +1,23 @@
 /* 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.util;
 
-import java.util.UUID;
-
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.os.Bundle;
 import android.util.Log;
 
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.UUID;
 
 public final class JSONUtils {
     private static final String LOGTAG = "GeckoJSONUtils";
 
     private JSONUtils() {}
 
     public static UUID getUUID(String name, JSONObject json) {
@@ -46,9 +45,25 @@ public final class JSONUtils {
                 json.put(key, bundle.get(key));
             } catch (JSONException e) {
                 Log.w(LOGTAG, "Error building JSON response.", e);
             }
         }
 
         return json;
     }
+
+    // Handles conversions between a JSONArray and a Set<String>
+    public static Set<String> parseStringSet(JSONArray json) {
+        final Set<String> ret = new HashSet<String>();
+
+        for (int i = 0; i < json.length(); i++) {
+            try {
+                ret.add(json.getString(i));
+            } catch(JSONException ex) {
+                Log.i(LOGTAG, "Error parsing json", ex);
+            }
+        }
+
+        return ret;
+    }
+
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/util/PrefUtils.java
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util;
+
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+
+public class PrefUtils {
+    private static final String LOGTAG = "GeckoPrefUtils";
+
+    // Cross version compatible way to get a string set from a pref
+    public static Set<String> getStringSet(final SharedPreferences prefs,
+                                           final String key,
+                                           final Set<String> defaultVal) {
+        if (!prefs.contains(key)) {
+            return defaultVal;
+        }
+
+        if (Build.VERSION.SDK_INT < 11) {
+            return getFromJSON(prefs, key);
+        }
+
+        // If this is Android version >= 11, try to use a Set<String>.
+        try {
+            return prefs.getStringSet(key, new HashSet<String>());
+        } catch(ClassCastException ex) {
+            // A ClassCastException means we've upgraded from a pre-v11 Android to a new one
+            final Set<String> val = getFromJSON(prefs, key);
+            SharedPreferences.Editor edit = prefs.edit();
+            putStringSet(edit, key, val).apply();
+            return val;
+        }
+    }
+
+    private static Set<String> getFromJSON(SharedPreferences prefs, String key) {
+        try {
+            final String val = prefs.getString(key, "[]");
+            return JSONUtils.parseStringSet(new JSONArray(val));
+        } catch(JSONException ex) {
+            Log.i(LOGTAG, "Unable to parse JSON", ex);
+        }
+
+        return new HashSet<String>();
+    }
+
+    // Cross version compatible way to save a string set to a pref.
+    // NOTE: The editor that is passed in will not commit the transaction for you. It is up to callers to commit
+    //       when they are done with any other changes to the database.
+    public static SharedPreferences.Editor putStringSet(final SharedPreferences.Editor edit,
+                                    final String key,
+                                    final Set<String> vals) {
+        if (Build.VERSION.SDK_INT < 11) {
+            final JSONArray json = new JSONArray(vals);
+            edit.putString(key, json.toString()).apply();
+        } else {
+            edit.putStringSet(key, vals).apply();
+        }
+
+        return edit;
+    }
+}
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1108,17 +1108,17 @@ var BrowserApp = {
   _handleTabSelected: function _handleTabSelected(aTab) {
     this.selectedTab = aTab;
 
     let evt = document.createEvent("UIEvents");
     evt.initUIEvent("TabSelect", true, false, window, null);
     aTab.browser.dispatchEvent(evt);
   },
 
-  quit: function quit() {
+  quit: function quit(aClear = {}) {
     // Figure out if there's at least one other browser window around.
     let lastBrowser = true;
     let e = Services.wm.getEnumerator("navigator:browser");
     while (e.hasMoreElements() && lastBrowser) {
       let win = e.getNext();
       if (!win.closed && win != window)
         lastBrowser = false;
     }
@@ -1128,18 +1128,20 @@ var BrowserApp = {
       let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
       Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null);
       if (closingCanceled.data)
         return;
 
       Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null);
     }
 
-    window.QueryInterface(Ci.nsIDOMChromeWindow).minimize();
-    window.close();
+    BrowserApp.sanitize(aClear, function() {
+      window.QueryInterface(Ci.nsIDOMChromeWindow).minimize();
+      window.close();
+    });
   },
 
   saveAsPDF: function saveAsPDF(aBrowser) {
     // Create the final destination file location
     let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null);
     fileName = fileName.trim() + ".pdf";
 
     let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
@@ -1383,43 +1385,60 @@ var BrowserApp = {
         let pref = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString);
         pref.data = json.value;
         Services.prefs.setComplexValue(json.name, Ci.nsISupportsString, pref);
         break;
       }
     }
   },
 
-  sanitize: function (aItems) {
-    let json = JSON.parse(aItems);
+  sanitize: function (aItems, callback) {
+    if (!aItems) {
+      return;
+    }
+
     let success = true;
 
-    for (let key in json) {
-      if (!json[key])
+    for (let key in aItems) {
+      if (!aItems[key])
         continue;
 
-      try {
-        switch (key) {
-          case "cookies_sessions":
-            Sanitizer.clearItem("cookies");
-            Sanitizer.clearItem("sessions");
-            break;
-          default:
-            Sanitizer.clearItem(key);
-        }
-      } catch (e) {
-        dump("sanitize error: " + e);
-        success = false;
-      }
-    }
-
-    sendMessageToJava({
-      type: "Sanitize:Finished",
-      success: success
-    });
+      key = key.replace("private.data.", "");
+
+      var promises = [];
+      switch (key) {
+        case "cookies_sessions":
+          promises.push(Sanitizer.clearItem("cookies"));
+          promises.push(Sanitizer.clearItem("sessions"));
+          break;
+        default:
+          promises.push(Sanitizer.clearItem(key));
+      }
+    }
+
+    Promise.all(promises).then(function() {
+      sendMessageToJava({
+        type: "Sanitize:Finished",
+        success: true
+      });
+
+      if (callback) {
+        callback();
+      }
+    }).catch(function(err) {
+      sendMessageToJava({
+        type: "Sanitize:Finished",
+        error: err,
+        success: false
+      });
+
+      if (callback) {
+        callback();
+      }
+    })
   },
 
   getFocusedInput: function(aBrowser, aOnlyInputElements = false) {
     if (!aBrowser)
       return null;
 
     let doc = aBrowser.contentDocument;
     if (!doc)
@@ -1596,17 +1615,18 @@ var BrowserApp = {
           type: "Search:Keyword",
           identifier: engine.identifier,
           name: engine.name,
           query: aData
         });
         break;
 
       case "Browser:Quit":
-        this.quit();
+        Services.console.logStringMessage(aData);
+        this.quit(JSON.parse(aData));
         break;
 
       case "SaveAs:PDF":
         this.saveAsPDF(browser);
         break;
 
       case "Preferences:Set":
         this.setPreferences(aData);
@@ -1614,17 +1634,17 @@ var BrowserApp = {
 
       case "ScrollTo:FocusedInput":
         // these messages come from a change in the viewable area and not user interaction
         // we allow scrolling to the selected input, but not zooming the page
         this.scrollToFocusedInput(browser, false);
         break;
 
       case "Sanitize:ClearData":
-        this.sanitize(aData);
+        this.sanitize(JSON.parse(aData));
         break;
 
       case "FullScreen:Exit":
         browser.contentDocument.mozCancelFullScreen();
         break;
 
       case "Viewport:Change":
         if (this.isBrowserContentDocumentDisplayed())
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -59,115 +59,137 @@ Sanitizer.prototype = {
       item.clear();
     }
   },
 
   items: {
     cache: {
       clear: function ()
       {
-        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
-        try {
-          cache.clear();
-        } catch(er) {}
+        return new Promise(function(resolve, reject) {
+          var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+          try {
+            cache.clear();
+          } catch(er) {}
 
-        let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                                                         .getImgCacheForDocument(null);
-        try {
-          imageCache.clearCache(false); // true=chrome, false=content
-        } catch(er) {}
+          let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                                                           .getImgCacheForDocument(null);
+          try {
+            imageCache.clearCache(false); // true=chrome, false=content
+          } catch(er) {}
+
+          resolve();
+        });
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     cookies: {
       clear: function ()
       {
-        Services.cookies.removeAll();
+        return new Promise(function(resolve, reject) {
+          Services.cookies.removeAll();
+          resolve();
+        });
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     siteSettings: {
       clear: function ()
       {
-        // Clear site-specific permissions like "Allow this site to open popups"
-        Services.perms.removeAll();
+        return new Promise(function(resolve, reject) {
+          // Clear site-specific permissions like "Allow this site to open popups"
+          Services.perms.removeAll();
+
+          // Clear site-specific settings like page-zoom level
+          Cc["@mozilla.org/content-pref/service;1"]
+            .getService(Ci.nsIContentPrefService2)
+            .removeAllDomains(null);
 
-        // Clear site-specific settings like page-zoom level
-        Cc["@mozilla.org/content-pref/service;1"]
-          .getService(Ci.nsIContentPrefService2)
-          .removeAllDomains(null);
+          // Clear "Never remember passwords for this site", which is not handled by
+          // the permission manager
+          var hosts = Services.logins.getAllDisabledHosts({})
+          for (var host of hosts) {
+            Services.logins.setLoginSavingEnabled(host, true);
+          }
 
-        // Clear "Never remember passwords for this site", which is not handled by
-        // the permission manager
-        var hosts = Services.logins.getAllDisabledHosts({})
-        for (var host of hosts) {
-          Services.logins.setLoginSavingEnabled(host, true);
-        }
+          resolve();
+        });
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     offlineApps: {
       clear: function ()
       {
-        var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
-        var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
-        try {
-          appCacheStorage.asyncEvictStorage(null);
-        } catch(er) {}
+        return new Promise(function(resolve, reject) {
+          var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+          var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+          try {
+            appCacheStorage.asyncEvictStorage(null);
+          } catch(er) {}
+
+          resolve();
+        });
       },
 
       get canClear()
       {
           return true;
       }
     },
 
     history: {
       clear: function ()
       {
-        sendMessageToJava({ type: "Sanitize:ClearHistory" });
+        return new Promise(function(resolve, reject) {
+          sendMessageToJava({ type: "Sanitize:ClearHistory" }, function() {
+            try {
+              Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+            }
+            catch (e) { }
 
-        try {
-          Services.obs.notifyObservers(null, "browser:purge-session-history", "");
-        }
-        catch (e) { }
+            try {
+              var predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
+              predictor.reset();
+            } catch (e) { }
 
-        try {
-          var predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
-          predictor.reset();
-        } catch (e) { }
+            resolve();
+          });
+        });
       },
 
       get canClear()
       {
         // bug 347231: Always allow clearing history due to dependencies on
         // the browser:purge-session-history notification. (like error console)
         return true;
       }
     },
 
     formdata: {
       clear: function ()
       {
-        FormHistory.update({ op: "remove" });
+        return new Promise(function(resolve, reject) {
+          FormHistory.update({ op: "remove" });
+          resolve();
+        });
       },
 
       canClear: function (aCallback)
       {
         let count = 0;
         let countDone = {
           handleResult: function(aResult) { count = aResult; },
           handleError: function(aError) { Cu.reportError(aError); },
@@ -175,56 +197,66 @@ Sanitizer.prototype = {
         };
         FormHistory.count({}, countDone);
       }
     },
 
     downloadFiles: {
       clear: function ()
       {
-        downloads.iterate(function (dl) {
-          // Delete the downloaded files themselves
-          let f = dl.targetFile;
-          if (f.exists()) {
-            f.remove(false);
-          }
+        return new Promise(function(resolve, reject) {
+          downloads.iterate(function (dl) {
+            // Delete the downloaded files themselves
+            let f = dl.targetFile;
+            if (f.exists()) {
+              f.remove(false);
+            }
 
-          // Also delete downloads from history
-          dl.remove();
+            // Also delete downloads from history
+            dl.remove();
+          });
+          resolve();
         });
       },
 
       get canClear()
       {
         return downloads.canClear;
       }
     },
 
     passwords: {
       clear: function ()
       {
-        Services.logins.removeAllLogins();
+        return new Promise(function(resolve, reject) {
+          Services.logins.removeAllLogins();
+          resolve();
+        });
       },
 
       get canClear()
       {
         let count = Services.logins.countLogins("", "", ""); // count all logins
         return (count > 0);
       }
     },
 
     sessions: {
       clear: function ()
       {
-        // clear all auth tokens
-        var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
-        sdr.logoutAndTeardown();
+        return new Promise(function(resolve, reject) {
+          // clear all auth tokens
+          var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
+          sdr.logoutAndTeardown();
 
-        // clear FTP and plain HTTP auth sessions
-        Services.obs.notifyObservers(null, "net:clear-active-logins", null);
+          // clear FTP and plain HTTP auth sessions
+          Services.obs.notifyObservers(null, "net:clear-active-logins", null);
+
+          resolve();
+        });
       },
 
       get canClear()
       {
         return true;
       }
     }
   }