browser/base/content/sanitizeDialog.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 const Cc = Components.classes;
michael@0 7 const Ci = Components.interfaces;
michael@0 8
michael@0 9 var gSanitizePromptDialog = {
michael@0 10
michael@0 11 get bundleBrowser()
michael@0 12 {
michael@0 13 if (!this._bundleBrowser)
michael@0 14 this._bundleBrowser = document.getElementById("bundleBrowser");
michael@0 15 return this._bundleBrowser;
michael@0 16 },
michael@0 17
michael@0 18 get selectedTimespan()
michael@0 19 {
michael@0 20 var durList = document.getElementById("sanitizeDurationChoice");
michael@0 21 return parseInt(durList.value);
michael@0 22 },
michael@0 23
michael@0 24 get sanitizePreferences()
michael@0 25 {
michael@0 26 if (!this._sanitizePreferences) {
michael@0 27 this._sanitizePreferences =
michael@0 28 document.getElementById("sanitizePreferences");
michael@0 29 }
michael@0 30 return this._sanitizePreferences;
michael@0 31 },
michael@0 32
michael@0 33 get warningBox()
michael@0 34 {
michael@0 35 return document.getElementById("sanitizeEverythingWarningBox");
michael@0 36 },
michael@0 37
michael@0 38 init: function ()
michael@0 39 {
michael@0 40 // This is used by selectByTimespan() to determine if the window has loaded.
michael@0 41 this._inited = true;
michael@0 42
michael@0 43 var s = new Sanitizer();
michael@0 44 s.prefDomain = "privacy.cpd.";
michael@0 45
michael@0 46 let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
michael@0 47 for (let i = 0; i < sanitizeItemList.length; i++) {
michael@0 48 let prefItem = sanitizeItemList[i];
michael@0 49 let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
michael@0 50 s.canClearItem(name, function canClearCallback(aItem, aCanClear, aPrefItem) {
michael@0 51 if (!aCanClear) {
michael@0 52 aPrefItem.preference = null;
michael@0 53 aPrefItem.checked = false;
michael@0 54 aPrefItem.disabled = true;
michael@0 55 }
michael@0 56 }, prefItem);
michael@0 57 }
michael@0 58
michael@0 59 document.documentElement.getButton("accept").label =
michael@0 60 this.bundleBrowser.getString("sanitizeButtonOK");
michael@0 61
michael@0 62 if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
michael@0 63 this.prepareWarning();
michael@0 64 this.warningBox.hidden = false;
michael@0 65 document.title =
michael@0 66 this.bundleBrowser.getString("sanitizeDialog2.everything.title");
michael@0 67 }
michael@0 68 else
michael@0 69 this.warningBox.hidden = true;
michael@0 70 },
michael@0 71
michael@0 72 selectByTimespan: function ()
michael@0 73 {
michael@0 74 // This method is the onselect handler for the duration dropdown. As a
michael@0 75 // result it's called a couple of times before onload calls init().
michael@0 76 if (!this._inited)
michael@0 77 return;
michael@0 78
michael@0 79 var warningBox = this.warningBox;
michael@0 80
michael@0 81 // If clearing everything
michael@0 82 if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
michael@0 83 this.prepareWarning();
michael@0 84 if (warningBox.hidden) {
michael@0 85 warningBox.hidden = false;
michael@0 86 window.resizeBy(0, warningBox.boxObject.height);
michael@0 87 }
michael@0 88 window.document.title =
michael@0 89 this.bundleBrowser.getString("sanitizeDialog2.everything.title");
michael@0 90 return;
michael@0 91 }
michael@0 92
michael@0 93 // If clearing a specific time range
michael@0 94 if (!warningBox.hidden) {
michael@0 95 window.resizeBy(0, -warningBox.boxObject.height);
michael@0 96 warningBox.hidden = true;
michael@0 97 }
michael@0 98 window.document.title =
michael@0 99 window.document.documentElement.getAttribute("noneverythingtitle");
michael@0 100 },
michael@0 101
michael@0 102 sanitize: function ()
michael@0 103 {
michael@0 104 // Update pref values before handing off to the sanitizer (bug 453440)
michael@0 105 this.updatePrefs();
michael@0 106 var s = new Sanitizer();
michael@0 107 s.prefDomain = "privacy.cpd.";
michael@0 108
michael@0 109 s.range = Sanitizer.getClearRange(this.selectedTimespan);
michael@0 110 s.ignoreTimespan = !s.range;
michael@0 111
michael@0 112 // As the sanitize is async, we disable the buttons, update the label on
michael@0 113 // the 'accept' button to indicate things are happening and return false -
michael@0 114 // once the async operation completes (either with or without errors)
michael@0 115 // we close the window.
michael@0 116 let docElt = document.documentElement;
michael@0 117 let acceptButton = docElt.getButton("accept");
michael@0 118 acceptButton.disabled = true;
michael@0 119 acceptButton.setAttribute("label",
michael@0 120 this.bundleBrowser.getString("sanitizeButtonClearing"));
michael@0 121 docElt.getButton("cancel").disabled = true;
michael@0 122 try {
michael@0 123 s.sanitize().then(null, Components.utils.reportError)
michael@0 124 .then(() => window.close())
michael@0 125 .then(null, Components.utils.reportError);
michael@0 126 } catch (er) {
michael@0 127 Components.utils.reportError("Exception during sanitize: " + er);
michael@0 128 return true; // We *do* want to close immediately on error.
michael@0 129 }
michael@0 130 return false;
michael@0 131 },
michael@0 132
michael@0 133 /**
michael@0 134 * If the panel that displays a warning when the duration is "Everything" is
michael@0 135 * not set up, sets it up. Otherwise does nothing.
michael@0 136 *
michael@0 137 * @param aDontShowItemList Whether only the warning message should be updated.
michael@0 138 * True means the item list visibility status should not
michael@0 139 * be changed.
michael@0 140 */
michael@0 141 prepareWarning: function (aDontShowItemList) {
michael@0 142 // If the date and time-aware locale warning string is ever used again,
michael@0 143 // initialize it here. Currently we use the no-visits warning string,
michael@0 144 // which does not include date and time. See bug 480169 comment 48.
michael@0 145
michael@0 146 var warningStringID;
michael@0 147 if (this.hasNonSelectedItems()) {
michael@0 148 warningStringID = "sanitizeSelectedWarning";
michael@0 149 if (!aDontShowItemList)
michael@0 150 this.showItemList();
michael@0 151 }
michael@0 152 else {
michael@0 153 warningStringID = "sanitizeEverythingWarning2";
michael@0 154 }
michael@0 155
michael@0 156 var warningDesc = document.getElementById("sanitizeEverythingWarning");
michael@0 157 warningDesc.textContent =
michael@0 158 this.bundleBrowser.getString(warningStringID);
michael@0 159 },
michael@0 160
michael@0 161 /**
michael@0 162 * Called when the value of a preference element is synced from the actual
michael@0 163 * pref. Enables or disables the OK button appropriately.
michael@0 164 */
michael@0 165 onReadGeneric: function ()
michael@0 166 {
michael@0 167 var found = false;
michael@0 168
michael@0 169 // Find any other pref that's checked and enabled.
michael@0 170 var i = 0;
michael@0 171 while (!found && i < this.sanitizePreferences.childNodes.length) {
michael@0 172 var preference = this.sanitizePreferences.childNodes[i];
michael@0 173
michael@0 174 found = !!preference.value &&
michael@0 175 !preference.disabled;
michael@0 176 i++;
michael@0 177 }
michael@0 178
michael@0 179 try {
michael@0 180 document.documentElement.getButton("accept").disabled = !found;
michael@0 181 }
michael@0 182 catch (e) { }
michael@0 183
michael@0 184 // Update the warning prompt if needed
michael@0 185 this.prepareWarning(true);
michael@0 186
michael@0 187 return undefined;
michael@0 188 },
michael@0 189
michael@0 190 /**
michael@0 191 * Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
michael@0 192 * Because the type of this prefwindow is "child" -- and that's needed because
michael@0 193 * without it the dialog has no OK and Cancel buttons -- the prefs are not
michael@0 194 * updated on dialogaccept on platforms that don't support instant-apply
michael@0 195 * (i.e., Windows). We must therefore manually set the prefs from their
michael@0 196 * corresponding preference elements.
michael@0 197 */
michael@0 198 updatePrefs : function ()
michael@0 199 {
michael@0 200 var tsPref = document.getElementById("privacy.sanitize.timeSpan");
michael@0 201 Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
michael@0 202
michael@0 203 // Keep the pref for the download history in sync with the history pref.
michael@0 204 document.getElementById("privacy.cpd.downloads").value =
michael@0 205 document.getElementById("privacy.cpd.history").value;
michael@0 206
michael@0 207 // Now manually set the prefs from their corresponding preference
michael@0 208 // elements.
michael@0 209 var prefs = this.sanitizePreferences.rootBranch;
michael@0 210 for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
michael@0 211 var p = this.sanitizePreferences.childNodes[i];
michael@0 212 prefs.setBoolPref(p.name, p.value);
michael@0 213 }
michael@0 214 },
michael@0 215
michael@0 216 /**
michael@0 217 * Check if all of the history items have been selected like the default status.
michael@0 218 */
michael@0 219 hasNonSelectedItems: function () {
michael@0 220 let checkboxes = document.querySelectorAll("#itemList > [preference]");
michael@0 221 for (let i = 0; i < checkboxes.length; ++i) {
michael@0 222 let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
michael@0 223 if (!pref.value)
michael@0 224 return true;
michael@0 225 }
michael@0 226 return false;
michael@0 227 },
michael@0 228
michael@0 229 /**
michael@0 230 * Show the history items list.
michael@0 231 */
michael@0 232 showItemList: function () {
michael@0 233 var itemList = document.getElementById("itemList");
michael@0 234 var expanderButton = document.getElementById("detailsExpander");
michael@0 235
michael@0 236 if (itemList.collapsed) {
michael@0 237 expanderButton.className = "expander-up";
michael@0 238 itemList.setAttribute("collapsed", "false");
michael@0 239 if (document.documentElement.boxObject.height)
michael@0 240 window.resizeBy(0, itemList.boxObject.height);
michael@0 241 }
michael@0 242 },
michael@0 243
michael@0 244 /**
michael@0 245 * Hide the history items list.
michael@0 246 */
michael@0 247 hideItemList: function () {
michael@0 248 var itemList = document.getElementById("itemList");
michael@0 249 var expanderButton = document.getElementById("detailsExpander");
michael@0 250
michael@0 251 if (!itemList.collapsed) {
michael@0 252 expanderButton.className = "expander-down";
michael@0 253 window.resizeBy(0, -itemList.boxObject.height);
michael@0 254 itemList.setAttribute("collapsed", "true");
michael@0 255 }
michael@0 256 },
michael@0 257
michael@0 258 /**
michael@0 259 * Called by the item list expander button to toggle the list's visibility.
michael@0 260 */
michael@0 261 toggleItemList: function ()
michael@0 262 {
michael@0 263 var itemList = document.getElementById("itemList");
michael@0 264
michael@0 265 if (itemList.collapsed)
michael@0 266 this.showItemList();
michael@0 267 else
michael@0 268 this.hideItemList();
michael@0 269 }
michael@0 270
michael@0 271 #ifdef CRH_DIALOG_TREE_VIEW
michael@0 272 // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
michael@0 273 // Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
michael@0 274 // of the sanitizeDurationCustom menuitem.
michael@0 275 get TIMESPAN_CUSTOM()
michael@0 276 {
michael@0 277 return -1;
michael@0 278 },
michael@0 279
michael@0 280 get placesTree()
michael@0 281 {
michael@0 282 if (!this._placesTree)
michael@0 283 this._placesTree = document.getElementById("placesTree");
michael@0 284 return this._placesTree;
michael@0 285 },
michael@0 286
michael@0 287 init: function ()
michael@0 288 {
michael@0 289 // This is used by selectByTimespan() to determine if the window has loaded.
michael@0 290 this._inited = true;
michael@0 291
michael@0 292 var s = new Sanitizer();
michael@0 293 s.prefDomain = "privacy.cpd.";
michael@0 294
michael@0 295 let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
michael@0 296 for (let i = 0; i < sanitizeItemList.length; i++) {
michael@0 297 let prefItem = sanitizeItemList[i];
michael@0 298 let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
michael@0 299 s.canClearItem(name, function canClearCallback(aCanClear) {
michael@0 300 if (!aCanClear) {
michael@0 301 prefItem.preference = null;
michael@0 302 prefItem.checked = false;
michael@0 303 prefItem.disabled = true;
michael@0 304 }
michael@0 305 });
michael@0 306 }
michael@0 307
michael@0 308 document.documentElement.getButton("accept").label =
michael@0 309 this.bundleBrowser.getString("sanitizeButtonOK");
michael@0 310
michael@0 311 this.selectByTimespan();
michael@0 312 },
michael@0 313
michael@0 314 /**
michael@0 315 * Sets up the hashes this.durationValsToRows, which maps duration values
michael@0 316 * to rows in the tree, this.durationRowsToVals, which maps rows in
michael@0 317 * the tree to duration values, and this.durationStartTimes, which maps
michael@0 318 * duration values to their corresponding start times.
michael@0 319 */
michael@0 320 initDurationDropdown: function ()
michael@0 321 {
michael@0 322 // First, calculate the start times for each duration.
michael@0 323 this.durationStartTimes = {};
michael@0 324 var durVals = [];
michael@0 325 var durPopup = document.getElementById("sanitizeDurationPopup");
michael@0 326 var durMenuitems = durPopup.childNodes;
michael@0 327 for (let i = 0; i < durMenuitems.length; i++) {
michael@0 328 let durMenuitem = durMenuitems[i];
michael@0 329 let durVal = parseInt(durMenuitem.value);
michael@0 330 if (durMenuitem.localName === "menuitem" &&
michael@0 331 durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
michael@0 332 durVal !== this.TIMESPAN_CUSTOM) {
michael@0 333 durVals.push(durVal);
michael@0 334 let durTimes = Sanitizer.getClearRange(durVal);
michael@0 335 this.durationStartTimes[durVal] = durTimes[0];
michael@0 336 }
michael@0 337 }
michael@0 338
michael@0 339 // Sort the duration values ascending. Because one tree index can map to
michael@0 340 // more than one duration, this ensures that this.durationRowsToVals maps
michael@0 341 // a row index to the largest duration possible in the code below.
michael@0 342 durVals.sort();
michael@0 343
michael@0 344 // Now calculate the rows in the tree of the durations' start times. For
michael@0 345 // each duration, we are looking for the node in the tree whose time is the
michael@0 346 // smallest time greater than or equal to the duration's start time.
michael@0 347 this.durationRowsToVals = {};
michael@0 348 this.durationValsToRows = {};
michael@0 349 var view = this.placesTree.view;
michael@0 350 // For all rows in the tree except the grippy row...
michael@0 351 for (let i = 0; i < view.rowCount - 1; i++) {
michael@0 352 let unfoundDurVals = [];
michael@0 353 let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
michael@0 354 nodeForTreeIndex(i).time;
michael@0 355 // For all durations whose rows have not yet been found in the tree, see
michael@0 356 // if index i is their index. An index may map to more than one duration,
michael@0 357 // in which case the final duration (the largest) wins.
michael@0 358 for (let j = 0; j < durVals.length; j++) {
michael@0 359 let durVal = durVals[j];
michael@0 360 let durStartTime = this.durationStartTimes[durVal];
michael@0 361 if (nodeTime < durStartTime) {
michael@0 362 this.durationValsToRows[durVal] = i - 1;
michael@0 363 this.durationRowsToVals[i - 1] = durVal;
michael@0 364 }
michael@0 365 else
michael@0 366 unfoundDurVals.push(durVal);
michael@0 367 }
michael@0 368 durVals = unfoundDurVals;
michael@0 369 }
michael@0 370
michael@0 371 // If any durations were not found above, then every node in the tree has a
michael@0 372 // time greater than or equal to the duration. In other words, those
michael@0 373 // durations include the entire tree (except the grippy row).
michael@0 374 for (let i = 0; i < durVals.length; i++) {
michael@0 375 let durVal = durVals[i];
michael@0 376 this.durationValsToRows[durVal] = view.rowCount - 2;
michael@0 377 this.durationRowsToVals[view.rowCount - 2] = durVal;
michael@0 378 }
michael@0 379 },
michael@0 380
michael@0 381 /**
michael@0 382 * If the Places tree is not set up, sets it up. Otherwise does nothing.
michael@0 383 */
michael@0 384 ensurePlacesTreeIsInited: function ()
michael@0 385 {
michael@0 386 if (this._placesTreeIsInited)
michael@0 387 return;
michael@0 388
michael@0 389 this._placesTreeIsInited = true;
michael@0 390
michael@0 391 // Either "Last Four Hours" or "Today" will have the most history. If
michael@0 392 // it's been more than 4 hours since today began, "Today" will. Otherwise
michael@0 393 // "Last Four Hours" will.
michael@0 394 var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
michael@0 395
michael@0 396 // If it's been less than 4 hours since today began, use the past 4 hours.
michael@0 397 if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
michael@0 398 times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
michael@0 399 }
michael@0 400
michael@0 401 var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
michael@0 402 getService(Ci.nsINavHistoryService);
michael@0 403 var query = histServ.getNewQuery();
michael@0 404 query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
michael@0 405 query.beginTime = times[0];
michael@0 406 query.endTimeReference = query.TIME_RELATIVE_EPOCH;
michael@0 407 query.endTime = times[1];
michael@0 408 var opts = histServ.getNewQueryOptions();
michael@0 409 opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
michael@0 410 opts.queryType = opts.QUERY_TYPE_HISTORY;
michael@0 411 var result = histServ.executeQuery(query, opts);
michael@0 412
michael@0 413 var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
michael@0 414 new PlacesTreeView());
michael@0 415 result.addObserver(view, false);
michael@0 416 this.initDurationDropdown();
michael@0 417 },
michael@0 418
michael@0 419 /**
michael@0 420 * Called on select of the duration dropdown and when grippyMoved() sets a
michael@0 421 * duration based on the location of the grippy row. Selects all the nodes in
michael@0 422 * the tree that are contained in the selected duration. If clearing
michael@0 423 * everything, the warning panel is shown instead.
michael@0 424 */
michael@0 425 selectByTimespan: function ()
michael@0 426 {
michael@0 427 // This method is the onselect handler for the duration dropdown. As a
michael@0 428 // result it's called a couple of times before onload calls init().
michael@0 429 if (!this._inited)
michael@0 430 return;
michael@0 431
michael@0 432 var durDeck = document.getElementById("durationDeck");
michael@0 433 var durList = document.getElementById("sanitizeDurationChoice");
michael@0 434 var durVal = parseInt(durList.value);
michael@0 435 var durCustom = document.getElementById("sanitizeDurationCustom");
michael@0 436
michael@0 437 // If grippy row is not at a duration boundary, show the custom menuitem;
michael@0 438 // otherwise, hide it. Since the user cannot specify a custom duration by
michael@0 439 // using the dropdown, this conditional is true only when this method is
michael@0 440 // called onselect from grippyMoved(), so no selection need be made.
michael@0 441 if (durVal === this.TIMESPAN_CUSTOM) {
michael@0 442 durCustom.hidden = false;
michael@0 443 return;
michael@0 444 }
michael@0 445 durCustom.hidden = true;
michael@0 446
michael@0 447 // If clearing everything, show the warning and change the dialog's title.
michael@0 448 if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
michael@0 449 this.prepareWarning();
michael@0 450 durDeck.selectedIndex = 1;
michael@0 451 window.document.title =
michael@0 452 this.bundleBrowser.getString("sanitizeDialog2.everything.title");
michael@0 453 document.documentElement.getButton("accept").disabled = false;
michael@0 454 return;
michael@0 455 }
michael@0 456
michael@0 457 // Otherwise -- if clearing a specific time range -- select that time range
michael@0 458 // in the tree.
michael@0 459 this.ensurePlacesTreeIsInited();
michael@0 460 durDeck.selectedIndex = 0;
michael@0 461 window.document.title =
michael@0 462 window.document.documentElement.getAttribute("noneverythingtitle");
michael@0 463 var durRow = this.durationValsToRows[durVal];
michael@0 464 gContiguousSelectionTreeHelper.rangedSelect(durRow);
michael@0 465 gContiguousSelectionTreeHelper.scrollToGrippy();
michael@0 466
michael@0 467 // If duration is empty (there are no selected rows), disable the dialog's
michael@0 468 // OK button.
michael@0 469 document.documentElement.getButton("accept").disabled = durRow < 0;
michael@0 470 },
michael@0 471
michael@0 472 sanitize: function ()
michael@0 473 {
michael@0 474 // Update pref values before handing off to the sanitizer (bug 453440)
michael@0 475 this.updatePrefs();
michael@0 476 var s = new Sanitizer();
michael@0 477 s.prefDomain = "privacy.cpd.";
michael@0 478
michael@0 479 var durList = document.getElementById("sanitizeDurationChoice");
michael@0 480 var durValue = parseInt(durList.value);
michael@0 481 s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
michael@0 482
michael@0 483 // Set the sanitizer's time range if we're not clearing everything.
michael@0 484 if (!s.ignoreTimespan) {
michael@0 485 // If user selected a custom timespan, use that.
michael@0 486 if (durValue === this.TIMESPAN_CUSTOM) {
michael@0 487 var view = this.placesTree.view;
michael@0 488 var now = Date.now() * 1000;
michael@0 489 // We disable the dialog's OK button if there's no selection, but we'll
michael@0 490 // handle that case just in... case.
michael@0 491 if (view.selection.getRangeCount() === 0)
michael@0 492 s.range = [now, now];
michael@0 493 else {
michael@0 494 var startIndexRef = {};
michael@0 495 // Tree sorted by visit date DEscending, so start time time comes last.
michael@0 496 view.selection.getRangeAt(0, {}, startIndexRef);
michael@0 497 view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
michael@0 498 var startNode = view.nodeForTreeIndex(startIndexRef.value);
michael@0 499 s.range = [startNode.time, now];
michael@0 500 }
michael@0 501 }
michael@0 502 // Otherwise use the predetermined range.
michael@0 503 else
michael@0 504 s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
michael@0 505 }
michael@0 506
michael@0 507 try {
michael@0 508 s.sanitize();
michael@0 509 } catch (er) {
michael@0 510 Components.utils.reportError("Exception during sanitize: " + er);
michael@0 511 }
michael@0 512 return true;
michael@0 513 },
michael@0 514
michael@0 515 /**
michael@0 516 * In order to mark the custom Places tree view and its nsINavHistoryResult
michael@0 517 * for garbage collection, we need to break the reference cycle between the
michael@0 518 * two.
michael@0 519 */
michael@0 520 unload: function ()
michael@0 521 {
michael@0 522 let result = this.placesTree.getResult();
michael@0 523 result.removeObserver(this.placesTree.view);
michael@0 524 this.placesTree.view = null;
michael@0 525 },
michael@0 526
michael@0 527 /**
michael@0 528 * Called when the user moves the grippy by dragging it, clicking in the tree,
michael@0 529 * or on keypress. Updates the duration dropdown so that it displays the
michael@0 530 * appropriate specific or custom duration.
michael@0 531 *
michael@0 532 * @param aEventName
michael@0 533 * The name of the event whose handler called this method, e.g.,
michael@0 534 * "ondragstart", "onkeypress", etc.
michael@0 535 * @param aEvent
michael@0 536 * The event captured in the event handler.
michael@0 537 */
michael@0 538 grippyMoved: function (aEventName, aEvent)
michael@0 539 {
michael@0 540 gContiguousSelectionTreeHelper[aEventName](aEvent);
michael@0 541 var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
michael@0 542 var durList = document.getElementById("sanitizeDurationChoice");
michael@0 543 var durValue = parseInt(durList.value);
michael@0 544
michael@0 545 // Multiple durations can map to the same row. Don't update the dropdown
michael@0 546 // if the current duration is valid for lastSelRow.
michael@0 547 if ((durValue !== this.TIMESPAN_CUSTOM ||
michael@0 548 lastSelRow in this.durationRowsToVals) &&
michael@0 549 (durValue === this.TIMESPAN_CUSTOM ||
michael@0 550 this.durationValsToRows[durValue] !== lastSelRow)) {
michael@0 551 // Setting durList.value causes its onselect handler to fire, which calls
michael@0 552 // selectByTimespan().
michael@0 553 if (lastSelRow in this.durationRowsToVals)
michael@0 554 durList.value = this.durationRowsToVals[lastSelRow];
michael@0 555 else
michael@0 556 durList.value = this.TIMESPAN_CUSTOM;
michael@0 557 }
michael@0 558
michael@0 559 // If there are no selected rows, disable the dialog's OK button.
michael@0 560 document.documentElement.getButton("accept").disabled = lastSelRow < 0;
michael@0 561 }
michael@0 562 #endif
michael@0 563
michael@0 564 };
michael@0 565
michael@0 566
michael@0 567 #ifdef CRH_DIALOG_TREE_VIEW
michael@0 568 /**
michael@0 569 * A helper for handling contiguous selection in the tree.
michael@0 570 */
michael@0 571 var gContiguousSelectionTreeHelper = {
michael@0 572
michael@0 573 /**
michael@0 574 * Gets the tree associated with this helper.
michael@0 575 */
michael@0 576 get tree()
michael@0 577 {
michael@0 578 return this._tree;
michael@0 579 },
michael@0 580
michael@0 581 /**
michael@0 582 * Sets the tree that this module handles. The tree is assigned a new view
michael@0 583 * that is equipped to handle contiguous selection. You can pass in an
michael@0 584 * object that will be used as the prototype of the new view. Otherwise
michael@0 585 * the tree's current view is used as the prototype.
michael@0 586 *
michael@0 587 * @param aTreeElement
michael@0 588 * The tree element
michael@0 589 * @param aProtoTreeView
michael@0 590 * If defined, this will be used as the prototype of the tree's new
michael@0 591 * view
michael@0 592 * @return The new view
michael@0 593 */
michael@0 594 setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
michael@0 595 {
michael@0 596 this._tree = aTreeElement;
michael@0 597 var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
michael@0 598 aTreeElement.view = newView;
michael@0 599 return newView;
michael@0 600 },
michael@0 601
michael@0 602 /**
michael@0 603 * The index of the row that the grippy occupies. Note that the index of the
michael@0 604 * last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
michael@0 605 * no selection exists.
michael@0 606 *
michael@0 607 * @return The row index of the grippy
michael@0 608 */
michael@0 609 getGrippyRow: function CSTH_getGrippyRow()
michael@0 610 {
michael@0 611 var sel = this.tree.view.selection;
michael@0 612 var rangeCount = sel.getRangeCount();
michael@0 613 if (rangeCount === 0)
michael@0 614 return 0;
michael@0 615 if (rangeCount !== 1) {
michael@0 616 throw "contiguous selection tree helper: getGrippyRow called with " +
michael@0 617 "multiple selection ranges";
michael@0 618 }
michael@0 619 var max = {};
michael@0 620 sel.getRangeAt(0, {}, max);
michael@0 621 return max.value + 1;
michael@0 622 },
michael@0 623
michael@0 624 /**
michael@0 625 * Helper function for the dragover event. Your dragover listener should
michael@0 626 * call this. It updates the selection in the tree under the mouse.
michael@0 627 *
michael@0 628 * @param aEvent
michael@0 629 * The observed dragover event
michael@0 630 */
michael@0 631 ondragover: function CSTH_ondragover(aEvent)
michael@0 632 {
michael@0 633 // Without this when dragging on Windows the mouse cursor is a "no" sign.
michael@0 634 // This makes it a drop symbol.
michael@0 635 var ds = Cc["@mozilla.org/widget/dragservice;1"].
michael@0 636 getService(Ci.nsIDragService).
michael@0 637 getCurrentSession();
michael@0 638 ds.canDrop = true;
michael@0 639 ds.dragAction = 0;
michael@0 640
michael@0 641 var tbo = this.tree.treeBoxObject;
michael@0 642 aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
michael@0 643 var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
michael@0 644
michael@0 645 if (hoverRow < 0)
michael@0 646 return;
michael@0 647
michael@0 648 this.rangedSelect(hoverRow - 1);
michael@0 649 },
michael@0 650
michael@0 651 /**
michael@0 652 * Helper function for the dragstart event. Your dragstart listener should
michael@0 653 * call this. It starts a drag session.
michael@0 654 *
michael@0 655 * @param aEvent
michael@0 656 * The observed dragstart event
michael@0 657 */
michael@0 658 ondragstart: function CSTH_ondragstart(aEvent)
michael@0 659 {
michael@0 660 var tbo = this.tree.treeBoxObject;
michael@0 661 var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
michael@0 662
michael@0 663 if (clickedRow !== this.getGrippyRow())
michael@0 664 return;
michael@0 665
michael@0 666 // This part is a hack. What we really want is a grab and slide, not
michael@0 667 // drag and drop. Start a move drag session with dummy data and a
michael@0 668 // dummy region. Set the region's coordinates to (Infinity, Infinity)
michael@0 669 // so it's drawn offscreen and its size to (1, 1).
michael@0 670 var arr = Cc["@mozilla.org/supports-array;1"].
michael@0 671 createInstance(Ci.nsISupportsArray);
michael@0 672 var trans = Cc["@mozilla.org/widget/transferable;1"].
michael@0 673 createInstance(Ci.nsITransferable);
michael@0 674 trans.init(null);
michael@0 675 trans.setTransferData('dummy-flavor', null, 0);
michael@0 676 arr.AppendElement(trans);
michael@0 677 var reg = Cc["@mozilla.org/gfx/region;1"].
michael@0 678 createInstance(Ci.nsIScriptableRegion);
michael@0 679 reg.setToRect(Infinity, Infinity, 1, 1);
michael@0 680 var ds = Cc["@mozilla.org/widget/dragservice;1"].
michael@0 681 getService(Ci.nsIDragService);
michael@0 682 ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
michael@0 683 },
michael@0 684
michael@0 685 /**
michael@0 686 * Helper function for the keypress event. Your keypress listener should
michael@0 687 * call this. Users can use Up, Down, Page Up/Down, Home, and End to move
michael@0 688 * the bottom of the selection window.
michael@0 689 *
michael@0 690 * @param aEvent
michael@0 691 * The observed keypress event
michael@0 692 */
michael@0 693 onkeypress: function CSTH_onkeypress(aEvent)
michael@0 694 {
michael@0 695 var grippyRow = this.getGrippyRow();
michael@0 696 var tbo = this.tree.treeBoxObject;
michael@0 697 var rangeEnd;
michael@0 698 switch (aEvent.keyCode) {
michael@0 699 case aEvent.DOM_VK_HOME:
michael@0 700 rangeEnd = 0;
michael@0 701 break;
michael@0 702 case aEvent.DOM_VK_PAGE_UP:
michael@0 703 rangeEnd = grippyRow - tbo.getPageLength();
michael@0 704 break;
michael@0 705 case aEvent.DOM_VK_UP:
michael@0 706 rangeEnd = grippyRow - 2;
michael@0 707 break;
michael@0 708 case aEvent.DOM_VK_DOWN:
michael@0 709 rangeEnd = grippyRow;
michael@0 710 break;
michael@0 711 case aEvent.DOM_VK_PAGE_DOWN:
michael@0 712 rangeEnd = grippyRow + tbo.getPageLength();
michael@0 713 break;
michael@0 714 case aEvent.DOM_VK_END:
michael@0 715 rangeEnd = this.tree.view.rowCount - 2;
michael@0 716 break;
michael@0 717 default:
michael@0 718 return;
michael@0 719 break;
michael@0 720 }
michael@0 721
michael@0 722 aEvent.stopPropagation();
michael@0 723
michael@0 724 // First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
michael@0 725 // select past the ends of the tree.
michael@0 726 if (rangeEnd < 0)
michael@0 727 rangeEnd = -1;
michael@0 728 else if (this.tree.view.rowCount - 2 < rangeEnd)
michael@0 729 rangeEnd = this.tree.view.rowCount - 2;
michael@0 730
michael@0 731 // Next, (de)select.
michael@0 732 this.rangedSelect(rangeEnd);
michael@0 733
michael@0 734 // Finally, scroll the tree. We always want one row above and below the
michael@0 735 // grippy row to be visible if possible.
michael@0 736 if (rangeEnd < grippyRow) // moved up
michael@0 737 tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
michael@0 738 else { // moved down
michael@0 739 if (rangeEnd + 2 < this.tree.view.rowCount)
michael@0 740 tbo.ensureRowIsVisible(rangeEnd + 2);
michael@0 741 else if (rangeEnd + 1 < this.tree.view.rowCount)
michael@0 742 tbo.ensureRowIsVisible(rangeEnd + 1);
michael@0 743 }
michael@0 744 },
michael@0 745
michael@0 746 /**
michael@0 747 * Helper function for the mousedown event. Your mousedown listener should
michael@0 748 * call this. Users can click on individual rows to make the selection
michael@0 749 * jump to them immediately.
michael@0 750 *
michael@0 751 * @param aEvent
michael@0 752 * The observed mousedown event
michael@0 753 */
michael@0 754 onmousedown: function CSTH_onmousedown(aEvent)
michael@0 755 {
michael@0 756 var tbo = this.tree.treeBoxObject;
michael@0 757 var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
michael@0 758
michael@0 759 if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
michael@0 760 return;
michael@0 761
michael@0 762 if (clickedRow < this.getGrippyRow())
michael@0 763 this.rangedSelect(clickedRow);
michael@0 764 else if (clickedRow > this.getGrippyRow())
michael@0 765 this.rangedSelect(clickedRow - 1);
michael@0 766 },
michael@0 767
michael@0 768 /**
michael@0 769 * Selects range [0, aEndRow] in the tree. The grippy row will then be at
michael@0 770 * index aEndRow + 1. aEndRow may be -1, in which case the selection is
michael@0 771 * cleared and the grippy row will be at index 0.
michael@0 772 *
michael@0 773 * @param aEndRow
michael@0 774 * The range [0, aEndRow] will be selected.
michael@0 775 */
michael@0 776 rangedSelect: function CSTH_rangedSelect(aEndRow)
michael@0 777 {
michael@0 778 var tbo = this.tree.treeBoxObject;
michael@0 779 if (aEndRow < 0)
michael@0 780 this.tree.view.selection.clearSelection();
michael@0 781 else
michael@0 782 this.tree.view.selection.rangedSelect(0, aEndRow, false);
michael@0 783 tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
michael@0 784 },
michael@0 785
michael@0 786 /**
michael@0 787 * Scrolls the tree so that the grippy row is in the center of the view.
michael@0 788 */
michael@0 789 scrollToGrippy: function CSTH_scrollToGrippy()
michael@0 790 {
michael@0 791 var rowCount = this.tree.view.rowCount;
michael@0 792 var tbo = this.tree.treeBoxObject;
michael@0 793 var pageLen = tbo.getPageLength() ||
michael@0 794 parseInt(this.tree.getAttribute("rows")) ||
michael@0 795 10;
michael@0 796
michael@0 797 // All rows fit on a single page.
michael@0 798 if (rowCount <= pageLen)
michael@0 799 return;
michael@0 800
michael@0 801 var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
michael@0 802
michael@0 803 // Grippy row is in first half of first page.
michael@0 804 if (scrollToRow < 0)
michael@0 805 scrollToRow = 0;
michael@0 806
michael@0 807 // Grippy row is in last half of last page.
michael@0 808 else if (rowCount < scrollToRow + pageLen)
michael@0 809 scrollToRow = rowCount - pageLen;
michael@0 810
michael@0 811 tbo.scrollToRow(scrollToRow);
michael@0 812 },
michael@0 813
michael@0 814 /**
michael@0 815 * Creates a new tree view suitable for contiguous selection. If
michael@0 816 * aProtoTreeView is specified, it's used as the new view's prototype.
michael@0 817 * Otherwise the tree's current view is used as the prototype.
michael@0 818 *
michael@0 819 * @param aProtoTreeView
michael@0 820 * Used as the new view's prototype if specified
michael@0 821 */
michael@0 822 _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
michael@0 823 {
michael@0 824 var view = aProtoTreeView;
michael@0 825 var that = this;
michael@0 826
michael@0 827 //XXXadw: When Alex gets the grippy icon done, this may or may not change,
michael@0 828 // depending on how we style it.
michael@0 829 view.isSeparator = function CSTH_View_isSeparator(aRow)
michael@0 830 {
michael@0 831 return aRow === that.getGrippyRow();
michael@0 832 };
michael@0 833
michael@0 834 // rowCount includes the grippy row.
michael@0 835 view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
michael@0 836 view.__defineGetter__("rowCount",
michael@0 837 function CSTH_View_rowCount()
michael@0 838 {
michael@0 839 return this._rowCount + 1;
michael@0 840 });
michael@0 841
michael@0 842 // This has to do with visual feedback in the view itself, e.g., drawing
michael@0 843 // a small line underneath the dropzone. Not what we want.
michael@0 844 view.canDrop = function CSTH_View_canDrop() { return false; };
michael@0 845
michael@0 846 // No clicking headers to sort the tree or sort feedback on columns.
michael@0 847 view.cycleHeader = function CSTH_View_cycleHeader() {};
michael@0 848 view.sortingChanged = function CSTH_View_sortingChanged() {};
michael@0 849
michael@0 850 // Override a bunch of methods to account for the grippy row.
michael@0 851
michael@0 852 view._getCellProperties = view.getCellProperties;
michael@0 853 view.getCellProperties =
michael@0 854 function CSTH_View_getCellProperties(aRow, aCol)
michael@0 855 {
michael@0 856 var grippyRow = that.getGrippyRow();
michael@0 857 if (aRow === grippyRow)
michael@0 858 return "grippyRow";
michael@0 859 if (aRow < grippyRow)
michael@0 860 return this._getCellProperties(aRow, aCol);
michael@0 861
michael@0 862 return this._getCellProperties(aRow - 1, aCol);
michael@0 863 };
michael@0 864
michael@0 865 view._getRowProperties = view.getRowProperties;
michael@0 866 view.getRowProperties =
michael@0 867 function CSTH_View_getRowProperties(aRow)
michael@0 868 {
michael@0 869 var grippyRow = that.getGrippyRow();
michael@0 870 if (aRow === grippyRow)
michael@0 871 return "grippyRow";
michael@0 872
michael@0 873 if (aRow < grippyRow)
michael@0 874 return this._getRowProperties(aRow);
michael@0 875
michael@0 876 return this._getRowProperties(aRow - 1);
michael@0 877 };
michael@0 878
michael@0 879 view._getCellText = view.getCellText;
michael@0 880 view.getCellText =
michael@0 881 function CSTH_View_getCellText(aRow, aCol)
michael@0 882 {
michael@0 883 var grippyRow = that.getGrippyRow();
michael@0 884 if (aRow === grippyRow)
michael@0 885 return "";
michael@0 886 aRow = aRow < grippyRow ? aRow : aRow - 1;
michael@0 887 return this._getCellText(aRow, aCol);
michael@0 888 };
michael@0 889
michael@0 890 view._getImageSrc = view.getImageSrc;
michael@0 891 view.getImageSrc =
michael@0 892 function CSTH_View_getImageSrc(aRow, aCol)
michael@0 893 {
michael@0 894 var grippyRow = that.getGrippyRow();
michael@0 895 if (aRow === grippyRow)
michael@0 896 return "";
michael@0 897 aRow = aRow < grippyRow ? aRow : aRow - 1;
michael@0 898 return this._getImageSrc(aRow, aCol);
michael@0 899 };
michael@0 900
michael@0 901 view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
michael@0 902 view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
michael@0 903 view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
michael@0 904 view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
michael@0 905 {
michael@0 906 return aRow < this.rowCount - 1;
michael@0 907 };
michael@0 908
michael@0 909 return view;
michael@0 910 }
michael@0 911 };
michael@0 912 #endif

mercurial