sanitizeDialog.js
Go to the documentation of this file.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is the Firefox Sanitizer.
16  *
17  * The Initial Developer of the Original Code is
18  * Ben Goodger.
19  * Portions created by the Initial Developer are Copyright (C) 2005
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Ben Goodger <ben@mozilla.org>
24  * Giorgio Maone <g.maone@informaction.com>
25  * Johnathan Nightingale <johnath@mozilla.com>
26  * Drew Willcoxon <adw@mozilla.com>
27  * Ehsan Akhgari <ehsan.akhgari@gmail.com>
28  *
29  * Alternatively, the contents of this file may be used under the terms of
30  * either the GNU General Public License Version 2 or later (the "GPL"), or
31  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32  * in which case the provisions of the GPL or the LGPL are applicable instead
33  * of those above. If you wish to allow use of your version of this file only
34  * under the terms of either the GPL or the LGPL, and not to allow others to
35  * use your version of this file under the terms of the MPL, indicate your
36  * decision by deleting the provisions above and replace them with the notice
37  * and other provisions required by the GPL or the LGPL. If you do not delete
38  * the provisions above, a recipient may use your version of this file under
39  * the terms of any one of the MPL, the GPL or the LGPL.
40  *
41  * ***** END LICENSE BLOCK ***** */
42 
43 const Cc = Components.classes;
44 const Ci = Components.interfaces;
45 
47 
48  get bundleBrowser()
49  {
50  if (!this._bundleBrowser)
51  this._bundleBrowser = document.getElementById("bundleBrowser");
52  return this._bundleBrowser;
53  },
54 
55  get selectedTimespan()
56  {
57  var durList = document.getElementById("sanitizeDurationChoice");
58  return parseInt(durList.value);
59  },
60 
61  get sanitizePreferences()
62  {
63  if (!this._sanitizePreferences) {
64  this._sanitizePreferences =
65  document.getElementById("sanitizePreferences");
66  }
67  return this._sanitizePreferences;
68  },
69 
70  get warningBox()
71  {
72  return document.getElementById("sanitizeEverythingWarningBox");
73  },
74 
75  init: function ()
76  {
77  // This is used by selectByTimespan() to determine if the window has loaded.
78  this._inited = true;
79 
80  var s = new Sanitizer();
81  s.prefDomain = "privacy.cpd.";
82  for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
83  var preference = this.sanitizePreferences.childNodes[i];
84  var name = s.getNameFromPreference(preference.name);
85  if (!s.canClearItem(name))
86  preference.disabled = true;
87  }
88 
89  document.documentElement.getButton("accept").label =
90  this.bundleBrowser.getString("sanitizeButtonOK");
91 
92  if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
93  this.prepareWarning();
94  this.warningBox.hidden = false;
95  }
96  else
97  this.warningBox.hidden = true;
98  },
99 
100  selectByTimespan: function ()
101  {
102  // This method is the onselect handler for the duration dropdown. As a
103  // result it's called a couple of times before onload calls init().
104  if (!this._inited)
105  return;
106 
107  var warningBox = this.warningBox;
108 
109  // If clearing everything
110  if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
111  this.prepareWarning();
112  if (warningBox.hidden) {
113  warningBox.hidden = false;
114  window.resizeBy(0, warningBox.boxObject.height);
115  }
116  window.document.title =
117  this.bundleBrowser.getString("sanitizeDialog2.everything.title");
118  return;
119  }
120 
121  // If clearing a specific time range
122  if (!warningBox.hidden) {
123  window.resizeBy(0, -warningBox.boxObject.height);
124  warningBox.hidden = true;
125  }
126  window.document.title =
127  window.document.documentElement.getAttribute("noneverythingtitle");
128  },
129 
130  sanitize: function ()
131  {
132  // Update pref values before handing off to the sanitizer (bug 453440)
133  this.updatePrefs();
134  var s = new Sanitizer();
135  s.prefDomain = "privacy.cpd.";
136 
137  s.range = Sanitizer.getClearRange(this.selectedTimespan);
138  s.ignoreTimespan = !s.range;
139 
140  try {
141  s.sanitize();
142  } catch (er) {
143  Components.utils.reportError("Exception during sanitize: " + er);
144  }
145  return true;
146  },
147 
156  prepareWarning: function (aDontShowItemList) {
157  // If the date and time-aware locale warning string is ever used again,
158  // initialize it here. Currently we use the no-visits warning string,
159  // which does not include date and time. See bug 480169 comment 48.
160 
161  var warningStringID;
162  if (this.hasNonSelectedItems()) {
163  warningStringID = "sanitizeSelectedWarning";
164  if (!aDontShowItemList)
165  this.showItemList();
166  }
167  else {
168  warningStringID = "sanitizeEverythingWarning2";
169  }
170 
171  var warningDesc = document.getElementById("sanitizeEverythingWarning");
172  warningDesc.textContent =
173  this.bundleBrowser.getString(warningStringID);
174  },
175 
180  onReadGeneric: function ()
181  {
182  var found = false;
183 
184  // Find any other pref that's checked and enabled.
185  var i = 0;
186  while (!found && i < this.sanitizePreferences.childNodes.length) {
187  var preference = this.sanitizePreferences.childNodes[i];
188 
189  found = !!preference.value &&
190  !preference.disabled;
191  i++;
192  }
193 
194  try {
195  document.documentElement.getButton("accept").disabled = !found;
196  }
197  catch (e) { }
198 
199  // Update the warning prompt if needed
200  this.prepareWarning(true);
201 
202  return undefined;
203  },
204 
213  updatePrefs : function ()
214  {
215  var tsPref = document.getElementById("privacy.sanitize.timeSpan");
216  Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
217 
218  // Keep the pref for the download history in sync with the history pref.
219  document.getElementById("privacy.cpd.downloads").value =
220  document.getElementById("privacy.cpd.history").value;
221 
222  // Now manually set the prefs from their corresponding preference
223  // elements.
224  var prefs = this.sanitizePreferences.rootBranch;
225  for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
226  var p = this.sanitizePreferences.childNodes[i];
227  prefs.setBoolPref(p.name, p.value);
228  }
229  },
230 
234  hasNonSelectedItems: function () {
235  let checkboxes = document.querySelectorAll("#itemList > [preference]");
236  for (let i = 0; i < checkboxes.length; ++i) {
237  let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
238  if (!pref.value)
239  return true;
240  }
241  return false;
242  },
243 
247  showItemList: function () {
248  var itemList = document.getElementById("itemList");
249  var expanderButton = document.getElementById("detailsExpander");
250 
251  if (itemList.collapsed) {
252  expanderButton.className = "expander-up";
253  itemList.setAttribute("collapsed", "false");
254  if (document.documentElement.boxObject.height)
255  window.resizeBy(0, itemList.boxObject.height);
256  }
257  },
258 
262  hideItemList: function () {
263  var itemList = document.getElementById("itemList");
264  var expanderButton = document.getElementById("detailsExpander");
265 
266  if (!itemList.collapsed) {
267  expanderButton.className = "expander-down";
268  window.resizeBy(0, -itemList.boxObject.height);
269  itemList.setAttribute("collapsed", "true");
270  }
271  },
272 
276  toggleItemList: function ()
277  {
278  var itemList = document.getElementById("itemList");
279 
280  if (itemList.collapsed)
281  this.showItemList();
282  else
283  this.hideItemList();
284  }
285 
286 #ifdef CRH_DIALOG_TREE_VIEW
287  // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
288  // Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
289  // of the sanitizeDurationCustom menuitem.
290  get TIMESPAN_CUSTOM()
291  {
292  return -1;
293  },
294 
295  get placesTree()
296  {
297  if (!this._placesTree)
298  this._placesTree = document.getElementById("placesTree");
299  return this._placesTree;
300  },
301 
302  init: function ()
303  {
304  // This is used by selectByTimespan() to determine if the window has loaded.
305  this._inited = true;
306 
307  var s = new Sanitizer();
308  s.prefDomain = "privacy.cpd.";
309  for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
310  var preference = this.sanitizePreferences.childNodes[i];
311  var name = s.getNameFromPreference(preference.name);
312  if (!s.canClearItem(name))
313  preference.disabled = true;
314  }
315 
316  document.documentElement.getButton("accept").label =
317  this.bundleBrowser.getString("sanitizeButtonOK");
318 
319  this.selectByTimespan();
320  },
321 
328  initDurationDropdown: function ()
329  {
330  // First, calculate the start times for each duration.
331  this.durationStartTimes = {};
332  var durVals = [];
333  var durPopup = document.getElementById("sanitizeDurationPopup");
334  var durMenuitems = durPopup.childNodes;
335  for (let i = 0; i < durMenuitems.length; i++) {
336  let durMenuitem = durMenuitems[i];
337  let durVal = parseInt(durMenuitem.value);
338  if (durMenuitem.localName === "menuitem" &&
339  durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
340  durVal !== this.TIMESPAN_CUSTOM) {
341  durVals.push(durVal);
342  let durTimes = Sanitizer.getClearRange(durVal);
343  this.durationStartTimes[durVal] = durTimes[0];
344  }
345  }
346 
347  // Sort the duration values ascending. Because one tree index can map to
348  // more than one duration, this ensures that this.durationRowsToVals maps
349  // a row index to the largest duration possible in the code below.
350  durVals.sort();
351 
352  // Now calculate the rows in the tree of the durations' start times. For
353  // each duration, we are looking for the node in the tree whose time is the
354  // smallest time greater than or equal to the duration's start time.
355  this.durationRowsToVals = {};
356  this.durationValsToRows = {};
357  var view = this.placesTree.view;
358  // For all rows in the tree except the grippy row...
359  for (let i = 0; i < view.rowCount - 1; i++) {
360  let unfoundDurVals = [];
361  let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
362  nodeForTreeIndex(i).time;
363  // For all durations whose rows have not yet been found in the tree, see
364  // if index i is their index. An index may map to more than one duration,
365  // in which case the final duration (the largest) wins.
366  for (let j = 0; j < durVals.length; j++) {
367  let durVal = durVals[j];
368  let durStartTime = this.durationStartTimes[durVal];
369  if (nodeTime < durStartTime) {
370  this.durationValsToRows[durVal] = i - 1;
371  this.durationRowsToVals[i - 1] = durVal;
372  }
373  else
374  unfoundDurVals.push(durVal);
375  }
376  durVals = unfoundDurVals;
377  }
378 
379  // If any durations were not found above, then every node in the tree has a
380  // time greater than or equal to the duration. In other words, those
381  // durations include the entire tree (except the grippy row).
382  for (let i = 0; i < durVals.length; i++) {
383  let durVal = durVals[i];
384  this.durationValsToRows[durVal] = view.rowCount - 2;
385  this.durationRowsToVals[view.rowCount - 2] = durVal;
386  }
387  },
388 
392  ensurePlacesTreeIsInited: function ()
393  {
394  if (this._placesTreeIsInited)
395  return;
396 
397  this._placesTreeIsInited = true;
398 
399  // Either "Last Four Hours" or "Today" will have the most history. If
400  // it's been more than 4 hours since today began, "Today" will. Otherwise
401  // "Last Four Hours" will.
402  var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
403 
404  // If it's been less than 4 hours since today began, use the past 4 hours.
405  if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
406  times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
407  }
408 
409  var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
410  getService(Ci.nsINavHistoryService);
411  var query = histServ.getNewQuery();
412  query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
413  query.beginTime = times[0];
414  query.endTimeReference = query.TIME_RELATIVE_EPOCH;
415  query.endTime = times[1];
416  var opts = histServ.getNewQueryOptions();
417  opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
418  opts.queryType = opts.QUERY_TYPE_HISTORY;
419  var result = histServ.executeQuery(query, opts);
420 
421  var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
422  new PlacesTreeView());
423  result.viewer = view;
424  this.initDurationDropdown();
425  },
426 
433  selectByTimespan: function ()
434  {
435  // This method is the onselect handler for the duration dropdown. As a
436  // result it's called a couple of times before onload calls init().
437  if (!this._inited)
438  return;
439 
440  var durDeck = document.getElementById("durationDeck");
441  var durList = document.getElementById("sanitizeDurationChoice");
442  var durVal = parseInt(durList.value);
443  var durCustom = document.getElementById("sanitizeDurationCustom");
444 
445  // If grippy row is not at a duration boundary, show the custom menuitem;
446  // otherwise, hide it. Since the user cannot specify a custom duration by
447  // using the dropdown, this conditional is true only when this method is
448  // called onselect from grippyMoved(), so no selection need be made.
449  if (durVal === this.TIMESPAN_CUSTOM) {
450  durCustom.hidden = false;
451  return;
452  }
453  durCustom.hidden = true;
454 
455  // If clearing everything, show the warning and change the dialog's title.
456  if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
457  this.prepareWarning();
458  durDeck.selectedIndex = 1;
459  window.document.title =
460  this.bundleBrowser.getString("sanitizeDialog2.everything.title");
461  document.documentElement.getButton("accept").disabled = false;
462  return;
463  }
464 
465  // Otherwise -- if clearing a specific time range -- select that time range
466  // in the tree.
467  this.ensurePlacesTreeIsInited();
468  durDeck.selectedIndex = 0;
469  window.document.title =
470  window.document.documentElement.getAttribute("noneverythingtitle");
471  var durRow = this.durationValsToRows[durVal];
472  gContiguousSelectionTreeHelper.rangedSelect(durRow);
473  gContiguousSelectionTreeHelper.scrollToGrippy();
474 
475  // If duration is empty (there are no selected rows), disable the dialog's
476  // OK button.
477  document.documentElement.getButton("accept").disabled = durRow < 0;
478  },
479 
480  sanitize: function ()
481  {
482  // Update pref values before handing off to the sanitizer (bug 453440)
483  this.updatePrefs();
484  var s = new Sanitizer();
485  s.prefDomain = "privacy.cpd.";
486 
487  var durList = document.getElementById("sanitizeDurationChoice");
488  var durValue = parseInt(durList.value);
489  s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
490 
491  // Set the sanitizer's time range if we're not clearing everything.
492  if (!s.ignoreTimespan) {
493  // If user selected a custom timespan, use that.
494  if (durValue === this.TIMESPAN_CUSTOM) {
495  var view = this.placesTree.view;
496  var now = Date.now() * 1000;
497  // We disable the dialog's OK button if there's no selection, but we'll
498  // handle that case just in... case.
499  if (view.selection.getRangeCount() === 0)
500  s.range = [now, now];
501  else {
502  var startIndexRef = {};
503  // Tree sorted by visit date DEscending, so start time time comes last.
504  view.selection.getRangeAt(0, {}, startIndexRef);
505  view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
506  var startNode = view.nodeForTreeIndex(startIndexRef.value);
507  s.range = [startNode.time, now];
508  }
509  }
510  // Otherwise use the predetermined range.
511  else
512  s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
513  }
514 
515  try {
516  s.sanitize();
517  } catch (er) {
518  Components.utils.reportError("Exception during sanitize: " + er);
519  }
520  return true;
521  },
522 
528  unload: function ()
529  {
530  var view = this.placesTree.view;
531  view.QueryInterface(Ci.nsINavHistoryResultViewer).result.viewer = null;
532  this.placesTree.view = null;
533  },
534 
546  grippyMoved: function (aEventName, aEvent)
547  {
548  gContiguousSelectionTreeHelper[aEventName](aEvent);
549  var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
550  var durList = document.getElementById("sanitizeDurationChoice");
551  var durValue = parseInt(durList.value);
552 
553  // Multiple durations can map to the same row. Don't update the dropdown
554  // if the current duration is valid for lastSelRow.
555  if ((durValue !== this.TIMESPAN_CUSTOM ||
556  lastSelRow in this.durationRowsToVals) &&
557  (durValue === this.TIMESPAN_CUSTOM ||
558  this.durationValsToRows[durValue] !== lastSelRow)) {
559  // Setting durList.value causes its onselect handler to fire, which calls
560  // selectByTimespan().
561  if (lastSelRow in this.durationRowsToVals)
562  durList.value = this.durationRowsToVals[lastSelRow];
563  else
564  durList.value = this.TIMESPAN_CUSTOM;
565  }
566 
567  // If there are no selected rows, disable the dialog's OK button.
568  document.documentElement.getButton("accept").disabled = lastSelRow < 0;
569  }
570 #endif
571 
572 };
573 
574 
575 #ifdef CRH_DIALOG_TREE_VIEW
576 
579 var gContiguousSelectionTreeHelper = {
580 
584  get tree()
585  {
586  return this._tree;
587  },
588 
602  setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
603  {
604  this._tree = aTreeElement;
605  var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
606  aTreeElement.view = newView;
607  return newView;
608  },
609 
617  getGrippyRow: function CSTH_getGrippyRow()
618  {
619  var sel = this.tree.view.selection;
620  var rangeCount = sel.getRangeCount();
621  if (rangeCount === 0)
622  return 0;
623  if (rangeCount !== 1) {
624  throw "contiguous selection tree helper: getGrippyRow called with " +
625  "multiple selection ranges";
626  }
627  var max = {};
628  sel.getRangeAt(0, {}, max);
629  return max.value + 1;
630  },
631 
639  ondragover: function CSTH_ondragover(aEvent)
640  {
641  // Without this when dragging on Windows the mouse cursor is a "no" sign.
642  // This makes it a drop symbol.
643  var ds = Cc["@mozilla.org/widget/dragservice;1"].
644  getService(Ci.nsIDragService).
645  getCurrentSession();
646  ds.canDrop = true;
647  ds.dragAction = 0;
648 
649  var tbo = this.tree.treeBoxObject;
650  aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
651  var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
652 
653  if (hoverRow < 0)
654  return;
655 
656  this.rangedSelect(hoverRow - 1);
657  },
658 
666  ondragstart: function CSTH_ondragstart(aEvent)
667  {
668  var tbo = this.tree.treeBoxObject;
669  var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
670 
671  if (clickedRow !== this.getGrippyRow())
672  return;
673 
674  // This part is a hack. What we really want is a grab and slide, not
675  // drag and drop. Start a move drag session with dummy data and a
676  // dummy region. Set the region's coordinates to (Infinity, Infinity)
677  // so it's drawn offscreen and its size to (1, 1).
678  var arr = Cc["@mozilla.org/supports-array;1"].
679  createInstance(Ci.nsISupportsArray);
680  var trans = Cc["@mozilla.org/widget/transferable;1"].
681  createInstance(Ci.nsITransferable);
682  trans.setTransferData('dummy-flavor', null, 0);
683  arr.AppendElement(trans);
684  var reg = Cc["@mozilla.org/gfx/region;1"].
685  createInstance(Ci.nsIScriptableRegion);
686  reg.setToRect(Infinity, Infinity, 1, 1);
687  var ds = Cc["@mozilla.org/widget/dragservice;1"].
688  getService(Ci.nsIDragService);
689  ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
690  },
691 
700  onkeypress: function CSTH_onkeypress(aEvent)
701  {
702  var grippyRow = this.getGrippyRow();
703  var tbo = this.tree.treeBoxObject;
704  var rangeEnd;
705  switch (aEvent.keyCode) {
706  case aEvent.DOM_VK_HOME:
707  rangeEnd = 0;
708  break;
709  case aEvent.DOM_VK_PAGE_UP:
710  rangeEnd = grippyRow - tbo.getPageLength();
711  break;
712  case aEvent.DOM_VK_UP:
713  rangeEnd = grippyRow - 2;
714  break;
715  case aEvent.DOM_VK_DOWN:
716  rangeEnd = grippyRow;
717  break;
718  case aEvent.DOM_VK_PAGE_DOWN:
719  rangeEnd = grippyRow + tbo.getPageLength();
720  break;
721  case aEvent.DOM_VK_END:
722  rangeEnd = this.tree.view.rowCount - 2;
723  break;
724  default:
725  return;
726  break;
727  }
728 
729  aEvent.stopPropagation();
730 
731  // First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
732  // select past the ends of the tree.
733  if (rangeEnd < 0)
734  rangeEnd = -1;
735  else if (this.tree.view.rowCount - 2 < rangeEnd)
736  rangeEnd = this.tree.view.rowCount - 2;
737 
738  // Next, (de)select.
739  this.rangedSelect(rangeEnd);
740 
741  // Finally, scroll the tree. We always want one row above and below the
742  // grippy row to be visible if possible.
743  if (rangeEnd < grippyRow) // moved up
744  tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
745  else { // moved down
746  if (rangeEnd + 2 < this.tree.view.rowCount)
747  tbo.ensureRowIsVisible(rangeEnd + 2);
748  else if (rangeEnd + 1 < this.tree.view.rowCount)
749  tbo.ensureRowIsVisible(rangeEnd + 1);
750  }
751  },
752 
761  onmousedown: function CSTH_onmousedown(aEvent)
762  {
763  var tbo = this.tree.treeBoxObject;
764  var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
765 
766  if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
767  return;
768 
769  if (clickedRow < this.getGrippyRow())
770  this.rangedSelect(clickedRow);
771  else if (clickedRow > this.getGrippyRow())
772  this.rangedSelect(clickedRow - 1);
773  },
774 
783  rangedSelect: function CSTH_rangedSelect(aEndRow)
784  {
785  var tbo = this.tree.treeBoxObject;
786  if (aEndRow < 0)
787  this.tree.view.selection.clearSelection();
788  else
789  this.tree.view.selection.rangedSelect(0, aEndRow, false);
790  tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
791  },
792 
796  scrollToGrippy: function CSTH_scrollToGrippy()
797  {
798  var rowCount = this.tree.view.rowCount;
799  var tbo = this.tree.treeBoxObject;
800  var pageLen = tbo.getPageLength() ||
801  parseInt(this.tree.getAttribute("rows")) ||
802  10;
803 
804  // All rows fit on a single page.
805  if (rowCount <= pageLen)
806  return;
807 
808  var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
809 
810  // Grippy row is in first half of first page.
811  if (scrollToRow < 0)
812  scrollToRow = 0;
813 
814  // Grippy row is in last half of last page.
815  else if (rowCount < scrollToRow + pageLen)
816  scrollToRow = rowCount - pageLen;
817 
818  tbo.scrollToRow(scrollToRow);
819  },
820 
829  _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
830  {
831  var atomServ = Cc["@mozilla.org/atom-service;1"].
832  getService(Ci.nsIAtomService);
833 
834  var view = aProtoTreeView;
835  var that = this;
836 
837  //XXXadw: When Alex gets the grippy icon done, this may or may not change,
838  // depending on how we style it.
839  view.isSeparator = function CSTH_View_isSeparator(aRow)
840  {
841  return aRow === that.getGrippyRow();
842  };
843 
844  // rowCount includes the grippy row.
845  view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
846  view.__defineGetter__("rowCount",
847  function CSTH_View_rowCount()
848  {
849  return this._rowCount + 1;
850  });
851 
852  // This has to do with visual feedback in the view itself, e.g., drawing
853  // a small line underneath the dropzone. Not what we want.
854  view.canDrop = function CSTH_View_canDrop() { return false; };
855 
856  // No clicking headers to sort the tree or sort feedback on columns.
857  view.cycleHeader = function CSTH_View_cycleHeader() {};
858  view.sortingChanged = function CSTH_View_sortingChanged() {};
859 
860  // Override a bunch of methods to account for the grippy row.
861 
862  view._getCellProperties = view.getCellProperties;
863  view.getCellProperties =
864  function CSTH_View_getCellProperties(aRow, aCol, aProps)
865  {
866  var grippyRow = that.getGrippyRow();
867  if (aRow === grippyRow)
868  aProps.AppendElement(atomServ.getAtom("grippyRow"));
869  else if (aRow < grippyRow)
870  this._getCellProperties(aRow, aCol, aProps);
871  else
872  this._getCellProperties(aRow - 1, aCol, aProps);
873  };
874 
875  view._getRowProperties = view.getRowProperties;
876  view.getRowProperties =
877  function CSTH_View_getRowProperties(aRow, aProps)
878  {
879  var grippyRow = that.getGrippyRow();
880  if (aRow === grippyRow)
881  aProps.AppendElement(atomServ.getAtom("grippyRow"));
882  else if (aRow < grippyRow)
883  this._getRowProperties(aRow, aProps);
884  else
885  this._getRowProperties(aRow - 1, aProps);
886  };
887 
888  view._getCellText = view.getCellText;
889  view.getCellText =
890  function CSTH_View_getCellText(aRow, aCol)
891  {
892  var grippyRow = that.getGrippyRow();
893  if (aRow === grippyRow)
894  return "";
895  aRow = aRow < grippyRow ? aRow : aRow - 1;
896  return this._getCellText(aRow, aCol);
897  };
898 
899  view._getImageSrc = view.getImageSrc;
900  view.getImageSrc =
901  function CSTH_View_getImageSrc(aRow, aCol)
902  {
903  var grippyRow = that.getGrippyRow();
904  if (aRow === grippyRow)
905  return "";
906  aRow = aRow < grippyRow ? aRow : aRow - 1;
907  return this._getImageSrc(aRow, aCol);
908  };
909 
910  view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
911  view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
912  view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
913  view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
914  {
915  return aRow < this.rowCount - 1;
916  };
917 
918  return view;
919  }
920 };
921 #endif
const Cc
var pref
Definition: openLocation.js:44
sidebarFactory createInstance
Definition: nsSidebar.js:351
getService(Ci.sbIFaceplateManager)
let window
Sanitizer sanitize
Definition: sanitize.js:464
_window init
Definition: FeedWriter.js:1144
var gSanitizePromptDialog
ConcertTicketing unload
Definition: browse.js:54
const Ci
return null
Definition: FeedWriter.js:1143
var prefs
Definition: FeedWriter.js:1169
return aWindow document documentElement getAttribute(aAttribute)||dimension
function Sanitizer()
Definition: sanitize.js:42
function now()
function PlacesTreeView(aFlatList, aOnOpenFlatContainer)
Definition: treeView.js:1343
_getSelectedPageStyle s i