Extend "Clear Private Data" with time period option. b=453440, r=mconnor ui-r=beltzner a=blocking-firefox3.1
authorJohnathan Nightingale <johnath@mozilla.com>
Wed, 05 Nov 2008 19:57:00 -0800
changeset 21375 7fc3096ef585821f0453febb4073f79bb56e498d
parent 21374 673d1ba1884979e00447b0fe273c971a6ba5763a
child 21376 9f77a26afd904f4349f40df51eda194e31b50b6b
push id3513
push userjnightingale@mozilla.com
push dateThu, 06 Nov 2008 04:00:01 +0000
treeherdermozilla-central@7fc3096ef585 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconnor, beltzner, blocking-firefox3.1
bugs453440
milestone1.9.1b2pre
Extend "Clear Private Data" with time period option. b=453440, r=mconnor ui-r=beltzner a=blocking-firefox3.1
browser/app/profile/firefox.js
browser/base/content/browser-menubar.inc
browser/base/content/browser.js
browser/base/content/sanitize.js
browser/base/content/sanitize.xul
browser/base/content/test/Makefile.in
browser/base/content/test/browser_sanitize-timespans.js
browser/components/preferences/sanitize.xul
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/locales/en-US/chrome/browser/sanitize.dtd
browser/themes/gnomestripe/browser/preferences/preferences.css
browser/themes/pinstripe/browser/preferences/preferences.css
browser/themes/winstripe/browser/preferences/preferences.css
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -391,16 +391,23 @@ pref("privacy.item.formdata",    true);
 pref("privacy.item.passwords",   false);
 pref("privacy.item.downloads",   true);
 pref("privacy.item.cookies",     false);
 pref("privacy.item.cache",       true);
 pref("privacy.item.siteprefs",   false);
 pref("privacy.item.sessions",    true);
 pref("privacy.item.offlineApps", false);
 
+// What default should we use for the time span in the sanitizer:
+// 0 - Clear everything
+// 1 - Last Hour
+// 2 - Last 2 Hours
+// 3 - Last 4 Hours
+// 4 - Today
+pref("privacy.sanitize.timeSpan", 0);
 pref("privacy.sanitize.sanitizeOnShutdown", false);
 pref("privacy.sanitize.promptOnSanitize", true);
 
 pref("network.proxy.share_proxy_settings",  false); // use the same proxy settings for all protocols
 
 pref("network.cookie.cookieBehavior", 0); // 0-Accept, 1-dontAcceptForeign, 2-dontUse
 
 // l12n and i18n
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -468,18 +468,18 @@
               <menuseparator id="sanitizeSeparator"/>
               <menuitem id="privateBrowsingItem"
                         label="&privateBrowsingCmd.label;"
                         accesskey="&privateBrowsingCmd.accesskey;"
                         type="checkbox"
                         autocheck="false"
                         command="Tools:PrivateBrowsing"/>
               <menuitem id="sanitizeItem"
-                        accesskey="&clearPrivateDataCmd.accesskey;"
-                        label="&clearPrivateDataCmd.label;"
+                        accesskey="&clearRecentHistoryCmd.accesskey;"
+                        label="&clearRecentHistoryCmd.label;"
                         key="key_sanitize" command="Tools:Sanitize"/>
 #ifndef XP_UNIX
               <menuseparator id="prefSep"/>
               <menuitem id="menu_preferences"
                         label="&preferencesCmd.label;"
                         accesskey="&preferencesCmd.accesskey;"
                         oncommand="openPreferences();"/>
 #endif
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1492,17 +1492,17 @@ SanitizeListener.prototype =
   shutdown: function ()
   {
     gPrefService.removeObserver(this.promptDomain, this);
   },
 
   _updateSanitizeItem: function ()
   {
     var label = gPrefService.getBoolPref(this.promptDomain) ?
-        gNavigatorBundle.getString("sanitizeWithPromptLabel") : 
+        gNavigatorBundle.getString("sanitizeWithPromptLabel2") : 
         this._defaultLabel;
     document.getElementById("sanitizeItem").setAttribute("label", label);
   }
 }
 
 function gotoHistoryIndex(aEvent)
 {
   var index = aEvent.target.getAttribute("index");
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -17,16 +17,17 @@
 # The Initial Developer of the Original Code is
 # Ben Goodger.
 # Portions created by the Initial Developer are Copyright (C) 2005
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@mozilla.org>
 #   Giorgio Maone <g.maone@informaction.com>
+#   Johnathan Nightingale <johnath@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -46,36 +47,45 @@ Sanitizer.prototype = {
       this.items[aItemName].clear();
   },
 
   canClearItem: function (aItemName)
   {
     return this.items[aItemName].canClear;
   },
   
-  _prefDomain: "privacy.item.",
+  prefDomain: "privacy.item.",
+  
   getNameFromPreference: function (aPreferenceName)
   {
-    return aPreferenceName.substr(this._prefDomain.length);
+    return aPreferenceName.substr(this.prefDomain.length);
   },
   
   /**
    * Deletes privacy sensitive data in a batch, according to user preferences
    *
    * @returns  null if everything's fine;  an object in the form
    *           { itemName: error, ... } on (partial) failure
    */
   sanitize: function ()
   {
     var psvc = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefService);
-    var branch = psvc.getBranch(this._prefDomain);
+    var branch = psvc.getBranch(this.prefDomain);
     var errors = null;
+
+    // Cache the range of times to clear
+    if (this.ignoreTimespan)
+      var range = null;  // If we ignore timespan, clear everything
+    else
+      range = Sanitizer.getClearRange();
+      
     for (var itemName in this.items) {
       var item = this.items[itemName];
+      item.range = range;
       if ("clear" in item && item.canClear && branch.getBoolPref(itemName)) {
         // Some of these clear() may raise exceptions (see bug #265028)
         // to sanitize as much as possible, we catch and store them, 
         // rather than fail fast.
         // Callers should check returned errors and give user feedback
         // about items that could not be sanitized
         try {
           item.clear();
@@ -85,57 +95,81 @@ Sanitizer.prototype = {
           errors[itemName] = er;
           dump("Error sanitizing " + itemName + ": " + er + "\n");
         }
       }
     }
     return errors;
   },
   
+  // Time span only makes sense in certain cases.  Consumers who want
+  // to only clear some private data can opt in by setting this to false
+  ignoreTimespan : true,
+  
   items: {
     cache: {
       clear: function ()
       {
-        const cc = Components.classes;
-        const ci = Components.interfaces;
-        var cacheService = cc["@mozilla.org/network/cache-service;1"]
-                             .getService(ci.nsICacheService);
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        var cacheService = Cc["@mozilla.org/network/cache-service;1"].
+                          getService(Ci.nsICacheService);
         try {
-          cacheService.evictEntries(ci.nsICache.STORE_ANYWHERE);
+          // Cache doesn't consult timespan, nor does it have the
+          // facility for timespan-based eviction.  Wipe it.
+          cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
         } catch(er) {}
       },
       
       get canClear()
       {
         return true;
       }
     },
     
     cookies: {
       clear: function ()
       {
+        const Ci = Components.interfaces;
         var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
-                                  .getService(Components.interfaces.nsICookieManager);
-        cookieMgr.removeAll();
+                                  .getService(Ci.nsICookieManager);
+        if (this.range) {
+          // Iterate through the cookies and delete any created after our cutoff.
+          var cookiesEnum = cookieMgr.enumerator;
+          while (cookiesEnum.hasMoreElements()) {
+            var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+            
+            if (cookie.creationTime > this.range[0])
+              // This cookie was created after our cutoff, clear it
+              cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
+          }
+        }
+        else {
+          // Remove everything
+          cookieMgr.removeAll();
+        }
+
       },
       
       get canClear()
       {
         return true;
       }
     },
     
     offlineApps: {
       clear: function ()
       {
         const Cc = Components.classes;
         const Ci = Components.interfaces;
         var cacheService = Cc["@mozilla.org/network/cache-service;1"].
                            getService(Ci.nsICacheService);
         try {
+          // Offline app data is "timeless", and doesn't respect
+          // the setting of timespan, it always clears everything
           cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
         } catch(er) {}
 
         var storageManagerService = Cc["@mozilla.org/dom/storagemanager;1"].
                                     getService(Ci.nsIDOMStorageManager);
         storageManagerService.clearOfflineApps();
       },
 
@@ -145,17 +179,20 @@ Sanitizer.prototype = {
       }
     },
 
     history: {
       clear: function ()
       {
         var globalHistory = Components.classes["@mozilla.org/browser/global-history;2"]
                                       .getService(Components.interfaces.nsIBrowserHistory);
-        globalHistory.removeAllPages();
+        if (this.range)
+          globalHistory.removePagesByTimeframe(this.range[0], this.range[1]);
+        else
+          globalHistory.removeAllPages();
         
         try {
           var os = Components.classes["@mozilla.org/observer-service;1"]
                              .getService(Components.interfaces.nsIObserverService);
           os.notifyObservers(null, "browser:purge-session-history", "");
         }
         catch (e) { }
         
@@ -218,32 +255,61 @@ Sanitizer.prototype = {
       }
     },
     
     downloads: {
       clear: function ()
       {
         var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
                               .getService(Components.interfaces.nsIDownloadManager);
-        dlMgr.cleanUp();
+
+        var dlIDsToRemove = [];
+        if (this.range) {
+          // First, remove the completed/cancelled downloads
+          dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
+          
+          // Queue up any active downloads that started in the time span as well
+          var dlsEnum = dlMgr.activeDownloads;
+          while(dlsEnum.hasMoreElements()) {
+            var dl = dlsEnum.next();
+            if(dl.startTime >= this.range[0])
+              dlIDsToRemove.push(dl.id);
+          }
+        }
+        else {
+          // Clear all completed/cancelled downloads
+          dlMgr.cleanUp();
+          
+          // Queue up all active ones as well
+          var dlsEnum = dlMgr.activeDownloads;
+          while(dlsEnum.hasMoreElements()) {
+            dlIDsToRemove.push(dlsEnum.next().id);
+          }
+        }
+        
+        // Remove any queued up active downloads
+        dlIDsToRemove.forEach(function(id) {
+          dlMgr.removeDownload(id);
+        });
       },
 
       get canClear()
       {
         var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
                               .getService(Components.interfaces.nsIDownloadManager);
         return dlMgr.canCleanUp;
       }
     },
     
     passwords: {
       clear: function ()
       {
         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
                               .getService(Components.interfaces.nsILoginManager);
+        // Passwords are timeless, and don't respect the timeSpan setting
         pwmgr.removeAllLogins();
       },
       
       get canClear()
       {
         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
                               .getService(Components.interfaces.nsILoginManager);
         var count = pwmgr.countLogins("", "", ""); // count all logins
@@ -276,16 +342,57 @@ Sanitizer.prototype = {
 
 
 // "Static" members
 Sanitizer.prefDomain          = "privacy.sanitize.";
 Sanitizer.prefPrompt          = "promptOnSanitize";
 Sanitizer.prefShutdown        = "sanitizeOnShutdown";
 Sanitizer.prefDidShutdown     = "didShutdownSanitize";
 
+// Time span constants corresponding to values of the privacy.sanitize.timeSpan
+// pref.  Used to determine how much history to clear, for various items
+Sanitizer.TIMESPAN_EVERYTHING = 0;
+Sanitizer.TIMESPAN_HOUR       = 1;
+Sanitizer.TIMESPAN_2HOURS     = 2;
+Sanitizer.TIMESPAN_4HOURS     = 3;
+Sanitizer.TIMESPAN_TODAY      = 4;
+
+// Return a 2 element array representing the start and end times,
+// in the uSec-since-epoch format that PRTime likes.  If we should
+// clear everything, return null
+Sanitizer.getClearRange = function() {
+  var ts = Sanitizer.prefs.getIntPref("timeSpan");
+  if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+    return null;
+  
+  // PRTime is microseconds while JS time is milliseconds
+  var endDate = Date.now() * 1000;
+  switch (ts) {
+    case Sanitizer.TIMESPAN_HOUR :
+      var startDate = endDate - 3600000000; // 1*60*60*1000000
+      break;
+    case Sanitizer.TIMESPAN_2HOURS :
+      startDate = endDate - 7200000000; // 2*60*60*1000000
+      break;
+    case Sanitizer.TIMESPAN_4HOURS :
+      startDate = endDate - 14400000000; // 4*60*60*1000000
+      break;
+    case Sanitizer.TIMESPAN_TODAY :
+      var d = new Date();  // Start with today
+      d.setHours(0);      // zero us back to midnight...
+      d.setMinutes(0);
+      d.setSeconds(0);
+      startDate = d.valueOf() * 1000; // convert to epoch usec
+      break;
+    default:
+      throw "Invalid time span for clear private data: " + ts;
+  }
+  return [startDate, endDate];
+};
+
 Sanitizer._prefs = null;
 Sanitizer.__defineGetter__("prefs", function() 
 {
   return Sanitizer._prefs ? Sanitizer._prefs
     : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefService)
                          .getBranch(Sanitizer.prefDomain);
 });
--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -19,72 +19,97 @@
 # The Initial Developer of the Original Code is
 # Ben Goodger.
 # Portions created by the Initial Developer are Copyright (C) 2005
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@mozilla.org>
 #   Giorgio Maone <g.maone@informaction.com>
+#   Johnathan Nightingale <johnath@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 <?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> 
 
 <!DOCTYPE prefwindow [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
   %brandDTD;
   %sanitizeDTD;
 ]>
 
 <prefwindow id="SanitizeDialog" type="child"
             xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             dlgbuttons="accept,cancel"
-            title="&sanitizeDialog.title;"
+            title="&sanitizeDialog2.title;"
             style="width: &window.width;;"
             ondialogaccept="gSanitizePromptDialog.sanitize();">
 
   <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
     <stringbundle id="bundleBrowser" src="chrome://browser/locale/browser.properties"/>
     
     <script type="application/x-javascript" src="chrome://browser/content/sanitize.js"/>
     <script type="application/x-javascript">
     <![CDATA[
       var gSanitizePromptDialog = {
         init: function ()
         {
+          this.checkPrefs();
           var s = new Sanitizer();
+          s.prefDomain = "privacy.cpd.";
           var sanitizePreferences = document.getElementById("sanitizePreferences");
           for (var i = 0; i < sanitizePreferences.childNodes.length; ++i) {
             var preference = sanitizePreferences.childNodes[i];
             var name = s.getNameFromPreference(preference.name);
             if (!s.canClearItem(name)) 
               preference.disabled = true;
           }
           
           var bundleBrowser = document.getElementById("bundleBrowser");
-          document.documentElement.getButton("accept").label = bundleBrowser.getString("sanitizeButton");
+          document.documentElement.getButton("accept").label = bundleBrowser.getString("sanitizeButton2");
         },
       
+        checkPrefs : function ()
+        {
+          var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefService);
+          var cpdBranch = prefService.getBranch("privacy.cpd.");
+          
+          // If we don't have defaults for the privacy.cpd branch,
+          // clone the privacy.item (clear at shutdown) defaults
+          if (cpdBranch.prefHasUserValue("history"))
+            return;
+          
+          var itemBranch = prefService.getBranch("privacy.item.");
+          var itemCount = { value: 0 };
+          var itemArray = itemBranch.getChildList("", itemCount);
+          itemArray.forEach(function (name) {
+            cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+          });
+        },
+        
         sanitize: function ()
         {
           var s = new Sanitizer();
+          s.ignoreTimespan = false;
+          s.prefDomain = "privacy.cpd.";
           var sanitizePreferences = document.getElementById("sanitizePreferences");
           var preference, name;
           for (var i = 0; i < sanitizePreferences.childNodes.length; ++i) {
             preference = sanitizePreferences.childNodes[i];
             if (preference.value) {
               name = s.getNameFromPreference(preference.name);
               try {
                 s.clearItem(name);
@@ -114,18 +139,18 @@
           return undefined;
         },
 
         onReadDownloads: function (aEvent)
         {
           // Call the common function that will update the accept button if needed
           this.onReadGeneric();
 
-          let historyPref = document.getElementById("privacy.item.history")
-          let downloadPref = document.getElementById("privacy.item.downloads");
+          let historyPref = document.getElementById("privacy.cpd.history")
+          let downloadPref = document.getElementById("privacy.cpd.downloads");
 
           // Disable the checkbox if history is selected
           let downloads = document.getElementById("downloads-checkbox");
           downloads.disabled = historyPref.value;
 
           // The "Download History" checkbox is selected if either of the history or
           // downloads preferences are true.
           return historyPref.value || downloadPref.value;
@@ -143,59 +168,93 @@
           if (history.checked)
             downloads.checked = true;
         },
       };
     ]]>
     </script>
 
     <preferences id="sanitizePreferences">
-      <preference id="privacy.item.history"               name="privacy.item.history"               type="bool" readonly="true"/>
-      <preference id="privacy.item.formdata"              name="privacy.item.formdata"              type="bool" readonly="true"/>
-      <preference id="privacy.item.passwords"             name="privacy.item.passwords"             type="bool" readonly="true"/>
-      <preference id="privacy.item.downloads"             name="privacy.item.downloads"             type="bool" readonly="true"/>
-      <preference id="privacy.item.cookies"               name="privacy.item.cookies"               type="bool" readonly="true"/>
-      <preference id="privacy.item.cache"                 name="privacy.item.cache"                 type="bool" readonly="true"/>
-      <preference id="privacy.item.offlineApps"           name="privacy.item.offlineApps"           type="bool"/>
-      <preference id="privacy.item.sessions"              name="privacy.item.sessions"              type="bool" readonly="true"/>
+      <preference id="privacy.cpd.history"               name="privacy.cpd.history"               type="bool"/>
+      <preference id="privacy.cpd.formdata"              name="privacy.cpd.formdata"              type="bool"/>
+      <preference id="privacy.cpd.passwords"             name="privacy.cpd.passwords"             type="bool"/>
+      <preference id="privacy.cpd.downloads"             name="privacy.cpd.downloads"             type="bool"/>
+      <preference id="privacy.cpd.cookies"               name="privacy.cpd.cookies"               type="bool"/>
+      <preference id="privacy.cpd.cache"                 name="privacy.cpd.cache"                 type="bool"/>
+      <preference id="privacy.cpd.offlineApps"           name="privacy.cpd.offlineApps"           type="bool"/>
+      <preference id="privacy.cpd.sessions"              name="privacy.cpd.sessions"              type="bool"/>
+    </preferences>
+    
+    <preferences id="nonItemPreferences">
+      <preference id="privacy.sanitize.timeSpan"
+                  name="privacy.sanitize.timeSpan"
+                  type="int"/>
     </preferences>
 
-    <description>&sanitizeItems.label;</description>
-
-    <checkbox id="history-checkbox"
-              label="&itemHistory.label;"
-              accesskey="&itemHistory.accesskey;"
-              preference="privacy.item.history"
-              oncommand="gSanitizePromptDialog.updateDownloadHistory();"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox id="downloads-checkbox"
-              class="indent"
-              label="&itemDownloads.label;"
-              accesskey="&itemDownloads.accesskey;"
-              preference="privacy.item.downloads"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadDownloads();"/>
-    <checkbox label="&itemFormSearchHistory.label;"
-              accesskey="&itemFormSearchHistory.accesskey;"
-              preference="privacy.item.formdata"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox label="&itemCache.label;"
-              accesskey="&itemCache.accesskey;"
-              preference="privacy.item.cache"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox label="&itemCookies.label;"
-              accesskey="&itemCookies.accesskey;"
-              preference="privacy.item.cookies"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox label="&itemOfflineApps.label;"
-              accesskey="&itemOfflineApps.accesskey;"
-              preference="privacy.item.offlineApps"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox label="&itemPasswords.label;"
-              accesskey="&itemPasswords.accesskey;"
-              preference="privacy.item.passwords"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
-    <checkbox label="&itemActiveLogins.label;"
-              accesskey="&itemActiveLogins.accesskey;"
-              preference="privacy.item.sessions"
-              onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+    <groupbox orient="vertical">
+      <caption label="&historySection.label;"/>
+      <hbox id="SanitizeDurationBox">
+        <label value="&clearDuration.label;" control="sanitizeDurationChoice" id="sanitizeDurationLabel"/>
+        <menulist id="sanitizeDurationChoice"
+                  preference="privacy.sanitize.timeSpan">
+          <menupopup>
+            <menuitem label="&clearDuration.lastHour;" value="1"/>
+            <menuitem label="&clearDuration.last2Hours;" value="2"/>
+            <menuitem label="&clearDuration.last4Hours;" value="3"/>
+            <menuitem label="&clearDuration.today;" value="4"/>
+            <menuseparator/>
+            <menuitem label="&clearDuration.everything;" value="0"/>
+          </menupopup>
+        </menulist>
+        <label value="&clearDuration.suffix;" flex="1"/>
+      </hbox>
+      <hbox>
+        <vbox style="width: &column.width;">
+          <checkbox id="history-checkbox"
+                    label="&itemVisitedPages.label;"
+                    accesskey="&itemVisitedPages.accesskey;"
+                    preference="privacy.cpd.history"
+                    oncommand="gSanitizePromptDialog.updateDownloadHistory();"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+          <checkbox id="downloads-checkbox"
+                    label="&itemDownloadList.label;"
+                    accesskey="&itemDownloadList.accesskey;"
+                    preference="privacy.cpd.downloads"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadDownloads();"/>
+          <checkbox label="&itemFormSearchEntries.label;"
+                    accesskey="&itemFormSearchEntries.accesskey;"
+                    preference="privacy.cpd.formdata"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+        </vbox>
+        <vbox style="width: &column.width;">
+          <checkbox label="&itemCookies.label;"
+                    accesskey="&itemCookies.accesskey;"
+                    preference="privacy.cpd.cookies"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+          <checkbox label="&itemActiveLogins.label;"
+                    accesskey="&itemActiveLogins.accesskey;"
+                    preference="privacy.cpd.sessions"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+          <checkbox label="&itemWebCache.label;"
+                    accesskey="&itemWebCache.accesskey;"
+                    preference="privacy.cpd.cache"
+                    onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+        </vbox>
+      </hbox>
+    </groupbox>
+    <groupbox orient="horizontal">
+      <caption label="&dataSection.label;"/>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemPasswords.label;"
+                  accesskey="&itemPasswords.accesskey;"
+                  preference="privacy.cpd.passwords"
+                  onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+      </vbox>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemOfflineApps.label;"
+                  accesskey="&itemOfflineApps.accesskey;"
+                  preference="privacy.cpd.offlineApps"
+                  onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+      </vbox>
+    </groupbox>
 
   </prefpane>
 </prefwindow>
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -50,16 +50,17 @@ include $(topsrcdir)/config/rules.mk
 		test_contextmenu.html \
 		subtst_contextmenu.html \
 		ctxmenu-image.png \
 		$(NULL)
 
 # browser_bug423833.js disabled temporarily since it's unreliable: bug 428712
 # browser_sanitize-download-history.js disabled temporarily since it's unreliable: bug 432425
 _BROWSER_FILES = browser_bug321000.js \
+                 browser_sanitize-timespans.js \
                  browser_bug405137.js \
                  browser_bug409481.js \
                  browser_bug413915.js \
                  browser_bug420160.js \
                  browser_bug427559.js \
                  browser_bug441778.js \
                  browser_discovery.js \
                  discovery.html \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_sanitize-timespans.js
@@ -0,0 +1,280 @@
+// Bug 453440 - Test the timespan-based logic of the sanitizer code
+var now_uSec = Date.now() * 1000;
+
+const dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+const bhist = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIBrowserHistory);
+const iosvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader)
+                                           .loadSubScript("chrome://browser/content/sanitize.js");
+
+function test() {
+  
+  var hoursSinceMidnight = new Date().getHours();
+
+  setupHistory();
+  setupDownloads();
+  
+  // Should test cookies here, but nsICookieManager/nsICookieService
+  // doesn't let us fake creation times.  bug 463127
+  
+  let s = new Sanitizer();
+  s.ignoreTimespan = false;
+  s.prefDomain = "privacy.cpd.";
+  var itemPrefs = Cc["@mozilla.org/preferences-service;1"]
+                  .getService(Components.interfaces.nsIPrefService)
+                  .getBranch(s.prefDomain);
+  itemPrefs.setBoolPref("history", true);
+  itemPrefs.setBoolPref("downloads", true);
+  itemPrefs.setBoolPref("cache", false);
+  itemPrefs.setBoolPref("cookies", false);
+  itemPrefs.setBoolPref("formdata", false);
+  itemPrefs.setBoolPref("offlineApps", false);
+  itemPrefs.setBoolPref("passwords", false);
+  itemPrefs.setBoolPref("sessions", false);
+  itemPrefs.setBoolPref("siteprefs", false);
+  
+  // Clear 1 hour
+  Sanitizer.prefs.setIntPref("timeSpan", 1);
+  s.sanitize();
+  
+  ok(!bhist.isVisited(uri("http://1hour.com")), "1hour.com should now be deleted");
+  ok(bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
+  ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
+  
+  if(hoursSinceMidnight > 1)
+    ok(bhist.isVisited(uri("http://today.com")), "Pretend visit to today.com should still exist");
+  ok(bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+  
+  ok(!downloadExists(5555551), "<1 hour download should now be deleted");
+  ok(downloadExists(5555550), "Year old download should still be present");
+  ok(downloadExists(5555552), "<2 hour old download should still be present");
+  ok(downloadExists(5555553), "<4 hour old download should still be present");
+
+  if(hoursSinceMidnight > 1)
+    ok(downloadExists(5555554), "'Today' download should still be present");
+  
+  // Clear 2 hours
+  Sanitizer.prefs.setIntPref("timeSpan", 2);
+  s.sanitize();
+  
+  ok(!bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should now be deleted");
+  ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
+  if(hoursSinceMidnight > 2)
+    ok(bhist.isVisited(uri("http://today.com")), "Pretend visit to today.com should still exist");
+  ok(bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+  
+  ok(!downloadExists(5555552), "<2 hour old download should now be deleted");
+  ok(downloadExists(5555550), "Year old download should still be present");
+  ok(downloadExists(5555553), "<4 hour old download should still be present");
+  if(hoursSinceMidnight > 2)
+    ok(downloadExists(5555554), "'Today' download should still be present");
+  
+  // Clear 4 hours
+  Sanitizer.prefs.setIntPref("timeSpan", 3);
+  s.sanitize();
+  
+  ok(!bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should now be deleted");
+  if(hoursSinceMidnight > 4)
+    ok(bhist.isVisited(uri("http://today.com")), "Pretend visit to today.com should still exist");
+  ok(bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+  
+  ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
+  ok(downloadExists(5555550), "Year old download should still be present");
+  if(hoursSinceMidnight > 4)
+    ok(downloadExists(5555554), "'Today' download should still be present");
+
+  // Clear Today
+  Sanitizer.prefs.setIntPref("timeSpan", 4);
+  s.sanitize();
+  
+  ok(!bhist.isVisited(uri("http://today.com")), "Pretend visit to today.com should now be deleted");
+  ok(bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+
+  ok(!downloadExists(5555554), "'Today' download should now be deleted");
+  ok(downloadExists(5555550), "Year old download should still be present");
+
+  // Choose everything
+  Sanitizer.prefs.setIntPref("timeSpan", 0);
+  s.sanitize();
+  
+  ok(!bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should now be deleted");
+  
+  ok(!downloadExists(5555550), "Year old download should now be deleted");
+
+}
+
+function setupHistory() {
+  bhist.addPageWithDetails(uri("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45*60*1000000);
+  bhist.addPageWithDetails(uri("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90*60*1000000);
+  bhist.addPageWithDetails(uri("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180*60*1000000);
+  
+  let today = new Date();
+  today.setHours(0);
+  today.setMinutes(0);
+  today.setSeconds(30);
+  bhist.addPageWithDetails(uri("http://today.com/"), "Today", today.valueOf() * 1000);
+  
+  let lastYear = new Date();
+  lastYear.setFullYear(lastYear.year - 1);
+  bhist.addPageWithDetails(uri("http://before-today.com/"), "Before Today", lastYear.valueOf() * 1000);
+  
+  // Confirm everything worked
+  ok(bhist.isVisited(uri("http://1hour.com")), "Pretend visit to 1hour.com should exist");
+  ok(bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should exist");
+  ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should exist");
+  ok(bhist.isVisited(uri("http://today.com")), "Pretend visit to today.com should exist");
+  ok(bhist.isVisited(uri("http://before-today.com")), "Pretend visit to before-today.com should exist");
+}
+
+function setupDownloads() {
+
+  // Add within-1-hour download to DB
+  let data = {
+    id:   "5555551",
+    name: "fakefile-1-hour",
+    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+    target: "fakefile-1-hour",
+    startTime: now_uSec - 45*60*1000000,  // 45 minutes ago, in uSec
+    endTime: now_uSec - 44*60*1000000, // 1 minute later
+    state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+  };
+
+  let db = dm.DBConnection;
+  let stmt = db.createStatement(
+    "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
+      "state, currBytes, maxBytes, preferredAction, autoResume) " +
+    "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
+      ":currBytes, :maxBytes, :preferredAction, :autoResume)");
+  try {
+    for (let prop in data)
+      stmt.params[prop] = data[prop];
+    stmt.execute();
+  }
+  finally {
+    stmt.reset();
+  }
+  
+  // Add within-2-hour download  
+  data = {
+    id:   "5555552",
+    name: "fakefile-2-hour",
+    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+    target: "fakefile-2-hour",
+    startTime: now_uSec - 90*60*1000000,  // 90 minutes ago, in uSec
+    endTime: now_uSec - 89*60*1000000, // 1 minute later
+    state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+  };
+
+  try {
+    for (let prop in data)
+      stmt.params[prop] = data[prop];
+    stmt.execute();
+  }
+  finally {
+    stmt.reset();
+  }
+  
+  // Add within-4-hour download
+  data = {
+    id:   "5555553",
+    name: "fakefile-4-hour",
+    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+    target: "fakefile-4-hour",
+    startTime: now_uSec - 180*60*1000000,  // 180 minutes ago, in uSec
+    endTime: now_uSec - 179*60*1000000, // 1 minute later
+    state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+  };
+  
+  try {
+    for (let prop in data)
+      stmt.params[prop] = data[prop];
+    stmt.execute();
+  }
+  finally {
+    stmt.reset();
+  }
+  
+  // Add "today" download
+  let today = new Date();
+  today.setHours(0);
+  today.setMinutes(0);
+  today.setSeconds(30);
+  
+  data = {
+    id:   "5555554",
+    name: "fakefile-today",
+    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+    target: "fakefile-today",
+    startTime: today.valueOf() * 1000,  // 12:00:30am this morning, in uSec
+    endTime: (today.valueOf() + 1000) * 1000, // 1 second later
+    state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+  };
+  
+  try {
+    for (let prop in data)
+      stmt.params[prop] = data[prop];
+    stmt.execute();
+  }
+  finally {
+    stmt.reset();
+  }
+  
+  // Add "before today" download
+  let lastYear = new Date();
+  lastYear.setFullYear(lastYear.year - 1);
+  data = {
+    id:   "5555550",
+    name: "fakefile-old",
+    source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+    target: "fakefile-old",
+    startTime: lastYear.valueOf() * 1000,  // 1 year ago, in uSec
+    endTime: (lastYear.valueOf() + 1000) * 1000, // 1 second later
+    state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+    currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+  };
+  
+  try {
+    for (let prop in data)
+      stmt.params[prop] = data[prop];
+    stmt.execute();
+  }
+  finally {
+    stmt.finalize();
+  }
+  
+  // Confirm everything worked
+  ok(downloadExists(5555550), "Pretend download for everything case should exist");
+  ok(downloadExists(5555551), "Pretend download for 1-hour case should exist");
+  ok(downloadExists(5555552), "Pretend download for 2-hour case should exist");
+  ok(downloadExists(5555553), "Pretend download for 4-hour case should exist");
+  ok(downloadExists(5555554), "Pretend download for Today case should exist");
+}
+
+/**
+ * Checks to see if the downloads with the specified id exists.
+ *
+ * @param aID
+ *        The ids of the downloads to check.
+ */
+function downloadExists(aID)
+{
+  let db = dm.DBConnection;
+  let stmt = db.createStatement(
+    "SELECT * " +
+    "FROM moz_downloads " +
+    "WHERE id = :id"
+  );
+  stmt.params.id = aID;
+  var rows = stmt.step();
+  stmt.finalize();
+  return rows;
+}
+
+function uri(spec) {
+  return iosvc.newURI(spec, null, null);
+}
--- a/browser/components/preferences/sanitize.xul
+++ b/browser/components/preferences/sanitize.xul
@@ -34,29 +34,30 @@
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 <?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> 
 
 <!DOCTYPE dialog [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
   %brandDTD;
   %sanitizeDTD;
 ]>
 
 <prefwindow id="SanitizeDialog" type="child"
             xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
-            title="&sanitizeDialog.title;">
+            title="&sanitizePrefs.title;">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="SanitizeDialogPane"
             helpTopic="prefs-clear-private-data">
 
     <preferences>
       <preference id="privacy.item.history"               name="privacy.item.history"               type="bool"/>
@@ -64,37 +65,50 @@
       <preference id="privacy.item.passwords"             name="privacy.item.passwords"             type="bool"/>
       <preference id="privacy.item.downloads"             name="privacy.item.downloads"             type="bool"/>
       <preference id="privacy.item.cookies"               name="privacy.item.cookies"               type="bool"/>
       <preference id="privacy.item.cache"                 name="privacy.item.cache"                 type="bool"/>
       <preference id="privacy.item.offlineApps"           name="privacy.item.offlineApps"           type="bool"/>
       <preference id="privacy.item.sessions"              name="privacy.item.sessions"              type="bool"/>
     </preferences>
 
-    <description>&clearDataSettings.label;</description>
+    <description>&clearDataSettings2.label;</description>
 
-    <checkbox label="&itemHistory.label;"
-              accesskey="&itemHistory.accesskey;"
-              preference="privacy.item.history"/>
-    <checkbox label="&itemDownloads.label;"
-              accesskey="&itemDownloads.accesskey;"
-              preference="privacy.item.downloads"/>
-    <checkbox label="&itemFormSearchHistory.label;"
-              accesskey="&itemFormSearchHistory.accesskey;"
-              preference="privacy.item.formdata"/>
-    <checkbox label="&itemCache.label;"
-              accesskey="&itemCache.accesskey;"
-              preference="privacy.item.cache"/>
-    <checkbox label="&itemCookies.label;"
-              accesskey="&itemCookies.accesskey;"
-              preference="privacy.item.cookies"/>
-    <checkbox label="&itemOfflineApps.label;"
-	      accesskey="&itemOfflineApps.accesskey;"
-	      preference="privacy.item.offlineApps"/>
-    <checkbox label="&itemPasswords.label;"
-              accesskey="&itemPasswords.accesskey;"
-              preference="privacy.item.passwords"/>
-    <checkbox label="&itemActiveLogins.label;"
-              accesskey="&itemActiveLogins.accesskey;"
-              preference="privacy.item.sessions"/>
-
+    <groupbox orient="horizontal">
+      <caption label="&historySection.label;"/>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemVisitedPages.label;"
+                  accesskey="&itemVisitedPages.accesskey;"
+                  preference="privacy.item.history"/>
+        <checkbox label="&itemDownloadList.label;"
+                  accesskey="&itemDownloadList.accesskey;"
+                  preference="privacy.item.downloads"/>
+        <checkbox label="&itemFormSearchEntries.label;"
+                  accesskey="&itemFormSearchEntries.accesskey;"
+                  preference="privacy.item.formdata"/>
+      </vbox>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemCookies.label;"
+                  accesskey="&itemCookies.accesskey;"
+                  preference="privacy.item.cookies"/>
+        <checkbox label="&itemActiveLogins.label;"
+                  accesskey="&itemActiveLogins.accesskey;"
+                  preference="privacy.item.sessions"/>
+        <checkbox label="&itemWebCache.label;"
+                  accesskey="&itemWebCache.accesskey;"
+                  preference="privacy.item.cache"/>
+      </vbox>
+    </groupbox>
+    <groupbox orient="horizontal">
+      <caption label="&dataSection.label;"/>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemPasswords.label;"
+                  accesskey="&itemPasswords.accesskey;"
+                  preference="privacy.item.passwords"/>
+      </vbox>
+      <vbox style="width: &column.width;">
+        <checkbox label="&itemOfflineApps.label;"
+                  accesskey="&itemOfflineApps.accesskey;"
+                  preference="privacy.item.offlineApps"/>
+      </vbox>
+    </groupbox>
   </prefpane>
 </prefwindow>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -156,18 +156,18 @@
 <!ENTITY selectAllCmd.label         "Select All">  
 <!ENTITY selectAllCmd.key         "A">  
 <!ENTITY selectAllCmd.accesskey       "A"> 
 <!ENTITY preferencesCmd.label       "Options…">
 <!ENTITY preferencesCmd.accesskey     "O"> 
 <!ENTITY preferencesCmdUnix.label       "Preferences">
 <!ENTITY preferencesCmdUnix.accesskey     "n"> 
 
-<!ENTITY clearPrivateDataCmd.label            "Clear Private Data">
-<!ENTITY clearPrivateDataCmd.accesskey        "P">
+<!ENTITY clearRecentHistoryCmd.label            "Clear Recent History">
+<!ENTITY clearRecentHistoryCmd.accesskey        "H">
 
 <!ENTITY privateBrowsingCmd.label            "Private Browsing">
 <!ENTITY privateBrowsingCmd.accesskey        "B">
 
 <!ENTITY viewMenu.label         "View"> 
 <!ENTITY viewMenu.accesskey       "V"> 
 <!ENTITY viewToolbarsMenu.label       "Toolbars"> 
 <!ENTITY viewToolbarsMenu.accesskey     "T"> 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -54,18 +54,18 @@ missingpluginsMessage.button.label=Install Missing Plugins…
 missingpluginsMessage.button.accesskey=I
 blockedpluginsMessage.title=Some plugins required by this page have been blocked for your protection.
 blockedpluginsMessage.infoButton.label=Details…
 blockedpluginsMessage.infoButton.accesskey=D
 blockedpluginsMessage.searchButton.label=Update Plugins…
 blockedpluginsMessage.searchButton.accesskey=U
 
 # Sanitize
-sanitizeWithPromptLabel=Clear Private Data…
-sanitizeButton=Clear Private Data Now
+sanitizeWithPromptLabel2=Clear Recent History…
+sanitizeButton2=Clear Private Data
 
 # Check for Updates
 updatesItem_default=Check for Updates…
 updatesItem_defaultFallback=Check for Updates…
 updatesItem_downloading=Downloading %S…
 updatesItem_downloadingFallback=Downloading Update…
 updatesItem_resume=Resume Downloading %S…
 updatesItem_resumeFallback=Resume Downloading Update…
--- a/browser/locales/en-US/chrome/browser/sanitize.dtd
+++ b/browser/locales/en-US/chrome/browser/sanitize.dtd
@@ -1,24 +1,40 @@
-<!ENTITY sanitizeDialog.title         "Clear Private Data">
+<!ENTITY sanitizePrefs.title           "Clear Private Data">
+<!ENTITY sanitizeDialog2.title         "Clear Recent History">
 
 <!ENTITY sanitizeItems.label          "Clear the following items now:">
-<!ENTITY clearDataSettings.label      "When I ask &brandShortName; to clear my private data, it should erase:">
+<!ENTITY clearDataSettings2.label     "When I quit &brandShortName;, it should automatically clear all:">
 
 <!-- XXX rearrange entities to match physical layout when l10n isn't an issue -->
 
-<!ENTITY itemHistory.label            "Browsing History">
-<!ENTITY itemHistory.accesskey        "B">
-<!ENTITY itemFormSearchHistory.label  "Saved Form and Search History">
-<!ENTITY itemFormSearchHistory.accesskey "F">
+<!ENTITY clearDuration.label          "Remove ">
+<!ENTITY clearDuration.lastHour       "the last hour">
+<!ENTITY clearDuration.last2Hours     "the last 2 hours">
+<!ENTITY clearDuration.last4Hours     "the last 4 hours">
+<!ENTITY clearDuration.today          "my history for today">
+<!ENTITY clearDuration.everything     "my entire history">
+<!-- Localization note (clearDuration.suffix) - trailing entity for languages
+that require it.  -->
+<!ENTITY clearDuration.suffix         "">
+
+<!ENTITY historySection.label         "History">
+<!ENTITY dataSection.label            "Data">
+
+<!ENTITY itemVisitedPages.label       "Visited Pages">
+<!ENTITY itemVisitedPages.accesskey   "V">
+<!ENTITY itemFormSearchEntries.label  "Form &amp; Search Entries">
+<!ENTITY itemFormSearchEntries.accesskey "F">
 <!ENTITY itemPasswords.label          "Saved Passwords">
 <!ENTITY itemPasswords.accesskey      "P">
 <!ENTITY itemCookies.label            "Cookies">
 <!ENTITY itemCookies.accesskey        "C">
-<!ENTITY itemCache.label              "Cache">
-<!ENTITY itemCache.accesskey          "a">
+<!ENTITY itemWebCache.label           "Web Cache">
+<!ENTITY itemWebCache.accesskey       "W">
 <!ENTITY itemOfflineApps.label        "Offline Website Data">
 <!ENTITY itemOfflineApps.accesskey    "O">
-<!ENTITY itemDownloads.label          "Download History">
-<!ENTITY itemDownloads.accesskey      "D">
+<!ENTITY itemDownloadList.label       "Download List">
+<!ENTITY itemDownloadList.accesskey   "D">
 <!ENTITY itemActiveLogins.label       "Active Logins">
 <!ENTITY itemActiveLogins.accesskey   "L">
+
 <!ENTITY window.width                 "30em">
+<!ENTITY column.width                 "14em">
--- a/browser/themes/gnomestripe/browser/preferences/preferences.css
+++ b/browser/themes/gnomestripe/browser/preferences/preferences.css
@@ -180,8 +180,27 @@ filefield {
   padding: 0px;
 }
 
 /* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
    of the groupbox from being cutoff */
 .bottomBox {
   padding-bottom: 4px;
 }
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDurationBox {
+  padding-bottom: 10px;
+}
+
+#sanitizeDurationChoice {
+  margin: 0; 
+}
+
+#sanitizeDurationLabel {
+  -moz-margin-start: 3px;
+}
+
+#SanitizeDialogPane > groupbox {
+  margin-top: 0;
+}
--- a/browser/themes/pinstripe/browser/preferences/preferences.css
+++ b/browser/themes/pinstripe/browser/preferences/preferences.css
@@ -274,8 +274,27 @@ caption {
  */
 #autoInstallOptions {
   -moz-margin-start: 20px;
 }
 
 .updateControls {
   -moz-margin-start: 10px;
 }
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDurationBox {
+  padding-bottom: 10px;
+}
+
+#sanitizeDurationChoice {
+  margin: 0; 
+}
+
+#sanitizeDurationLabel {
+  -moz-margin-start: 3px;
+}
+
+#SanitizeDialogPane > groupbox {
+  margin-top: 0;
+}
--- a/browser/themes/winstripe/browser/preferences/preferences.css
+++ b/browser/themes/winstripe/browser/preferences/preferences.css
@@ -193,8 +193,27 @@ filefield {
   padding: 0px;
 }
 
 /* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
    of the groupbox from being cutoff */
 .bottomBox {
   padding-bottom: 4px;
 }
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDurationBox {
+  padding-bottom: 10px;
+}
+
+#sanitizeDurationChoice {
+  margin: 0; 
+}
+
+#sanitizeDurationLabel {
+  -moz-margin-start: 3px;
+}
+
+#SanitizeDialogPane > groupbox {
+  margin-top: 0;
+}