Bug 480169 - Clear recent history refresh (sprint)

Item list hidden behind expander/progressive disclosure button, added a warning when clearing all history, added a tree view for selecting a fine-grained timespan.  We decided to remove the tree view at the last minute; code is still there but #ifdef'ed out.
r=mconnor
This commit is contained in:
Drew Willcoxon 2009-04-15 10:59:07 -07:00
parent c7862e55bd
commit 73f247cb0f
16 changed files with 3031 additions and 200 deletions

View File

@ -395,7 +395,7 @@ pref("privacy.item.history", true);
pref("privacy.item.formdata", true);
pref("privacy.item.passwords", false);
pref("privacy.item.downloads", true);
pref("privacy.item.cookies", false);
pref("privacy.item.cookies", true);
pref("privacy.item.cache", true);
pref("privacy.item.sessions", true);
pref("privacy.item.offlineApps", false);

View File

@ -77,7 +77,7 @@ Sanitizer.prototype = {
if (this.ignoreTimespan)
var range = null; // If we ignore timespan, clear everything
else
range = Sanitizer.getClearRange();
range = this.range || Sanitizer.getClearRange();
for (var itemName in this.items) {
var item = this.items[itemName];
@ -102,8 +102,12 @@ Sanitizer.prototype = {
},
// 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
// to only clear some private data can opt in by setting this to false,
// and can optionally specify a specific range. If timespan is not ignored,
// and range is not set, sanitize() will use the value of the timespan
// pref to determine a range
ignoreTimespan : true,
range : null,
items: {
cache: {
@ -395,9 +399,11 @@ 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");
// clear everything, return null. Use ts if it is defined; otherwise
// use the timeSpan pref.
Sanitizer.getClearRange = function (ts) {
if (ts === undefined)
ts = Sanitizer.prefs.getIntPref("timeSpan");
if (ts === Sanitizer.TIMESPAN_EVERYTHING)
return null;

View File

@ -25,6 +25,7 @@
# Ben Goodger <ben@mozilla.org>
# Giorgio Maone <g.maone@informaction.com>
# Johnathan Nightingale <johnath@mozilla.com>
# Drew Willcoxon <adw@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
@ -42,6 +43,13 @@
<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
#ifdef CRH_DIALOG_TREE_VIEW
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
#endif
<?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
<!DOCTYPE prefwindow [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
@ -54,129 +62,28 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
dlgbuttons="accept,cancel"
title="&sanitizeDialog2.title;"
noneverythingtitle="&sanitizeDialog2.title;"
style="width: &dialog.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("sanitizeButtonOK");
},
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 ()
{
// Update pref values before handing off to the sanitizer (bug 453440)
this.updatePrefs();
var s = new Sanitizer();
s.ignoreTimespan = false;
s.prefDomain = "privacy.cpd.";
try {
s.sanitize();
} catch (er) {
Components.utils.reportError("Exception during sanitize: " + er);
}
return true;
},
onReadGeneric: function ()
{
var preferences = document.getElementById("sanitizePreferences");
var found = false;
for (var i = 0; i < preferences.childNodes.length; ++i) {
var preference = preferences.childNodes[i];
if (preference.value && !preference.disabled) {
found = true;
break;
}
}
try {
document.documentElement.getButton("accept").disabled = !found;
}
catch (e) { }
return undefined;
},
<stringbundle id="bundleBrowser"
src="chrome://browser/locale/browser.properties"/>
onReadDownloads: function (aEvent)
{
// Call the common function that will update the accept button if needed
this.onReadGeneric();
<script type="application/javascript"
src="chrome://browser/content/sanitize.js"/>
let historyPref = document.getElementById("privacy.cpd.history")
let downloadPref = document.getElementById("privacy.cpd.downloads");
#ifdef CRH_DIALOG_TREE_VIEW
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/utils.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/treeView.js"/>
#endif
// 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;
},
updateDownloadHistory: function ()
{
// When toggling history, we automatically clear download history too,
// so we disable that control and set its value to true.
let downloads = document.getElementById("downloads-checkbox");
let history = document.getElementById("history-checkbox");
let s = new Sanitizer();
downloads.disabled = history.checked ||
!s.canClearItem("downloads");
if (history.checked)
downloads.checked = true;
},
updatePrefs : function ()
{
var tsPref = document.getElementById("privacy.sanitize.timeSpan");
Sanitizer.prefs.setIntPref("timeSpan", tsPref.value);
var sanitizePreferences = document.getElementById("sanitizePreferences");
var prefs = sanitizePreferences.rootBranch;
for (var i = 0; i < sanitizePreferences.childNodes.length; ++i) {
var p = sanitizePreferences.childNodes[i];
prefs.setBoolPref(p.name, p.value);
}
}
};
]]>
</script>
<script type="application/javascript"
src="chrome://browser/content/sanitizeDialog.js"/>
<preferences id="sanitizePreferences">
<preference id="privacy.cpd.history" name="privacy.cpd.history" type="bool"/>
@ -196,77 +103,121 @@
type="int"/>
</preferences>
<groupbox orient="vertical">
<caption label="&historySection.label;"/>
<hbox id="SanitizeDurationBox" align="center">
<label value="&clearTimeDuration.label;" control="sanitizeDurationChoice"
accesskey="&clearTimeDuration.accesskey;" id="sanitizeDurationLabel"/>
<menulist id="sanitizeDurationChoice"
preference="privacy.sanitize.timeSpan">
<menupopup>
<menuitem label="&clearTimeDuration.lastHour;" value="1"/>
<menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
<menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
<menuitem label="&clearTimeDuration.today;" value="4"/>
<menuseparator/>
<menuitem label="&clearTimeDuration.everything;" value="0"/>
</menupopup>
</menulist>
<label value="&clearTimeDuration.suffix;" flex="1"/>
</hbox>
<hbox>
<vbox style="width: &column.width;">
<checkbox id="history-checkbox"
label="&itemBrowsingHistory.label;"
accesskey="&itemBrowsingHistory.accesskey;"
preference="privacy.cpd.history"
oncommand="gSanitizePromptDialog.updateDownloadHistory();"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<checkbox id="downloads-checkbox"
label="&itemDownloadHistory.label;"
accesskey="&itemDownloadHistory.accesskey;"
preference="privacy.cpd.downloads"
onsyncfrompreference="return gSanitizePromptDialog.onReadDownloads();"/>
<checkbox label="&itemFormSearchHistory.label;"
accesskey="&itemFormSearchHistory.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="&itemCache.label;"
accesskey="&itemCache.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();"/>
<checkbox label="&itemSitePreferences.label;"
accesskey="&itemSitePreferences.accesskey;"
preference="privacy.cpd.siteSettings"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<hbox id="SanitizeDurationBox" align="center">
<label value="&clearTimeDuration.label;"
accesskey="&clearTimeDuration.accesskey;"
control="sanitizeDurationChoice"
id="sanitizeDurationLabel"/>
<menulist id="sanitizeDurationChoice"
preference="privacy.sanitize.timeSpan"
onselect="gSanitizePromptDialog.selectByTimespan();"
flex="1">
<menupopup id="sanitizeDurationPopup">
#ifdef CRH_DIALOG_TREE_VIEW
<menuitem label="" value="-1" id="sanitizeDurationCustom"/>
#endif
<menuitem label="&clearTimeDuration.lastHour;" value="1"/>
<menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
<menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
<menuitem label="&clearTimeDuration.today;" value="4"/>
<menuseparator/>
<menuitem label="&clearTimeDuration.everything;" value="0"/>
</menupopup>
</menulist>
<label id="sanitizeDurationSuffixLabel"
value="&clearTimeDuration.suffix;"/>
</hbox>
#ifdef CRH_DIALOG_TREE_VIEW
<deck id="durationDeck">
<tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
disabled="true" disableKeyNavigation="true">
<treecols>
<treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
<splitter class="tree-splitter"/>
<treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
</treecols>
<treechildren id="placesTreechildren"
ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
</tree>
#endif
<vbox id="sanitizeEverythingWarningBox">
<spacer flex="1"/>
<hbox align="center">
<image id="sanitizeEverythingWarningIcon"/>
<vbox id="sanitizeEverythingWarningDescBox">
<description id="sanitizeEverythingWarning"/>
<description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
</vbox>
</hbox>
<spacer flex="1"/>
</vbox>
<vbox style="width: &column.width;">
<checkbox label="&itemOfflineApps.label;"
accesskey="&itemOfflineApps.accesskey;"
preference="privacy.cpd.offlineApps"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
</vbox>
</groupbox>
#ifdef CRH_DIALOG_TREE_VIEW
</deck>
#endif
<separator class="thin"/>
<hbox id="detailsExpanderWrapper" align="center">
<button type="image"
id="detailsExpander"
class="expander-down"
oncommand="gSanitizePromptDialog.toggleItemList();"/>
<label id="detailsExpanderLabel"
value="&detailsProgressiveDisclosure.label;"
accesskey="&detailsProgressiveDisclosure.accesskey;"
control="detailsExpander"/>
</hbox>
<listbox id="itemList" rows="4" collapsed="true">
<listitem id="history-downloads-checkbox"
label="&itemHistoryAndDownloads.label;"
type="checkbox"
accesskey="&itemHistoryAndDownloads.accesskey;"
oncommand="gSanitizePromptDialog.onReadGeneric();"/>
<listitem label="&itemFormSearchHistory.label;"
type="checkbox"
accesskey="&itemFormSearchHistory.accesskey;"
preference="privacy.cpd.formdata"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<listitem label="&itemCookies.label;"
type="checkbox"
accesskey="&itemCookies.accesskey;"
preference="privacy.cpd.cookies"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<listitem label="&itemCache.label;"
type="checkbox"
accesskey="&itemCache.accesskey;"
preference="privacy.cpd.cache"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<listitem label="&itemActiveLogins.label;"
type="checkbox"
accesskey="&itemActiveLogins.accesskey;"
preference="privacy.cpd.sessions"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
<listitem label="&itemSitePreferences.label;"
type="checkbox"
accesskey="&itemSitePreferences.accesskey;"
preference="privacy.cpd.siteSettings"
noduration="true"
onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
</listbox>
<!-- The separate history and downloads prefs are subsumed by the combined
history-downloads checkbox, but by hiding them here we can take
advantage of the onsyncfrompreference events. -->
<checkbox id="history-checkbox"
preference="privacy.cpd.history"
onsyncfrompreference="return gSanitizePromptDialog.onReadHistoryOrDownloads();"
hidden="true"/>
<checkbox id="downloads-checkbox"
preference="privacy.cpd.downloads"
onsyncfrompreference="return gSanitizePromptDialog.onReadHistoryOrDownloads();"
hidden="true"/>
</prefpane>
</prefwindow>

View File

@ -0,0 +1,19 @@
/* Places tree */
#placesTreechildren {
-moz-user-focus: normal;
}
#placesTreechildren::-moz-tree-cell(grippyRow),
#placesTreechildren::-moz-tree-cell-text(grippyRow),
#placesTreechildren::-moz-tree-image(grippyRow) {
cursor: -moz-grab;
}
/* Sanitize everything warnings */
#sanitizeEverythingWarning,
#sanitizeEverythingUndoWarning {
white-space: pre-wrap;
}

View File

@ -0,0 +1,934 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Firefox Sanitizer.
*
* 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>
* Drew Willcoxon <adw@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 ***** */
const Cc = Components.classes;
const Ci = Components.interfaces;
var gSanitizePromptDialog = {
get bundleBrowser()
{
if (!this._bundleBrowser)
this._bundleBrowser = document.getElementById("bundleBrowser");
return this._bundleBrowser;
},
get selectedTimespan()
{
var durList = document.getElementById("sanitizeDurationChoice");
return parseInt(durList.value);
},
get sanitizePreferences()
{
if (!this._sanitizePreferences) {
this._sanitizePreferences =
document.getElementById("sanitizePreferences");
}
return this._sanitizePreferences;
},
get warningBox()
{
return document.getElementById("sanitizeEverythingWarningBox");
},
init: function ()
{
// This is used by selectByTimespan() to determine if the window has loaded.
this._inited = true;
this.checkPrefs();
var s = new Sanitizer();
s.prefDomain = "privacy.cpd.";
for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
var preference = this.sanitizePreferences.childNodes[i];
var name = s.getNameFromPreference(preference.name);
if (!s.canClearItem(name))
preference.disabled = true;
}
document.documentElement.getButton("accept").label =
this.bundleBrowser.getString("sanitizeButtonOK");
if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
this.ensureWarningIsInited();
this.warningBox.hidden = false;
}
else
this.warningBox.hidden = true;
},
selectByTimespan: function ()
{
// This method is the onselect handler for the duration dropdown. As a
// result it's called a couple of times before onload calls init().
if (!this._inited)
return;
var warningBox = this.warningBox;
// If clearing everything
if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
this.ensureWarningIsInited();
if (warningBox.hidden) {
warningBox.hidden = false;
window.resizeBy(0, warningBox.boxObject.height);
}
window.document.title =
this.bundleBrowser.getString("sanitizeDialog2.everything.title");
return;
}
// If clearing a specific time range
if (!warningBox.hidden) {
window.resizeBy(0, -warningBox.boxObject.height);
warningBox.hidden = true;
}
window.document.title =
window.document.documentElement.getAttribute("noneverythingtitle");
},
sanitize: function ()
{
// Update pref values before handing off to the sanitizer (bug 453440)
this.updatePrefs();
var s = new Sanitizer();
s.prefDomain = "privacy.cpd.";
s.range = Sanitizer.getClearRange(this.selectedTimespan);
s.ignoreTimespan = !s.range;
try {
s.sanitize();
} catch (er) {
Components.utils.reportError("Exception during sanitize: " + er);
}
return true;
},
/**
* If the panel that displays a warning when the duration is "Everything" is
* not set up, sets it up. Otherwise does nothing.
*/
ensureWarningIsInited: function ()
{
if (this._warningIsInited)
return;
this._warningIsInited = true;
// Get the number of items in history and the oldest item.
var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
var query = histServ.getNewQuery();
var opts = histServ.getNewQueryOptions();
opts.sortingMode = opts.SORT_BY_DATE_ASCENDING;
opts.queryType = opts.QUERY_TYPE_HISTORY;
var result = histServ.executeQuery(query, opts);
result.root.containerOpen = true;
var numItems = result.root.childCount;
var oldestTime = numItems > 0 ? result.root.getChild(0).time : null;
result.root.containerOpen = false;
var warningDesc = document.getElementById("sanitizeEverythingWarning");
warningDesc.textContent =
this.bundleBrowser.getString("sanitizeEverythingNoVisitsWarning");
},
checkPrefs : function ()
{
var prefService = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.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));
});
},
/**
* Called when the value of a preference element is synced from the actual
* pref. Enables or disables the OK button appropriately.
*/
onReadGeneric: function ()
{
// We don't update the separate history and downloads prefs until
// dialogaccept. So we need to handle the checked state of the combined
// history-downloads checkbox specially.
var combinedCb = document.getElementById("history-downloads-checkbox");
var found = combinedCb.checked;
// Find any other pref that's checked and enabled.
var i = 0;
while (!found && i < this.sanitizePreferences.childNodes.length) {
var preference = this.sanitizePreferences.childNodes[i];
// We took into account history and downloads above; don't do it again.
found = !!preference.value &&
!preference.disabled &&
preference.id !== "privacy.cpd.history" &&
preference.id !== "privacy.cpd.downloads";
i++;
}
try {
document.documentElement.getButton("accept").disabled = !found;
}
catch (e) { }
return undefined;
},
/**
* Called when the values of the history and downloads preference elements are
* synced from the actual prefs. Sets the state of the combined history-
* downloads checkbox appropriately.
*/
onReadHistoryOrDownloads: function ()
{
// Call the common function that will update the accept button
this.onReadGeneric();
var historyPref = document.getElementById("privacy.cpd.history");
var downloadsPref = document.getElementById("privacy.cpd.downloads");
var combinedCb = document.getElementById("history-downloads-checkbox");
combinedCb.disabled = historyPref.disabled && downloadsPref.disabled;
combinedCb.checked = historyPref.value || downloadsPref.value;
},
/**
* Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
* Because the type of this prefwindow is "child" -- and that's needed because
* without it the dialog has no OK and Cancel buttons -- the prefs are not
* updated on dialogaccept on platforms that don't support instant-apply
* (i.e., Windows). We must therefore manually set the prefs from their
* corresponding preference elements.
*/
updatePrefs : function ()
{
var tsPref = document.getElementById("privacy.sanitize.timeSpan");
Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
// First set the values of the separate history and downloads pref
// elements based on the combined history-downloads checkbox.
var combinedCbChecked =
document.getElementById("history-downloads-checkbox").checked;
var historyPref = document.getElementById("privacy.cpd.history");
historyPref.value = !historyPref.disabled && combinedCbChecked;
var downloadsPref = document.getElementById("privacy.cpd.downloads");
downloadsPref.value = !downloadsPref.disabled && combinedCbChecked;
// Now manually set the prefs from their corresponding preference
// elements.
var prefs = this.sanitizePreferences.rootBranch;
for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
var p = this.sanitizePreferences.childNodes[i];
prefs.setBoolPref(p.name, p.value);
}
},
/**
* Called by the item list expander button to toggle the list's visibility.
*/
toggleItemList: function ()
{
var itemList = document.getElementById("itemList");
var expanderButton = document.getElementById("detailsExpander");
// Showing item list
if (itemList.collapsed) {
expanderButton.className = "expander-up";
itemList.collapsed = false;
window.resizeBy(0, itemList.boxObject.height);
}
// Hiding item list
else {
expanderButton.className = "expander-down";
window.resizeBy(0, -itemList.boxObject.height);
itemList.collapsed = true;
}
}
#ifdef CRH_DIALOG_TREE_VIEW
// A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
// Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
// of the sanitizeDurationCustom menuitem.
get TIMESPAN_CUSTOM()
{
return -1;
},
get placesTree()
{
if (!this._placesTree)
this._placesTree = document.getElementById("placesTree");
return this._placesTree;
},
init: function ()
{
// This is used by selectByTimespan() to determine if the window has loaded.
this._inited = true;
this.checkPrefs();
var s = new Sanitizer();
s.prefDomain = "privacy.cpd.";
for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
var preference = this.sanitizePreferences.childNodes[i];
var name = s.getNameFromPreference(preference.name);
if (!s.canClearItem(name))
preference.disabled = true;
}
document.documentElement.getButton("accept").label =
this.bundleBrowser.getString("sanitizeButtonOK");
this.selectByTimespan();
},
/**
* Sets up the hashes this.durationValsToRows, which maps duration values
* to rows in the tree, this.durationRowsToVals, which maps rows in
* the tree to duration values, and this.durationStartTimes, which maps
* duration values to their corresponding start times.
*/
initDurationDropdown: function ()
{
// First, calculate the start times for each duration.
this.durationStartTimes = {};
var durVals = [];
var durPopup = document.getElementById("sanitizeDurationPopup");
var durMenuitems = durPopup.childNodes;
for (let i = 0; i < durMenuitems.length; i++) {
let durMenuitem = durMenuitems[i];
let durVal = parseInt(durMenuitem.value);
if (durMenuitem.localName === "menuitem" &&
durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
durVal !== this.TIMESPAN_CUSTOM) {
durVals.push(durVal);
let durTimes = Sanitizer.getClearRange(durVal);
this.durationStartTimes[durVal] = durTimes[0];
}
}
// Sort the duration values ascending. Because one tree index can map to
// more than one duration, this ensures that this.durationRowsToVals maps
// a row index to the largest duration possible in the code below.
durVals.sort();
// Now calculate the rows in the tree of the durations' start times. For
// each duration, we are looking for the node in the tree whose time is the
// smallest time greater than or equal to the duration's start time.
this.durationRowsToVals = {};
this.durationValsToRows = {};
var view = this.placesTree.view;
// For all rows in the tree except the grippy row...
for (let i = 0; i < view.rowCount - 1; i++) {
let unfoundDurVals = [];
let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
nodeForTreeIndex(i).time;
// For all durations whose rows have not yet been found in the tree, see
// if index i is their index. An index may map to more than one duration,
// in which case the final duration (the largest) wins.
for (let j = 0; j < durVals.length; j++) {
let durVal = durVals[j];
let durStartTime = this.durationStartTimes[durVal];
if (nodeTime < durStartTime) {
this.durationValsToRows[durVal] = i - 1;
this.durationRowsToVals[i - 1] = durVal;
}
else
unfoundDurVals.push(durVal);
}
durVals = unfoundDurVals;
}
// If any durations were not found above, then every node in the tree has a
// time greater than or equal to the duration. In other words, those
// durations include the entire tree (except the grippy row).
for (let i = 0; i < durVals.length; i++) {
let durVal = durVals[i];
this.durationValsToRows[durVal] = view.rowCount - 2;
this.durationRowsToVals[view.rowCount - 2] = durVal;
}
},
/**
* If the Places tree is not set up, sets it up. Otherwise does nothing.
*/
ensurePlacesTreeIsInited: function ()
{
if (this._placesTreeIsInited)
return;
this._placesTreeIsInited = true;
// Either "Last Four Hours" or "Today" will have the most history. If
// it's been more than 4 hours since today began, "Today" will. Otherwise
// "Last Four Hours" will.
var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
// If it's been less than 4 hours since today began, use the past 4 hours.
if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
}
var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
var query = histServ.getNewQuery();
query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
query.beginTime = times[0];
query.endTimeReference = query.TIME_RELATIVE_EPOCH;
query.endTime = times[1];
var opts = histServ.getNewQueryOptions();
opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
opts.queryType = opts.QUERY_TYPE_HISTORY;
var result = histServ.executeQuery(query, opts);
var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
new PlacesTreeView());
result.viewer = view;
this.initDurationDropdown();
},
/**
* Called on select of the duration dropdown and when grippyMoved() sets a
* duration based on the location of the grippy row. Selects all the nodes in
* the tree that are contained in the selected duration. If clearing
* everything, the warning panel is shown instead.
*/
selectByTimespan: function ()
{
// This method is the onselect handler for the duration dropdown. As a
// result it's called a couple of times before onload calls init().
if (!this._inited)
return;
var durDeck = document.getElementById("durationDeck");
var durList = document.getElementById("sanitizeDurationChoice");
var durVal = parseInt(durList.value);
var durCustom = document.getElementById("sanitizeDurationCustom");
// If grippy row is not at a duration boundary, show the custom menuitem;
// otherwise, hide it. Since the user cannot specify a custom duration by
// using the dropdown, this conditional is true only when this method is
// called onselect from grippyMoved(), so no selection need be made.
if (durVal === this.TIMESPAN_CUSTOM) {
durCustom.hidden = false;
return;
}
durCustom.hidden = true;
// If clearing everything, show the warning and change the dialog's title.
if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
this.ensureWarningIsInited();
durDeck.selectedIndex = 1;
window.document.title =
this.bundleBrowser.getString("sanitizeDialog2.everything.title");
document.documentElement.getButton("accept").disabled = false;
return;
}
// Otherwise -- if clearing a specific time range -- select that time range
// in the tree.
this.ensurePlacesTreeIsInited();
durDeck.selectedIndex = 0;
window.document.title =
window.document.documentElement.getAttribute("noneverythingtitle");
var durRow = this.durationValsToRows[durVal];
gContiguousSelectionTreeHelper.rangedSelect(durRow);
gContiguousSelectionTreeHelper.scrollToGrippy();
// If duration is empty (there are no selected rows), disable the dialog's
// OK button.
document.documentElement.getButton("accept").disabled = durRow < 0;
},
sanitize: function ()
{
// Update pref values before handing off to the sanitizer (bug 453440)
this.updatePrefs();
var s = new Sanitizer();
s.prefDomain = "privacy.cpd.";
var durList = document.getElementById("sanitizeDurationChoice");
var durValue = parseInt(durList.value);
s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
// Set the sanitizer's time range if we're not clearing everything.
if (!s.ignoreTimespan) {
// If user selected a custom timespan, use that.
if (durValue === this.TIMESPAN_CUSTOM) {
var view = this.placesTree.view;
var now = Date.now() * 1000;
// We disable the dialog's OK button if there's no selection, but we'll
// handle that case just in... case.
if (view.selection.getRangeCount() === 0)
s.range = [now, now];
else {
var startIndexRef = {};
// Tree sorted by visit date DEscending, so start time time comes last.
view.selection.getRangeAt(0, {}, startIndexRef);
view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
var startNode = view.nodeForTreeIndex(startIndexRef.value);
s.range = [startNode.time, now];
}
}
// Otherwise use the predetermined range.
else
s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
}
try {
s.sanitize();
} catch (er) {
Components.utils.reportError("Exception during sanitize: " + er);
}
return true;
},
/**
* In order to mark the custom Places tree view and its nsINavHistoryResult
* for garbage collection, we need to break the reference cycle between the
* two.
*/
unload: function ()
{
var view = this.placesTree.view;
view.QueryInterface(Ci.nsINavHistoryResultViewer).result.viewer = null;
this.placesTree.view = null;
},
/**
* Called when the user moves the grippy by dragging it, clicking in the tree,
* or on keypress. Updates the duration dropdown so that it displays the
* appropriate specific or custom duration.
*
* @param aEventName
* The name of the event whose handler called this method, e.g.,
* "ondragstart", "onkeypress", etc.
* @param aEvent
* The event captured in the event handler.
*/
grippyMoved: function (aEventName, aEvent)
{
gContiguousSelectionTreeHelper[aEventName](aEvent);
var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
var durList = document.getElementById("sanitizeDurationChoice");
var durValue = parseInt(durList.value);
// Multiple durations can map to the same row. Don't update the dropdown
// if the current duration is valid for lastSelRow.
if ((durValue !== this.TIMESPAN_CUSTOM ||
lastSelRow in this.durationRowsToVals) &&
(durValue === this.TIMESPAN_CUSTOM ||
this.durationValsToRows[durValue] !== lastSelRow)) {
// Setting durList.value causes its onselect handler to fire, which calls
// selectByTimespan().
if (lastSelRow in this.durationRowsToVals)
durList.value = this.durationRowsToVals[lastSelRow];
else
durList.value = this.TIMESPAN_CUSTOM;
}
// If there are no selected rows, disable the dialog's OK button.
document.documentElement.getButton("accept").disabled = lastSelRow < 0;
}
#endif
};
#ifdef CRH_DIALOG_TREE_VIEW
/**
* A helper for handling contiguous selection in the tree.
*/
var gContiguousSelectionTreeHelper = {
/**
* Gets the tree associated with this helper.
*/
get tree()
{
return this._tree;
},
/**
* Sets the tree that this module handles. The tree is assigned a new view
* that is equipped to handle contiguous selection. You can pass in an
* object that will be used as the prototype of the new view. Otherwise
* the tree's current view is used as the prototype.
*
* @param aTreeElement
* The tree element
* @param aProtoTreeView
* If defined, this will be used as the prototype of the tree's new
* view
* @return The new view
*/
setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
{
this._tree = aTreeElement;
var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
aTreeElement.view = newView;
return newView;
},
/**
* The index of the row that the grippy occupies. Note that the index of the
* last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
* no selection exists.
*
* @return The row index of the grippy
*/
getGrippyRow: function CSTH_getGrippyRow()
{
var sel = this.tree.view.selection;
var rangeCount = sel.getRangeCount();
if (rangeCount === 0)
return 0;
if (rangeCount !== 1) {
throw "contiguous selection tree helper: getGrippyRow called with " +
"multiple selection ranges";
}
var max = {};
sel.getRangeAt(0, {}, max);
return max.value + 1;
},
/**
* Helper function for the dragover event. Your dragover listener should
* call this. It updates the selection in the tree under the mouse.
*
* @param aEvent
* The observed dragover event
*/
ondragover: function CSTH_ondragover(aEvent)
{
// Without this when dragging on Windows the mouse cursor is a "no" sign.
// This makes it a drop symbol.
var ds = Cc["@mozilla.org/widget/dragservice;1"].
getService(Ci.nsIDragService).
getCurrentSession();
ds.canDrop = true;
ds.dragAction = 0;
var tbo = this.tree.treeBoxObject;
aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
if (hoverRow < 0)
return;
this.rangedSelect(hoverRow - 1);
},
/**
* Helper function for the dragstart event. Your dragstart listener should
* call this. It starts a drag session.
*
* @param aEvent
* The observed dragstart event
*/
ondragstart: function CSTH_ondragstart(aEvent)
{
var tbo = this.tree.treeBoxObject;
var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
if (clickedRow !== this.getGrippyRow())
return;
// This part is a hack. What we really want is a grab and slide, not
// drag and drop. Start a move drag session with dummy data and a
// dummy region. Set the region's coordinates to (Infinity, Infinity)
// so it's drawn offscreen and its size to (1, 1).
var arr = Cc["@mozilla.org/supports-array;1"].
createInstance(Ci.nsISupportsArray);
var trans = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
trans.setTransferData('dummy-flavor', null, 0);
arr.AppendElement(trans);
var reg = Cc["@mozilla.org/gfx/region;1"].
createInstance(Ci.nsIScriptableRegion);
reg.setToRect(Infinity, Infinity, 1, 1);
var ds = Cc["@mozilla.org/widget/dragservice;1"].
getService(Ci.nsIDragService);
ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
},
/**
* Helper function for the keypress event. Your keypress listener should
* call this. Users can use Up, Down, Page Up/Down, Home, and End to move
* the bottom of the selection window.
*
* @param aEvent
* The observed keypress event
*/
onkeypress: function CSTH_onkeypress(aEvent)
{
var grippyRow = this.getGrippyRow();
var tbo = this.tree.treeBoxObject;
var rangeEnd;
switch (aEvent.keyCode) {
case aEvent.DOM_VK_HOME:
rangeEnd = 0;
break;
case aEvent.DOM_VK_PAGE_UP:
rangeEnd = grippyRow - tbo.getPageLength();
break;
case aEvent.DOM_VK_UP:
rangeEnd = grippyRow - 2;
break;
case aEvent.DOM_VK_DOWN:
rangeEnd = grippyRow;
break;
case aEvent.DOM_VK_PAGE_DOWN:
rangeEnd = grippyRow + tbo.getPageLength();
break;
case aEvent.DOM_VK_END:
rangeEnd = this.tree.view.rowCount - 2;
break;
default:
return;
break;
}
aEvent.stopPropagation();
// First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
// select past the ends of the tree.
if (rangeEnd < 0)
rangeEnd = -1;
else if (this.tree.view.rowCount - 2 < rangeEnd)
rangeEnd = this.tree.view.rowCount - 2;
// Next, (de)select.
this.rangedSelect(rangeEnd);
// Finally, scroll the tree. We always want one row above and below the
// grippy row to be visible if possible.
if (rangeEnd < grippyRow) // moved up
tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
else { // moved down
if (rangeEnd + 2 < this.tree.view.rowCount)
tbo.ensureRowIsVisible(rangeEnd + 2);
else if (rangeEnd + 1 < this.tree.view.rowCount)
tbo.ensureRowIsVisible(rangeEnd + 1);
}
},
/**
* Helper function for the mousedown event. Your mousedown listener should
* call this. Users can click on individual rows to make the selection
* jump to them immediately.
*
* @param aEvent
* The observed mousedown event
*/
onmousedown: function CSTH_onmousedown(aEvent)
{
var tbo = this.tree.treeBoxObject;
var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
return;
if (clickedRow < this.getGrippyRow())
this.rangedSelect(clickedRow);
else if (clickedRow > this.getGrippyRow())
this.rangedSelect(clickedRow - 1);
},
/**
* Selects range [0, aEndRow] in the tree. The grippy row will then be at
* index aEndRow + 1. aEndRow may be -1, in which case the selection is
* cleared and the grippy row will be at index 0.
*
* @param aEndRow
* The range [0, aEndRow] will be selected.
*/
rangedSelect: function CSTH_rangedSelect(aEndRow)
{
var tbo = this.tree.treeBoxObject;
if (aEndRow < 0)
this.tree.view.selection.clearSelection();
else
this.tree.view.selection.rangedSelect(0, aEndRow, false);
tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
},
/**
* Scrolls the tree so that the grippy row is in the center of the view.
*/
scrollToGrippy: function CSTH_scrollToGrippy()
{
var rowCount = this.tree.view.rowCount;
var tbo = this.tree.treeBoxObject;
var pageLen = tbo.getPageLength() ||
parseInt(this.tree.getAttribute("rows")) ||
10;
// All rows fit on a single page.
if (rowCount <= pageLen)
return;
var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
// Grippy row is in first half of first page.
if (scrollToRow < 0)
scrollToRow = 0;
// Grippy row is in last half of last page.
else if (rowCount < scrollToRow + pageLen)
scrollToRow = rowCount - pageLen;
tbo.scrollToRow(scrollToRow);
},
/**
* Creates a new tree view suitable for contiguous selection. If
* aProtoTreeView is specified, it's used as the new view's prototype.
* Otherwise the tree's current view is used as the prototype.
*
* @param aProtoTreeView
* Used as the new view's prototype if specified
*/
_makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
{
var atomServ = Cc["@mozilla.org/atom-service;1"].
getService(Ci.nsIAtomService);
var view = aProtoTreeView;
var that = this;
//XXXadw: When Alex gets the grippy icon done, this may or may not change,
// depending on how we style it.
view.isSeparator = function CSTH_View_isSeparator(aRow)
{
return aRow === that.getGrippyRow();
};
// rowCount includes the grippy row.
view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
view.__defineGetter__("rowCount",
function CSTH_View_rowCount()
{
return this._rowCount + 1;
});
// This has to do with visual feedback in the view itself, e.g., drawing
// a small line underneath the dropzone. Not what we want.
view.canDrop = function CSTH_View_canDrop() { return false; };
// No clicking headers to sort the tree or sort feedback on columns.
view.cycleHeader = function CSTH_View_cycleHeader() {};
view.sortingChanged = function CSTH_View_sortingChanged() {};
// Override a bunch of methods to account for the grippy row.
view._getCellProperties = view.getCellProperties;
view.getCellProperties =
function CSTH_View_getCellProperties(aRow, aCol, aProps)
{
var grippyRow = that.getGrippyRow();
if (aRow === grippyRow)
aProps.AppendElement(atomServ.getAtom("grippyRow"));
else if (aRow < grippyRow)
this._getCellProperties(aRow, aCol, aProps);
else
this._getCellProperties(aRow - 1, aCol, aProps);
};
view._getRowProperties = view.getRowProperties;
view.getRowProperties =
function CSTH_View_getRowProperties(aRow, aProps)
{
var grippyRow = that.getGrippyRow();
if (aRow === grippyRow)
aProps.AppendElement(atomServ.getAtom("grippyRow"));
else if (aRow < grippyRow)
this._getRowProperties(aRow, aProps);
else
this._getRowProperties(aRow - 1, aProps);
};
view._getCellText = view.getCellText;
view.getCellText =
function CSTH_View_getCellText(aRow, aCol)
{
var grippyRow = that.getGrippyRow();
if (aRow === grippyRow)
return "";
aRow = aRow < grippyRow ? aRow : aRow - 1;
return this._getCellText(aRow, aCol);
};
view._getImageSrc = view.getImageSrc;
view.getImageSrc =
function CSTH_View_getImageSrc(aRow, aCol)
{
var grippyRow = that.getGrippyRow();
if (aRow === grippyRow)
return "";
aRow = aRow < grippyRow ? aRow : aRow - 1;
return this._getImageSrc(aRow, aCol);
};
view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
{
return aRow < this.rowCount - 1;
};
return view;
}
};
#endif

View File

@ -63,6 +63,11 @@ _TEST_FILES = test_feed_discovery.html \
# browser_bug321000.js is bug 474081
# browser_bug423833.js is bug 428712
# browser_sanitize-download-history.js is bug 432425
#
# browser_sanitizeDialog_treeView.js is disabled until the tree view is added
# back to the clear recent history dialog (santize.xul), if it ever is (bug
# 480169)
_BROWSER_FILES = browser_sanitize-timespans.js \
browser_bug405137.js \
browser_bug409481.js \
@ -107,6 +112,7 @@ _BROWSER_FILES = browser_sanitize-timespans.js \
browser_bug479408_sample.html \
browser_scope.js \
browser_overflowScroll.js \
browser_sanitizeDialog.js \
$(NULL)
ifeq (,$(filter mac cocoa,$(MOZ_WIDGET_TOOLKIT)))

View File

@ -12,6 +12,7 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.moz
function test() {
var hoursSinceMidnight = new Date().getHours();
var minutesSinceMidnight = new Date().getMinutes();
setupHistory();
setupFormHistory();
@ -35,46 +36,131 @@ function test() {
itemPrefs.setBoolPref("passwords", false);
itemPrefs.setBoolPref("sessions", false);
itemPrefs.setBoolPref("siteSettings", false);
// Clear 10 minutes ago
s.range = [now_uSec - 10*60*1000000, now_uSec];
s.sanitize();
s.range = null;
ok(!bhist.isVisited(uri("http://10minutes.com")), "10minutes.com should now be deleted");
ok(bhist.isVisited(uri("http://1hour.com")), "Pretend visit to 1hour.com should still exist");
ok(bhist.isVisited(uri("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
ok(bhist.isVisited(uri("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
if(minutesSinceMidnight > 10)
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(!formhist.nameExists("10minutes"), "10minutes form entry should be deleted");
ok(formhist.nameExists("1hour"), "1hour form entry should still exist");
ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(minutesSinceMidnight > 10)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555555), "10 minute download should now be deleted");
ok(downloadExists(5555551), "<1 hour download should still be present");
ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
ok(downloadExists(5555550), "Year old download should still be present");
ok(downloadExists(5555552), "<2 hour old download should still be present");
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
ok(downloadExists(5555553), "<4 hour old download should still be present");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
if(minutesSinceMidnight > 10)
ok(downloadExists(5555554), "'Today' download should still be present");
// 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://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
ok(bhist.isVisited(uri("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.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(!formhist.nameExists("1hour"), "1hour form entry should be deleted");
ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(hoursSinceMidnight > 1)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555551), "<1 hour download should now be deleted");
ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
ok(downloadExists(5555550), "Year old download should still be present");
ok(downloadExists(5555552), "<2 hour old download should still be present");
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
ok(downloadExists(5555553), "<4 hour old download should still be present");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
if(hoursSinceMidnight > 1)
ok(downloadExists(5555554), "'Today' download should still be present");
// Clear 1 hour 10 minutes
s.range = [now_uSec - 70*60*1000000, now_uSec];
s.sanitize();
s.range = null;
ok(!bhist.isVisited(uri("http://1hour10minutes.com")), "Pretend visit to 1hour10minutes.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://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
if(minutesSinceMidnight > 70)
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(!formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should be deleted");
ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(minutesSinceMidnight > 70)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555556), "1 hour 10 minute old 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(5555557), "2 hour 10 minute download should still be present");
ok(downloadExists(5555553), "<4 hour old download should still be present");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
if(minutesSinceMidnight > 70)
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://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.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(!formhist.nameExists("2hour"), "2hour form entry should be deleted");
ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(hoursSinceMidnight > 2)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
@ -82,29 +168,80 @@ function test() {
ok(formhist.nameExists("b4today"), "b4today form entry 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(5555557), "2 hour 10 minute download should still be present");
ok(downloadExists(5555553), "<4 hour old download should still be present");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
if(hoursSinceMidnight > 2)
ok(downloadExists(5555554), "'Today' download should still be present");
// Clear 2 hours 10 minutes
s.range = [now_uSec - 130*60*1000000, now_uSec];
s.sanitize();
s.range = null;
ok(!bhist.isVisited(uri("http://2hour10minutes.com")), "Pretend visit to 2hour10minutes.com should now be deleted");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
if(minutesSinceMidnight > 130)
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(!formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should be deleted");
ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(minutesSinceMidnight > 130)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555557), "2 hour 10 minute old download should now be deleted");
ok(downloadExists(5555553), "<4 hour old download should still be present");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
ok(downloadExists(5555550), "Year old download should still be present");
if(minutesSinceMidnight > 130)
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");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
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(!formhist.nameExists("4hour"), "4hour form entry should be deleted");
ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
if(hoursSinceMidnight > 4)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
ok(downloadExists(5555550), "Year old download should still be present");
if(hoursSinceMidnight > 4)
ok(downloadExists(5555554), "'Today' download should still be present");
// Clear 4 hours 10 minutes
s.range = [now_uSec - 250*60*1000000, now_uSec];
s.sanitize();
s.range = null;
ok(!bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should now be deleted");
if(minutesSinceMidnight > 250)
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(!formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should be deleted");
if(minutesSinceMidnight > 250)
ok(formhist.nameExists("today"), "today form entry should still exist");
ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
ok(!downloadExists(5555558), "4 hour 10 minute download should now be deleted");
ok(downloadExists(5555550), "Year old download should still be present");
if(minutesSinceMidnight > 250)
ok(downloadExists(5555554), "'Today' download should still be present");
// Clear Today
Sanitizer.prefs.setIntPref("timeSpan", 4);
s.sanitize();
@ -131,9 +268,13 @@ function test() {
}
function setupHistory() {
bhist.addPageWithDetails(uri("http://10minutes.com/"), "10 minutes ago", now_uSec - 10*60*1000000);
bhist.addPageWithDetails(uri("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45*60*1000000);
bhist.addPageWithDetails(uri("http://1hour10minutes.com/"), "1 hour 10 minutes ago", now_uSec - 70*60*1000000);
bhist.addPageWithDetails(uri("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90*60*1000000);
bhist.addPageWithDetails(uri("http://2hour10minutes.com/"), "2 hours 10 minutes ago", now_uSec - 130*60*1000000);
bhist.addPageWithDetails(uri("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180*60*1000000);
bhist.addPageWithDetails(uri("http://4hour10minutes.com/"), "4 hours 10 minutesago", now_uSec - 250*60*1000000);
let today = new Date();
today.setHours(0);
@ -146,9 +287,13 @@ function setupHistory() {
bhist.addPageWithDetails(uri("http://before-today.com/"), "Before Today", lastYear.valueOf() * 1000);
// Confirm everything worked
ok(bhist.isVisited(uri("http://10minutes.com/")), "Pretend visit to 10minutes.com should exist");
ok(bhist.isVisited(uri("http://1hour.com")), "Pretend visit to 1hour.com should exist");
ok(bhist.isVisited(uri("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should exist");
ok(bhist.isVisited(uri("http://2hour.com")), "Pretend visit to 2hour.com should exist");
ok(bhist.isVisited(uri("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should exist");
ok(bhist.isVisited(uri("http://4hour.com")), "Pretend visit to 4hour.com should exist");
ok(bhist.isVisited(uri("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.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");
}
@ -158,23 +303,39 @@ function setupFormHistory() {
formhist.removeAllEntries();
// Add the entries we'll be testing.
formhist.addEntry("10minutes", "10m");
formhist.addEntry("1hour", "1h");
formhist.addEntry("1hour10minutes", "1h10m");
formhist.addEntry("2hour", "2h");
formhist.addEntry("2hour10minutes", "2h10m");
formhist.addEntry("4hour", "4h");
formhist.addEntry("4hour10minutes", "4h10m");
formhist.addEntry("today", "1d");
formhist.addEntry("b4today", "1y");
// Artifically age the entries to the proper vintage.
let db = formhist.DBConnection;
let timestamp = now_uSec - 45*60*1000000;
let timestamp = now_uSec - 10*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '10minutes'");
timestamp = now_uSec - 45*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '1hour'");
timestamp = now_uSec - 70*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '1hour10minutes'");
timestamp = now_uSec - 90*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '2hour'");
timestamp = now_uSec - 130*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '2hour10minutes'");
timestamp = now_uSec - 180*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '4hour'");
timestamp = now_uSec - 250*60*1000000;
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '4hour10minutes'");
let today = new Date();
today.setHours(0);
@ -191,23 +352,27 @@ function setupFormHistory() {
timestamp + " WHERE fieldname = 'b4today'");
// Sanity check.
ok(formhist.nameExists("10minutes"), "Checking for 10minutes form history entry creation");
ok(formhist.nameExists("1hour"), "Checking for 1hour form history entry creation");
ok(formhist.nameExists("1hour10minutes"), "Checking for 1hour10minutes form history entry creation");
ok(formhist.nameExists("2hour"), "Checking for 2hour form history entry creation");
ok(formhist.nameExists("2hour10minutes"), "Checking for 2hour10minutes form history entry creation");
ok(formhist.nameExists("4hour"), "Checking for 4hour form history entry creation");
ok(formhist.nameExists("4hour10minutes"), "Checking for 4hour10minutes form history entry creation");
ok(formhist.nameExists("today"), "Checking for today form history entry creation");
ok(formhist.nameExists("b4today"), "Checking for b4today form history entry creation");
}
function setupDownloads() {
// Add within-1-hour download to DB
// Add 10-minutes 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
id: "5555555",
name: "fakefile-10-minutes",
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: "fakefile-10-minutes",
startTime: now_uSec - 10*60*1000000, // 10 minutes ago, in uSec
endTime: now_uSec - 11*60*1000000, // 1 minute later
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
};
@ -227,6 +392,48 @@ function setupDownloads() {
stmt.reset();
}
// Add within-1-hour download to DB
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
};
try {
for (let prop in data)
stmt.params[prop] = data[prop];
stmt.execute();
}
finally {
stmt.reset();
}
// Add 1-hour-10-minutes download to DB
data = {
id: "5555556",
name: "fakefile-1-hour-10-minutes",
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: "fakefile-1-hour-10-minutes",
startTime: now_uSec - 70*60*1000000, // 70 minutes ago, in uSec
endTime: now_uSec - 71*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-2-hour download
data = {
id: "5555552",
@ -248,6 +455,27 @@ function setupDownloads() {
stmt.reset();
}
// Add 2-hour-10-minutes download
data = {
id: "5555557",
name: "fakefile-2-hour-10-minutes",
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: "fakefile-2-hour-10-minutes",
startTime: now_uSec - 130*60*1000000, // 130 minutes ago, in uSec
endTime: now_uSec - 131*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",
@ -269,6 +497,27 @@ function setupDownloads() {
stmt.reset();
}
// Add 4-hour-10-minutes download
data = {
id: "5555558",
name: "fakefile-4-hour-10-minutes",
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: "fakefile-4-hour-10-minutes",
startTime: now_uSec - 250*60*1000000, // 250 minutes ago, in uSec
endTime: now_uSec - 251*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);
@ -320,9 +569,13 @@ function setupDownloads() {
// Confirm everything worked
ok(downloadExists(5555550), "Pretend download for everything case should exist");
ok(downloadExists(5555555), "Pretend download for 10-minutes case should exist");
ok(downloadExists(5555551), "Pretend download for 1-hour case should exist");
ok(downloadExists(5555556), "Pretend download for 1-hour-10-minutes case should exist");
ok(downloadExists(5555552), "Pretend download for 2-hour case should exist");
ok(downloadExists(5555557), "Pretend download for 2-hour-10-minutes case should exist");
ok(downloadExists(5555553), "Pretend download for 4-hour case should exist");
ok(downloadExists(5555558), "Pretend download for 4-hour-10-minutes case should exist");
ok(downloadExists(5555554), "Pretend download for Today case should exist");
}

View File

@ -0,0 +1,721 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is sanitize dialog test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (Original Author)
*
* 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 ***** */
/**
* Tests the sanitize dialog (a.k.a. the clear recent history dialog).
* See bug 480169.
*
* The purpose of this test is not to fully flex the sanitize timespan code;
* browser/base/content/test/browser_sanitize-timespans.js does that. This
* test checks the UI of the dialog and makes sure it's correctly connected to
* the sanitize timespan code.
*
* Some of this code, especially the history creation parts, was taken from
* browser/base/content/test/browser_sanitize-timespans.js.
*/
Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Components.interfaces.mozIJSSubScriptLoader).
loadSubScript("chrome://mochikit/content/MochiKit/packed.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Components.interfaces.mozIJSSubScriptLoader).
loadSubScript("chrome://browser/content/sanitize.js");
const winWatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
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);
const formhist = Cc["@mozilla.org/satchel/form-history;1"].
getService(Ci.nsIFormHistory2);
// Add tests here. Each is a function that's called by doNextTest().
var gAllTests = [
/**
* Cancels the dialog, makes sure history not cleared.
*/
function () {
// Add history (within the past hour)
let uris = [];
for (let i = 0; i < 30; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
let wh = new WindowHelper();
wh.onload = function () {
this.selectDuration(Sanitizer.TIMESPAN_HOUR);
this.checkPrefCheckbox("history-downloads-checkbox", false);
this.checkDetails();
this.toggleDetails();
this.checkDetails();
this.cancelDialog();
ensureHistoryClearedState(uris, false);
blankSlate();
ensureHistoryClearedState(uris, true);
};
wh.open();
},
/**
* Ensures that the combined history-downloads checkbox clears both history
* visits and downloads when checked; the dialog respects simple timespan.
*/
function () {
// Add history and downloads (within the past hour).
let uris = [];
for (let i = 0; i < 30; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
let downloadIDs = [];
for (let i = 0; i < 5; i++) {
downloadIDs.push(addDownloadWithMinutesAgo(i));
}
// Add history and downloads (over an hour ago).
let olderURIs = [];
for (let i = 0; i < 5; i++) {
olderURIs.push(addHistoryWithMinutesAgo(61 + i));
}
let olderDownloadIDs = [];
for (let i = 0; i < 5; i++) {
olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
}
let totalHistoryVisits = uris.length + olderURIs.length;
let wh = new WindowHelper();
wh.onload = function () {
this.selectDuration(Sanitizer.TIMESPAN_HOUR);
this.checkPrefCheckbox("history-downloads-checkbox", true);
this.acceptDialog();
intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
"timeSpan pref should be hour after accepting dialog with " +
"hour selected");
boolPrefIs("cpd.history", true,
"history pref should be true after accepting dialog with " +
"combined history-downloads checkbox checked");
boolPrefIs("cpd.downloads", true,
"downloads pref should be true after accepting dialog with " +
"combined history-downloads checkbox checked");
// History visits and downloads within one hour should be cleared.
ensureHistoryClearedState(uris, true);
ensureDownloadsClearedState(downloadIDs, true);
// Visits and downloads > 1 hour should still exist.
ensureHistoryClearedState(olderURIs, false);
ensureDownloadsClearedState(olderDownloadIDs, false);
// OK, done, cleanup after ourselves.
blankSlate();
ensureHistoryClearedState(olderURIs, true);
ensureDownloadsClearedState(olderDownloadIDs, true);
};
wh.open();
},
/**
* Ensures that the combined history-downloads checkbox removes neither
* history visits nor downloads when not checked.
*/
function () {
// Add history, downloads, form entries (within the past hour).
let uris = [];
for (let i = 0; i < 5; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
let downloadIDs = [];
for (let i = 0; i < 5; i++) {
downloadIDs.push(addDownloadWithMinutesAgo(i));
}
let formEntries = [];
for (let i = 0; i < 5; i++) {
formEntries.push(addFormEntryWithMinutesAgo(i));
}
let wh = new WindowHelper();
wh.onload = function () {
is(this.isWarningPanelVisible(), false,
"Warning panel should be hidden after previously accepting dialog " +
"with a predefined timespan");
this.selectDuration(Sanitizer.TIMESPAN_HOUR);
// Remove only form entries, leave history and downloads.
this.checkPrefCheckbox("history-downloads-checkbox", false);
this.checkPrefCheckbox("formdata", true);
this.acceptDialog();
intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
"timeSpan pref should be hour after accepting dialog with " +
"hour selected");
boolPrefIs("cpd.history", false,
"history pref should be false after accepting dialog with " +
"combined history-downloads checkbox unchecked");
boolPrefIs("cpd.downloads", false,
"downloads pref should be false after accepting dialog with " +
"combined history-downloads checkbox unchecked");
// Of the three only form entries should be cleared.
ensureHistoryClearedState(uris, false);
ensureDownloadsClearedState(downloadIDs, false);
ensureFormEntriesClearedState(formEntries, true);
// OK, done, cleanup after ourselves.
blankSlate();
ensureHistoryClearedState(uris, true);
ensureDownloadsClearedState(downloadIDs, true);
};
wh.open();
},
/**
* Ensures that the "Everything" duration option works.
*/
function () {
// Add history.
let uris = [];
uris.push(addHistoryWithMinutesAgo(10)); // within past hour
uris.push(addHistoryWithMinutesAgo(70)); // within past two hours
uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
let wh = new WindowHelper();
wh.onload = function () {
is(this.isWarningPanelVisible(), false,
"Warning panel should be hidden after previously accepting dialog " +
"with a predefined timespan");
this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
this.checkPrefCheckbox("history-downloads-checkbox", true);
this.checkDetails();
this.toggleDetails();
this.checkDetails();
this.acceptDialog();
intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
"timeSpan pref should be everything after accepting dialog " +
"with everything selected");
ensureHistoryClearedState(uris, true);
};
wh.open();
},
/**
* Ensures that the "Everything" warning is visible on dialog open after
* the previous test.
*/
function () {
// Add history.
let uris = [];
uris.push(addHistoryWithMinutesAgo(10)); // within past hour
uris.push(addHistoryWithMinutesAgo(70)); // within past two hours
uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
let wh = new WindowHelper();
wh.onload = function () {
is(this.isWarningPanelVisible(), true,
"Warning panel should be visible after previously accepting dialog " +
"with clearing everything");
this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
this.checkPrefCheckbox("history-downloads-checkbox", true);
this.acceptDialog();
intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
"timeSpan pref should be everything after accepting dialog " +
"with everything selected");
ensureHistoryClearedState(uris, true);
};
wh.open();
}
];
// Used as the download database ID for a new download. Incremented for each
// new download. See addDownloadWithMinutesAgo().
var gDownloadId = 5555551;
// Index in gAllTests of the test currently being run. Incremented for each
// test run. See doNextTest().
var gCurrTest = 0;
var now_uSec = Date.now() * 1000;
///////////////////////////////////////////////////////////////////////////////
/**
* This wraps the dialog and provides some convenience methods for interacting
* with it.
*
* @param aWin
* The dialog's nsIDOMWindow
*/
function WindowHelper(aWin) {
this.win = aWin;
}
WindowHelper.prototype = {
/**
* "Presses" the dialog's OK button.
*/
acceptDialog: function () {
is(this.win.document.documentElement.getButton("accept").disabled, false,
"Dialog's OK button should not be disabled");
this.win.document.documentElement.acceptDialog();
},
/**
* "Presses" the dialog's Cancel button.
*/
cancelDialog: function () {
this.win.document.documentElement.cancelDialog();
},
/**
* Ensures that the details progressive disclosure button and the item list
* hidden by it match up. Also makes sure the height of the dialog is
* sufficient for the item list and warning panel.
*/
checkDetails: function () {
let button = this.getDetailsButton();
let list = this.getItemList();
let hidden = list.hidden || list.collapsed;
let dir = hidden ? "down" : "up";
is(button.className, "expander-" + dir,
"Details button should be " + dir + " because item list is " +
(hidden ? "" : "not ") + "hidden");
let height = 0;
if (!hidden)
height += list.boxObject.height;
if (this.isWarningPanelVisible())
height += this.getWarningPanel().boxObject.height;
ok(height < this.win.innerHeight,
"Window should be tall enough to fit warning panel and item list");
},
/**
* (Un)checks a history scope checkbox (browser & download history,
* form history, etc.).
*
* @param aPrefName
* Either the ID of the checkbox or the final portion of its
* privacy.cpd.* preference name
* @param aCheckState
* True if the checkbox should be checked, false otherwise
*/
checkPrefCheckbox: function (aPrefName, aCheckState) {
let checkBoxes = this.win.document.getElementsByTagName("listitem");
for (let i = 0; i < checkBoxes.length; i++) {
let cb = checkBoxes[i];
if (cb.id === aPrefName ||
(cb.hasAttribute("preference") &&
cb.getAttribute("preference") === "privacy.cpd." + aPrefName)) {
cb.checked = aCheckState;
break;
}
}
},
/**
* @return The details progressive disclosure button
*/
getDetailsButton: function () {
return this.win.document.getElementById("detailsExpander");
},
/**
* @return The dialog's duration dropdown
*/
getDurationDropdown: function () {
return this.win.document.getElementById("sanitizeDurationChoice");
},
/**
* @return The item list hidden by the details progressive disclosure button
*/
getItemList: function () {
return this.win.document.getElementById("itemList");
},
/**
* @return The clear-everything warning box
*/
getWarningPanel: function () {
return this.win.document.getElementById("sanitizeEverythingWarningBox");
},
/**
* @return True if the "Everything" warning panel is visible (as opposed to
* the tree)
*/
isWarningPanelVisible: function () {
return !this.getWarningPanel().hidden;
},
/**
* Opens the clear recent history dialog. Before calling this, set
* this.onload to a function to execute onload. It should close the dialog
* when done so that the tests may continue. Set this.onunload to a function
* to execute onunload. this.onunload is optional.
*/
open: function () {
let wh = this;
let windowObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic !== "domwindowopened")
return;
winWatch.unregisterNotification(this);
var loaded = false;
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function onload(event) {
win.removeEventListener("load", onload, false);
if (win.name !== "SanitizeDialog")
return;
wh.win = win;
loaded = true;
executeSoon(function () {
// Some exceptions that reach here don't reach the test harness, but
// ok()/is() do...
try {
wh.onload();
}
catch (exc) {
win.close();
ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
finish();
}
});
}, false);
win.addEventListener("unload", function onunload(event) {
if (win.name !== "SanitizeDialog") {
win.removeEventListener("unload", onunload, false);
return;
}
// Why is unload fired before load?
if (!loaded)
return;
win.removeEventListener("unload", onunload, false);
wh.win = win;
executeSoon(function () {
// Some exceptions that reach here don't reach the test harness, but
// ok()/is() do...
try {
if (wh.onunload)
wh.onunload();
doNextTest();
}
catch (exc) {
win.close();
ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
finish();
}
});
}, false);
}
};
winWatch.registerNotification(windowObserver);
winWatch.openWindow(null,
"chrome://browser/content/sanitize.xul",
"SanitizeDialog",
"chrome,titlebar,dialog,centerscreen,modal",
null);
},
/**
* Selects a duration in the duration dropdown.
*
* @param aDurVal
* One of the Sanitizer.TIMESPAN_* values
*/
selectDuration: function (aDurVal) {
this.getDurationDropdown().value = aDurVal;
if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
is(this.isWarningPanelVisible(), true,
"Warning panel should be visible for TIMESPAN_EVERYTHING");
}
else {
is(this.isWarningPanelVisible(), false,
"Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
}
},
/**
* Toggles the details progressive disclosure button.
*/
toggleDetails: function () {
this.getDetailsButton().click();
}
};
/**
* Adds a download to history.
*
* @param aMinutesAgo
* The download will be downloaded this many minutes ago
*/
function addDownloadWithMinutesAgo(aMinutesAgo) {
let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
let data = {
id: gDownloadId,
name: name,
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: name,
startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
endTime: now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
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();
}
is(downloadExists(gDownloadId), true,
"Sanity check: download " + gDownloadId +
" should exist after creating it");
return gDownloadId++;
}
/**
* Adds a form entry to history.
*
* @param aMinutesAgo
* The entry will be added this many minutes ago
*/
function addFormEntryWithMinutesAgo(aMinutesAgo) {
let name = aMinutesAgo + "-minutes-ago";
formhist.addEntry(name, "dummy");
// Artifically age the entry to the proper vintage.
let db = formhist.DBConnection;
let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '" + name + "'");
is(formhist.nameExists(name), true,
"Sanity check: form entry " + name + " should exist after creating it");
return name;
}
/**
* Adds a history visit to history.
*
* @param aMinutesAgo
* The visit will be visited this many minutes ago
*/
function addHistoryWithMinutesAgo(aMinutesAgo) {
let pURI = uri("http://" + aMinutesAgo + "-minutes-ago.com/");
bhist.addPageWithDetails(pURI,
aMinutesAgo + " minutes ago",
now_uSec - (aMinutesAgo * 60 * 1000 * 1000));
is(bhist.isVisited(pURI), true,
"Sanity check: history visit " + pURI.spec +
" should exist after creating it");
return pURI;
}
/**
* Removes all history visits, downloads, and form entries.
*/
function blankSlate() {
bhist.removeAllPages();
dm.cleanUp();
formhist.removeAllEntries();
}
/**
* Ensures that the given pref is the expected value.
*
* @param aPrefName
* The pref's sub-branch under the privacy branch
* @param aExpectedVal
* The pref's expected value
* @param aMsg
* Passed to is()
*/
function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
let prefs = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).
getBranch("privacy.");
is(prefs.getBoolPref(aPrefName), aExpectedVal, aMsg);
}
/**
* Checks to see if the download with the specified ID exists.
*
* @param aID
* The ID of the download to check
* @return True if the download exists, false otherwise
*/
function downloadExists(aID)
{
let db = dm.DBConnection;
let stmt = db.createStatement(
"SELECT * " +
"FROM moz_downloads " +
"WHERE id = :id"
);
stmt.params.id = aID;
let rows = stmt.step();
stmt.finalize();
return !!rows;
}
/**
* Runs the next test in the gAllTests array. If all tests have been run,
* finishes the entire suite.
*/
function doNextTest() {
if (gAllTests.length <= gCurrTest) {
blankSlate();
finish();
}
else {
let ct = gCurrTest;
gCurrTest++;
gAllTests[ct]();
}
}
/**
* Ensures that the specified downloads are either cleared or not.
*
* @param aDownloadIDs
* Array of download database IDs
* @param aShouldBeCleared
* True if each download should be cleared, false otherwise
*/
function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aDownloadIDs.forEach(function (id) {
is(downloadExists(id), !aShouldBeCleared,
"download " + id + " should " + niceStr + " exist");
});
}
/**
* Ensures that the specified form entries are either cleared or not.
*
* @param aFormEntries
* Array of form entry names
* @param aShouldBeCleared
* True if each form entry should be cleared, false otherwise
*/
function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aFormEntries.forEach(function (entry) {
is(formhist.nameExists(entry), !aShouldBeCleared,
"form entry " + entry + " should " + niceStr + " exist");
});
}
/**
* Ensures that the specified URIs are either cleared or not.
*
* @param aURIs
* Array of page URIs
* @param aShouldBeCleared
* True if each visit to the URI should be cleared, false otherwise
*/
function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aURIs.forEach(function (aURI) {
is(bhist.isVisited(aURI), !aShouldBeCleared,
"history visit " + aURI.spec + " should " + niceStr + " exist");
});
}
/**
* Ensures that the given pref is the expected value.
*
* @param aPrefName
* The pref's sub-branch under the privacy branch
* @param aExpectedVal
* The pref's expected value
* @param aMsg
* Passed to is()
*/
function intPrefIs(aPrefName, aExpectedVal, aMsg) {
let prefs = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).
getBranch("privacy.");
is(prefs.getIntPref(aPrefName), aExpectedVal, aMsg);
}
/**
* @return A new nsIURI from aSpec.
*/
function uri(aSpec) {
return iosvc.newURI(aSpec, null, null);
}
///////////////////////////////////////////////////////////////////////////////
function test() {
blankSlate();
waitForExplicitFinish();
// Kick off all the tests in the gAllTests array.
doNextTest();
}

View File

@ -0,0 +1,674 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is sanitize dialog test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (Original Author)
*
* 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 ***** */
/**
* Tests the sanitize dialog (a.k.a. the clear recent history dialog).
* See bug 480169.
*
* The purpose of this test is not to fully flex the sanitize timespan code;
* browser/base/content/test/browser_sanitize-timespans.js does that. This
* test checks the UI of the dialog and makes sure it's correctly connected to
* the sanitize timespan code.
*
* Some of this code, especially the history creation parts, was taken from
* browser/base/content/test/browser_sanitize-timespans.js.
*/
Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Components.interfaces.mozIJSSubScriptLoader).
loadSubScript("chrome://mochikit/content/MochiKit/packed.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Components.interfaces.mozIJSSubScriptLoader).
loadSubScript("chrome://browser/content/sanitize.js");
const winWatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
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);
const formhist = Cc["@mozilla.org/satchel/form-history;1"].
getService(Ci.nsIFormHistory2);
// Add tests here. Each is a function that's called by doNextTest().
var gAllTests = [
/**
* Moves the grippy around, makes sure it works OK.
*/
function () {
// Add history (within the past hour) to get some rows in the tree.
let uris = [];
for (let i = 0; i < 30; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
// Open the dialog and do our tests.
openWindow(function (aWin) {
let wh = new WindowHelper(aWin);
wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
"duration",
wh.getRowCount() - 1);
// Move the grippy around.
let row = wh.getGrippyRow();
while (row !== 0) {
row--;
wh.moveGrippyBy(-1);
wh.checkGrippy("Grippy should be moved up one row", row);
}
wh.moveGrippyBy(-1);
wh.checkGrippy("Grippy should remain at first row after trying to move " +
"it up",
0);
while (row !== wh.getRowCount() - 1) {
row++;
wh.moveGrippyBy(1);
wh.checkGrippy("Grippy should be moved down one row", row);
}
wh.moveGrippyBy(1);
wh.checkGrippy("Grippy should remain at last row after trying to move " +
"it down",
wh.getRowCount() - 1);
// Cancel the dialog, make sure history visits are not cleared.
wh.checkPrefCheckbox("history-downloads-checkbox", false);
wh.cancelDialog();
ensureHistoryClearedState(uris, false);
// OK, done, cleanup after ourselves.
blankSlate();
ensureHistoryClearedState(uris, true);
});
},
/**
* Ensures that the combined history-downloads checkbox clears both history
* visits and downloads when checked; the dialog respects simple timespan.
*/
function () {
// Add history and downloads (within the past hour).
let uris = [];
for (let i = 0; i < 30; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
let downloadIDs = [];
for (let i = 0; i < 5; i++) {
downloadIDs.push(addDownloadWithMinutesAgo(i));
}
// Add history and downloads (over an hour ago).
let olderURIs = [];
for (let i = 0; i < 5; i++) {
olderURIs.push(addHistoryWithMinutesAgo(61 + i));
}
let olderDownloadIDs = [];
for (let i = 0; i < 5; i++) {
olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
}
let totalHistoryVisits = uris.length + olderURIs.length;
// Open the dialog and do our tests.
openWindow(function (aWin) {
let wh = new WindowHelper(aWin);
wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
"duration",
uris.length);
// Accept the dialog, make sure history visits and downloads within one
// hour are cleared.
wh.checkPrefCheckbox("history-downloads-checkbox", true);
wh.acceptDialog();
ensureHistoryClearedState(uris, true);
ensureDownloadsClearedState(downloadIDs, true);
// Make sure visits and downloads > 1 hour still exist.
ensureHistoryClearedState(olderURIs, false);
ensureDownloadsClearedState(olderDownloadIDs, false);
// OK, done, cleanup after ourselves.
blankSlate();
ensureHistoryClearedState(olderURIs, true);
ensureDownloadsClearedState(olderDownloadIDs, true);
});
},
/**
* Ensures that the combined history-downloads checkbox removes neither
* history visits nor downloads when not checked.
*/
function () {
// Add history, downloads, form entries (within the past hour).
let uris = [];
for (let i = 0; i < 5; i++) {
uris.push(addHistoryWithMinutesAgo(i));
}
let downloadIDs = [];
for (let i = 0; i < 5; i++) {
downloadIDs.push(addDownloadWithMinutesAgo(i));
}
let formEntries = [];
for (let i = 0; i < 5; i++) {
formEntries.push(addFormEntryWithMinutesAgo(i));
}
// Open the dialog and do our tests.
openWindow(function (aWin) {
let wh = new WindowHelper(aWin);
wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
"duration",
wh.getRowCount() - 1);
// Remove only form entries, leave history and downloads.
wh.checkPrefCheckbox("history-downloads-checkbox", false);
wh.checkPrefCheckbox("formdata", true);
wh.acceptDialog();
// Of the three only form entries should be cleared.
ensureHistoryClearedState(uris, false);
ensureDownloadsClearedState(downloadIDs, false);
ensureFormEntriesClearedState(formEntries, true);
// OK, done, cleanup after ourselves.
blankSlate();
ensureHistoryClearedState(uris, true);
ensureDownloadsClearedState(downloadIDs, true);
});
},
/**
* Ensures that the "Everything" duration option works.
*/
function () {
// Add history.
let uris = [];
uris.push(addHistoryWithMinutesAgo(10)); // within past hour
uris.push(addHistoryWithMinutesAgo(70)); // within past two hours
uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
// Open the dialog and do our tests.
openWindow(function (aWin) {
let wh = new WindowHelper(aWin);
wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
wh.checkPrefCheckbox("history-downloads-checkbox", true);
wh.acceptDialog();
ensureHistoryClearedState(uris, true);
});
}
];
// Used as the download database ID for a new download. Incremented for each
// new download. See addDownloadWithMinutesAgo().
var gDownloadId = 5555551;
// Index in gAllTests of the test currently being run. Incremented for each
// test run. See doNextTest().
var gCurrTest = 0;
var now_uSec = Date.now() * 1000;
///////////////////////////////////////////////////////////////////////////////
/**
* This wraps the dialog and provides some convenience methods for interacting
* with it.
*
* A warning: Before you call any function that uses the tree (or any function
* that calls a function that uses the tree), you must set a non-everything
* duration by calling selectDuration(). The dialog does not initialize the
* tree if it does not yet need to be shown.
*
* @param aWin
* The dialog's nsIDOMWindow
*/
function WindowHelper(aWin) {
this.win = aWin;
}
WindowHelper.prototype = {
/**
* "Presses" the dialog's OK button.
*/
acceptDialog: function () {
is(this.win.document.documentElement.getButton("accept").disabled, false,
"Dialog's OK button should not be disabled");
this.win.document.documentElement.acceptDialog();
},
/**
* "Presses" the dialog's Cancel button.
*/
cancelDialog: function () {
this.win.document.documentElement.cancelDialog();
},
/**
* Ensures that the grippy row is in the right place, tree selection is OK,
* and that the grippy's visible.
*
* @param aMsg
* Passed to is() when checking grippy location
* @param aExpectedRow
* The row that the grippy should be at
*/
checkGrippy: function (aMsg, aExpectedRow) {
is(this.getGrippyRow(), aExpectedRow, aMsg);
this.checkTreeSelection();
this.ensureGrippyIsVisible();
},
/**
* (Un)checks a history scope checkbox (browser & download history,
* form history, etc.).
*
* @param aPrefName
* Either the ID of the checkbox or the final portion of its
* privacy.cpd.* preference name
* @param aCheckState
* True if the checkbox should be checked, false otherwise
*/
checkPrefCheckbox: function (aPrefName, aCheckState) {
let checkBoxes = this.win.document.getElementsByTagName("listitem");
for (let i = 0; i < checkBoxes.length; i++) {
let cb = checkBoxes[i];
if (cb.id === aPrefName ||
(cb.hasAttribute("preference") &&
cb.getAttribute("preference") === "privacy.cpd." + aPrefName)) {
cb.checked = aCheckState;
break;
}
}
},
/**
* Ensures that the tree selection is appropriate to the grippy row. (A
* single, contiguous selection should exist from the first row all the way
* to the grippy.)
*/
checkTreeSelection: function () {
let grippyRow = this.getGrippyRow();
let sel = this.getTree().view.selection;
if (grippyRow === 0) {
is(sel.getRangeCount(), 0,
"Grippy row is 0, so no tree selection should exist");
}
else {
is(sel.getRangeCount(), 1,
"Grippy row > 0, so only one tree selection range should exist");
let min = {};
let max = {};
sel.getRangeAt(0, min, max);
is(min.value, 0, "Tree selection should start at first row");
is(max.value, grippyRow - 1,
"Tree selection should end at row before grippy");
}
},
/**
* The grippy should always be visible when it's moved directly. This method
* ensures that.
*/
ensureGrippyIsVisible: function () {
let tbo = this.getTree().treeBoxObject;
let firstVis = tbo.getFirstVisibleRow();
let lastVis = tbo.getLastVisibleRow();
let grippyRow = this.getGrippyRow();
ok(firstVis <= grippyRow && grippyRow <= lastVis,
"Grippy row should be visible; this inequality should be true: " +
firstVis + " <= " + grippyRow + " <= " + lastVis);
},
/**
* @return The dialog's duration dropdown
*/
getDurationDropdown: function () {
return this.win.document.getElementById("sanitizeDurationChoice");
},
/**
* @return The grippy row index
*/
getGrippyRow: function () {
return this.win.gContiguousSelectionTreeHelper.getGrippyRow();
},
/**
* @return The tree's row count (includes the grippy row)
*/
getRowCount: function () {
return this.getTree().view.rowCount;
},
/**
* @return The tree
*/
getTree: function () {
return this.win.gContiguousSelectionTreeHelper.tree;
},
/**
* @return True if the "Everything" warning panel is visible (as opposed to
* the tree)
*/
isWarningPanelVisible: function () {
return this.win.document.getElementById("durationDeck").selectedIndex == 1;
},
/**
* @return True if the tree is visible (as opposed to the warning panel)
*/
isTreeVisible: function () {
return this.win.document.getElementById("durationDeck").selectedIndex == 0;
},
/**
* Moves the grippy one row at a time in the direction and magnitude specified.
* If aDelta < 0, moves the grippy up; if aDelta > 0, moves it down.
*
* @param aDelta
* The amount and direction to move
*/
moveGrippyBy: function (aDelta) {
if (aDelta === 0)
return;
let key = aDelta < 0 ? "UP" : "DOWN";
let abs = Math.abs(aDelta);
let treechildren = this.getTree().treeBoxObject.treeBody;
for (let i = 0; i < abs; i++) {
EventUtils.sendKey(key, treechildren);
}
},
/**
* Selects a duration in the duration dropdown.
*
* @param aDurVal
* One of the Sanitizer.TIMESPAN_* values
*/
selectDuration: function (aDurVal) {
this.getDurationDropdown().value = aDurVal;
if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
is(this.isTreeVisible(), false,
"Tree should not be visible for TIMESPAN_EVERYTHING");
is(this.isWarningPanelVisible(), true,
"Warning panel should be visible for TIMESPAN_EVERYTHING");
}
else {
is(this.isTreeVisible(), true,
"Tree should be visible for non-TIMESPAN_EVERYTHING");
is(this.isWarningPanelVisible(), false,
"Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
}
}
};
/**
* Adds a download to history.
*
* @param aMinutesAgo
* The download will be downloaded this many minutes ago
*/
function addDownloadWithMinutesAgo(aMinutesAgo) {
let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
let data = {
id: gDownloadId,
name: name,
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
target: name,
startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
endTime: now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
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();
}
is(downloadExists(gDownloadId), true,
"Sanity check: download " + gDownloadId +
" should exist after creating it");
return gDownloadId++;
}
/**
* Adds a form entry to history.
*
* @param aMinutesAgo
* The entry will be added this many minutes ago
*/
function addFormEntryWithMinutesAgo(aMinutesAgo) {
let name = aMinutesAgo + "-minutes-ago";
formhist.addEntry(name, "dummy");
// Artifically age the entry to the proper vintage.
let db = formhist.DBConnection;
let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
timestamp + " WHERE fieldname = '" + name + "'");
is(formhist.nameExists(name), true,
"Sanity check: form entry " + name + " should exist after creating it");
return name;
}
/**
* Adds a history visit to history.
*
* @param aMinutesAgo
* The visit will be visited this many minutes ago
*/
function addHistoryWithMinutesAgo(aMinutesAgo) {
let pURI = uri("http://" + aMinutesAgo + "-minutes-ago.com/");
bhist.addPageWithDetails(pURI,
aMinutesAgo + " minutes ago",
now_uSec - (aMinutesAgo * 60 * 1000 * 1000));
is(bhist.isVisited(pURI), true,
"Sanity check: history visit " + pURI.spec +
" should exist after creating it");
return pURI;
}
/**
* Removes all history visits, downloads, and form entries.
*/
function blankSlate() {
bhist.removeAllPages();
dm.cleanUp();
formhist.removeAllEntries();
}
/**
* Checks to see if the download with the specified ID exists.
*
* @param aID
* The ID of the download to check
* @return True if the download exists, false otherwise
*/
function downloadExists(aID)
{
let db = dm.DBConnection;
let stmt = db.createStatement(
"SELECT * " +
"FROM moz_downloads " +
"WHERE id = :id"
);
stmt.params.id = aID;
let rows = stmt.step();
stmt.finalize();
return !!rows;
}
/**
* Runs the next test in the gAllTests array. If all tests have been run,
* finishes the entire suite.
*/
function doNextTest() {
if (gAllTests.length <= gCurrTest) {
blankSlate();
finish();
}
else {
let ct = gCurrTest;
gCurrTest++;
gAllTests[ct]();
}
}
/**
* Ensures that the specified downloads are either cleared or not.
*
* @param aDownloadIDs
* Array of download database IDs
* @param aShouldBeCleared
* True if each download should be cleared, false otherwise
*/
function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aDownloadIDs.forEach(function (id) {
is(downloadExists(id), !aShouldBeCleared,
"download " + id + " should " + niceStr + " exist");
});
}
/**
* Ensures that the specified form entries are either cleared or not.
*
* @param aFormEntries
* Array of form entry names
* @param aShouldBeCleared
* True if each form entry should be cleared, false otherwise
*/
function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aFormEntries.forEach(function (entry) {
is(formhist.nameExists(entry), !aShouldBeCleared,
"form entry " + entry + " should " + niceStr + " exist");
});
}
/**
* Ensures that the specified URIs are either cleared or not.
*
* @param aURIs
* Array of page URIs
* @param aShouldBeCleared
* True if each visit to the URI should be cleared, false otherwise
*/
function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
let niceStr = aShouldBeCleared ? "no longer" : "still";
aURIs.forEach(function (aURI) {
is(bhist.isVisited(aURI), !aShouldBeCleared,
"history visit " + aURI.spec + " should " + niceStr + " exist");
});
}
/**
* Opens the sanitize dialog and runs a callback once it's finished loading.
*
* @param aOnloadCallback
* A function that will be called once the dialog has loaded
*/
function openWindow(aOnloadCallback) {
let windowObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic === "domwindowopened") {
winWatch.unregisterNotification(this);
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function onload(event) {
win.removeEventListener("load", onload, false);
executeSoon(function () {
// Some exceptions that reach here don't reach the test harness, but
// ok()/is() do...
try {
aOnloadCallback(win);
doNextTest();
}
catch (exc) {
win.close();
ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
finish();
}
});
}, false);
}
}
};
winWatch.registerNotification(windowObserver);
winWatch.openWindow(null,
"chrome://browser/content/sanitize.xul",
"Sanitize",
"chrome,titlebar,dialog,centerscreen,modal",
null);
}
/**
* @return A new nsIURI from aSpec.
*/
function uri(aSpec) {
return iosvc.newURI(aSpec, null, null);
}
///////////////////////////////////////////////////////////////////////////////
function test() {
blankSlate();
waitForExplicitFinish();
// Kick off all the tests in the gAllTests array.
doNextTest();
}

View File

@ -40,6 +40,8 @@ browser.jar:
* content/browser/safeMode.xul (content/safeMode.xul)
* content/browser/sanitize.js (content/sanitize.js)
* content/browser/sanitize.xul (content/sanitize.xul)
* content/browser/sanitizeDialog.js (content/sanitizeDialog.js)
content/browser/sanitizeDialog.css (content/sanitizeDialog.css)
* content/browser/tabbrowser.css (content/tabbrowser.css)
* content/browser/tabbrowser.xml (content/tabbrowser.xml)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)

View File

@ -1,6 +1,7 @@
classic.jar:
% skin browser classic/1.0 %skin/classic/browser/
% override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css)
* skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css)
* skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css)
skin/classic/browser/aboutCertError.css (aboutCertError.css)

View File

@ -0,0 +1,88 @@
/* Hide the duration dropdown suffix label if it's empty. Otherwise it
takes up a little space, causing the end of the dropdown to not be aligned
with the warning box. */
#sanitizeDurationSuffixLabel[value=""] {
display: none;
}
/* Places tree */
#placesTreechildren::-moz-tree-row(selected),
#placesTreechildren::-moz-tree-row(grippyRow) {
background: #999;
}
#placesTreechildren::-moz-tree-cell-text(selected) {
color: #111;
}
/* Sanitize everything warning box */
#sanitizeEverythingWarningBox {
background-color: Window;
border: 1px solid ThreeDDarkShadow;
-moz-border-radius: 5px;
padding: 16px;
}
#sanitizeEverythingWarningIcon {
list-style-image: url("chrome://global/skin/icons/warning-large.png");
padding: 0;
margin: 0;
}
#sanitizeEverythingWarningDescBox {
padding: 0;
margin: 0;
padding-left: 16px;
}
/* Progressive disclosure button */
#detailsExpanderWrapper {
padding: 0;
margin-top: 6px;
margin-bottom: 6px;
margin-left: -6px;
margin-right: 0;
}
.expander-up,
.expander-down {
min-width: 0;
padding: 2px 0;
-moz-padding-start: 2px;
}
.expander-up {
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}
.expander-down {
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}
.expander-down:hover:active {
list-style-image: url("chrome://global/skin/arrow/arrow-dn-hov.gif");
}
.expander-up:hover:active {
list-style-image: url("chrome://global/skin/arrow/arrow-up-hov.gif");
}
/* Make the item list the same width as the warning box */
#itemList {
margin-left: 0;
margin-right: 0;
}
/* Make the rightmost dialog button ("accept") align wih right side of the
warning box */
.prefWindow-dlgbuttons {
margin-right: 0;
}
.dialog-button[dlgtype="accept"] {
margin-right: 0;
}

View File

@ -1,5 +1,6 @@
classic.jar:
% skin browser classic/1.0 %skin/classic/browser/
skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css)
* skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css)
* skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css)
skin/classic/browser/aboutCertError.css (aboutCertError.css)

View File

@ -0,0 +1,89 @@
/* Hide the duration dropdown suffix label if it's empty. Otherwise it
takes up a little space, causing the end of the dropdown to not be aligned
with the warning box. */
#sanitizeDurationSuffixLabel[value=""] {
display: none;
}
/* Places tree */
#placesTreechildren::-moz-tree-row(selected),
#placesTreechildren::-moz-tree-row(grippyRow) {
background: #999;
}
#placesTreechildren::-moz-tree-cell-text(selected) {
color: #111;
}
/* Sanitize everything warning box */
#sanitizeEverythingWarningBox {
background-color: Window;
border: 1px solid ThreeDDarkShadow;
-moz-border-radius: 5px;
padding: 16px;
}
#sanitizeEverythingWarningIcon {
list-style-image: url("chrome://global/skin/icons/warning-large.png");
padding: 0;
margin: 0;
}
#sanitizeEverythingWarningDescBox {
padding: 0;
margin: 0;
padding-left: 16px;
}
/* Progressive disclosure button */
#detailsExpanderWrapper {
padding: 0;
margin-top: 6px;
margin-bottom: 6px;
margin-left: -2px;
margin-right: 0;
}
.expander-up,
.expander-down {
-moz-appearance: none;
min-width: 0;
padding: 0;
margin: 0;
}
.expander-up {
list-style-image: url("chrome://browser/skin/places/expander-open.png");
}
.expander-up:hover:active {
list-style-image: url("chrome://browser/skin/places/expander-open-active.png");
}
.expander-down {
list-style-image: url("chrome://browser/skin/places/expander-closed.png");
}
.expander-down:hover:active {
list-style-image: url("chrome://browser/skin/places/expander-closed-active.png");
}
/* Make the item list the same width as the warning box */
#itemList {
margin-left: 0;
margin-right: 0;
}
/* Make the rightmost dialog button ("accept") align wih right side of the
warning box */
.prefWindow-dlgbuttons {
margin-right: 0;
}
.dialog-button[dlgtype="accept"] {
margin-right: 0;
}

View File

@ -3,6 +3,7 @@ classic.jar:
% skin browser classic/1.0 %skin/classic/browser/ os!=WINNT
# NOTE: If you add a new file here, you'll need to add it to the aero
# section at the bottom of this file
skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css)
* skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css)
* skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css)
skin/classic/browser/aboutCertError.css (aboutCertError.css)
@ -95,6 +96,7 @@ classic.jar:
#ifdef XP_WIN
classic.jar:
% skin browser classic/1.0 %skin/classic/aero/browser/ os=WINNT osversion>=6
skin/classic/aero/browser/sanitizeDialog.css (sanitizeDialog.css)
* skin/classic/aero/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css)
* skin/classic/aero/browser/aboutSessionRestore.css (aboutSessionRestore.css)
skin/classic/aero/browser/aboutCertError.css (aboutCertError.css)

View File

@ -0,0 +1,84 @@
/* Hide the duration dropdown suffix label if it's empty. Otherwise it
takes up a little space, causing the end of the dropdown to not be aligned
with the warning box. */
#sanitizeDurationSuffixLabel[value=""] {
display: none;
}
/* Places tree */
#placesTreechildren::-moz-tree-row(selected),
#placesTreechildren::-moz-tree-row(grippyRow) {
background: #999;
}
#placesTreechildren::-moz-tree-cell-text(selected) {
color: #111;
}
/* Sanitize everything warning box */
#sanitizeEverythingWarningBox {
background-color: Window;
border: 1px solid ThreeDDarkShadow;
-moz-border-radius: 5px;
padding: 16px;
}
#sanitizeEverythingWarningIcon {
list-style-image: url("chrome://global/skin/icons/warning-large.png");
padding: 0;
margin: 0;
}
#sanitizeEverythingWarningDescBox {
padding: 0;
margin: 0;
padding-left: 16px;
}
/* Progressive disclosure button */
#detailsExpanderWrapper {
padding: 0;
margin-top: 6px;
margin-bottom: 6px;
margin-left: -1px;
margin-right: 0;
}
.expander-up,
.expander-down {
min-width: 0;
margin: 0;
}
.expander-up > hbox,
.expander-down > hbox {
padding: 0;
}
.expander-up {
list-style-image: url("chrome://global/skin/icons/collapse.png");
}
.expander-down {
list-style-image: url("chrome://global/skin/icons/expand.png");
}
/* Make the item list the same width as the warning box */
#itemList {
margin-left: 0;
margin-right: 0;
}
/* Make the rightmost dialog button ("cancel") align wih right side of the
warning box */
.prefWindow-dlgbuttons {
margin-right: 0;
}
.dialog-button[dlgtype="cancel"] {
margin-right: 0;
}