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 195069 00b81ba0835a987a2e46805a3b6c24387351c02a
parent 195068 0773f2e66747884b5b6c36eb184ed69731e927bb
child 195070 d407b2eb63213272418b49bfee87631d60f213df
push id27168
push userryanvm@gmail.com
push dateSun, 20 Jul 2014 22:30:25 +0000
treeherdermozilla-central@e9cdcf646d1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersliuche, bnicholson, mcomella
bugs1001309
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
       }
     }
   }