browser/base/content/test/general/browser_sanitizeDialog_treeView.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

     1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /**
     8  * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
     9  * See bug 480169.
    10  *
    11  * The purpose of this test is not to fully flex the sanitize timespan code;
    12  * browser/base/content/test/general/browser_sanitize-timespans.js does that.  This
    13  * test checks the UI of the dialog and makes sure it's correctly connected to
    14  * the sanitize timespan code.
    15  *
    16  * Some of this code, especially the history creation parts, was taken from
    17  * browser/base/content/test/general/browser_sanitize-timespans.js.
    18  */
    20 Cc["@mozilla.org/moz/jssubscript-loader;1"].
    21   getService(Ci.mozIJSSubScriptLoader).
    22   loadSubScript("chrome://browser/content/sanitize.js");
    24 const dm = Cc["@mozilla.org/download-manager;1"].
    25            getService(Ci.nsIDownloadManager);
    26 const formhist = Cc["@mozilla.org/satchel/form-history;1"].
    27                  getService(Ci.nsIFormHistory2);
    29 // Add tests here.  Each is a function that's called by doNextTest().
    30 var gAllTests = [
    32   /**
    33    * Moves the grippy around, makes sure it works OK.
    34    */
    35   function () {
    36     // Add history (within the past hour) to get some rows in the tree.
    37     let uris = [];
    38     let places = [];
    39     let pURI;
    40     for (let i = 0; i < 30; i++) {
    41       pURI = makeURI("http://" + i + "-minutes-ago.com/");
    42       places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
    43       uris.push(pURI);
    44     }
    46     addVisits(places, function() {
    47       // Open the dialog and do our tests.
    48       openWindow(function (aWin) {
    49         let wh = new WindowHelper(aWin);
    50         wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
    51         wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
    52                        "duration",
    53                        wh.getRowCount() - 1);
    55         // Move the grippy around.
    56         let row = wh.getGrippyRow();
    57         while (row !== 0) {
    58           row--;
    59           wh.moveGrippyBy(-1);
    60           wh.checkGrippy("Grippy should be moved up one row", row);
    61         }
    62         wh.moveGrippyBy(-1);
    63         wh.checkGrippy("Grippy should remain at first row after trying to move " +
    64                        "it up",
    65                        0);
    66         while (row !== wh.getRowCount() - 1) {
    67           row++;
    68           wh.moveGrippyBy(1);
    69           wh.checkGrippy("Grippy should be moved down one row", row);
    70         }
    71         wh.moveGrippyBy(1);
    72         wh.checkGrippy("Grippy should remain at last row after trying to move " +
    73                        "it down",
    74                        wh.getRowCount() - 1);
    76         // Cancel the dialog, make sure history visits are not cleared.
    77         wh.checkPrefCheckbox("history", false);
    79         wh.cancelDialog();
    80         yield promiseHistoryClearedState(uris, false);
    82         // OK, done, cleanup after ourselves.
    83         blankSlate();
    84         yield promiseHistoryClearedState(uris, true);
    85       });
    86     });
    87   },
    89   /**
    90    * Ensures that the combined history-downloads checkbox clears both history
    91    * visits and downloads when checked; the dialog respects simple timespan.
    92    */
    93   function () {
    94     // Add history (within the past hour).
    95     let uris = [];
    96     let places = [];
    97     let pURI;
    98     for (let i = 0; i < 30; i++) {
    99       pURI = makeURI("http://" + i + "-minutes-ago.com/");
   100       places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
   101       uris.push(pURI);
   102     }
   103     // Add history (over an hour ago).
   104     let olderURIs = [];
   105     for (let i = 0; i < 5; i++) {
   106       pURI = makeURI("http://" + (60 + i) + "-minutes-ago.com/");
   107       places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(60 + i)});
   108       olderURIs.push(pURI);
   109     }
   111     addVisits(places, function() {
   112       // Add downloads (within the past hour).
   113       let downloadIDs = [];
   114       for (let i = 0; i < 5; i++) {
   115         downloadIDs.push(addDownloadWithMinutesAgo(i));
   116       }
   117       // Add downloads (over an hour ago).
   118       let olderDownloadIDs = [];
   119       for (let i = 0; i < 5; i++) {
   120         olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
   121       }
   122       let totalHistoryVisits = uris.length + olderURIs.length;
   124       // Open the dialog and do our tests.
   125       openWindow(function (aWin) {
   126         let wh = new WindowHelper(aWin);
   127         wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
   128         wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
   129                        "duration",
   130                        uris.length);
   132         // Accept the dialog, make sure history visits and downloads within one
   133         // hour are cleared.
   134         wh.checkPrefCheckbox("history", true);
   135         wh.acceptDialog();
   136         yield promiseHistoryClearedState(uris, true);
   137         ensureDownloadsClearedState(downloadIDs, true);
   139         // Make sure visits and downloads > 1 hour still exist.
   140         yield promiseHistoryClearedState(olderURIs, false);
   141         ensureDownloadsClearedState(olderDownloadIDs, false);
   143         // OK, done, cleanup after ourselves.
   144         blankSlate();
   145         yield promiseHistoryClearedState(olderURIs, true);
   146         ensureDownloadsClearedState(olderDownloadIDs, true);
   147       });
   148     });
   149   },
   151   /**
   152    * Ensures that the combined history-downloads checkbox removes neither
   153    * history visits nor downloads when not checked.
   154    */
   155   function () {
   156     // Add history, downloads, form entries (within the past hour).
   157     let uris = [];
   158     let places = [];
   159     let pURI;
   160     for (let i = 0; i < 5; i++) {
   161       pURI = makeURI("http://" + i + "-minutes-ago.com/");
   162       places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
   163       uris.push(pURI);
   164     }
   166     addVisits(places, function() {
   167       let downloadIDs = [];
   168       for (let i = 0; i < 5; i++) {
   169         downloadIDs.push(addDownloadWithMinutesAgo(i));
   170       }
   171       let formEntries = [];
   172       for (let i = 0; i < 5; i++) {
   173         formEntries.push(addFormEntryWithMinutesAgo(i));
   174       }
   176       // Open the dialog and do our tests.
   177       openWindow(function (aWin) {
   178         let wh = new WindowHelper(aWin);
   179         wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
   180         wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
   181                        "duration",
   182                        wh.getRowCount() - 1);
   184         // Remove only form entries, leave history (including downloads).
   185         wh.checkPrefCheckbox("history", false);
   186         wh.checkPrefCheckbox("formdata", true);
   187         wh.acceptDialog();
   189         // Of the three only form entries should be cleared.
   190         yield promiseHistoryClearedState(uris, false);
   191         ensureDownloadsClearedState(downloadIDs, false);
   192         ensureFormEntriesClearedState(formEntries, true);
   194         // OK, done, cleanup after ourselves.
   195         blankSlate();
   196         yield promiseHistoryClearedState(uris, true);
   197         ensureDownloadsClearedState(downloadIDs, true);
   198       });
   199     });
   200   },
   202   /**
   203    * Ensures that the "Everything" duration option works.
   204    */
   205   function () {
   206     // Add history.
   207     let uris = [];
   208     let places = [];
   209     let pURI;
   210     // within past hour, within past two hours, within past four hours and 
   211     // outside past four hours
   212     [10, 70, 130, 250].forEach(function(aValue) {
   213       pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
   214       places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
   215       uris.push(pURI);
   216     });
   217     addVisits(places, function() {
   219       // Open the dialog and do our tests.
   220       openWindow(function (aWin) {
   221         let wh = new WindowHelper(aWin);
   222         wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
   223         wh.checkPrefCheckbox("history", true);
   224         wh.acceptDialog();
   225         yield promiseHistoryClearedState(uris, true);
   226       });
   227     });
   228   }
   229 ];
   231 // Used as the download database ID for a new download.  Incremented for each
   232 // new download.  See addDownloadWithMinutesAgo().
   233 var gDownloadId = 5555551;
   235 // Index in gAllTests of the test currently being run.  Incremented for each
   236 // test run.  See doNextTest().
   237 var gCurrTest = 0;
   239 var now_uSec = Date.now() * 1000;
   241 ///////////////////////////////////////////////////////////////////////////////
   243 /**
   244  * This wraps the dialog and provides some convenience methods for interacting
   245  * with it.
   246  *
   247  * A warning:  Before you call any function that uses the tree (or any function
   248  * that calls a function that uses the tree), you must set a non-everything
   249  * duration by calling selectDuration().  The dialog does not initialize the
   250  * tree if it does not yet need to be shown.
   251  *
   252  * @param aWin
   253  *        The dialog's nsIDOMWindow
   254  */
   255 function WindowHelper(aWin) {
   256   this.win = aWin;
   257 }
   259 WindowHelper.prototype = {
   260   /**
   261    * "Presses" the dialog's OK button.
   262    */
   263   acceptDialog: function () {
   264     is(this.win.document.documentElement.getButton("accept").disabled, false,
   265        "Dialog's OK button should not be disabled");
   266     this.win.document.documentElement.acceptDialog();
   267   },
   269   /**
   270    * "Presses" the dialog's Cancel button.
   271    */
   272   cancelDialog: function () {
   273     this.win.document.documentElement.cancelDialog();
   274   },
   276   /**
   277    * Ensures that the grippy row is in the right place, tree selection is OK,
   278    * and that the grippy's visible.
   279    *
   280    * @param aMsg
   281    *        Passed to is() when checking grippy location
   282    * @param aExpectedRow
   283    *        The row that the grippy should be at
   284    */
   285   checkGrippy: function (aMsg, aExpectedRow) {
   286     is(this.getGrippyRow(), aExpectedRow, aMsg);
   287     this.checkTreeSelection();
   288     this.ensureGrippyIsVisible();
   289   },
   291   /**
   292    * (Un)checks a history scope checkbox (browser & download history,
   293    * form history, etc.).
   294    *
   295    * @param aPrefName
   296    *        The final portion of the checkbox's privacy.cpd.* preference name
   297    * @param aCheckState
   298    *        True if the checkbox should be checked, false otherwise
   299    */
   300   checkPrefCheckbox: function (aPrefName, aCheckState) {
   301     var pref = "privacy.cpd." + aPrefName;
   302     var cb = this.win.document.querySelectorAll(
   303                "#itemList > [preference='" + pref + "']");
   304     is(cb.length, 1, "found checkbox for " + pref + " preference");
   305     if (cb[0].checked != aCheckState)
   306       cb[0].click();
   307   },
   309   /**
   310    * Ensures that the tree selection is appropriate to the grippy row.  (A
   311    * single, contiguous selection should exist from the first row all the way
   312    * to the grippy.)
   313    */
   314   checkTreeSelection: function () {
   315     let grippyRow = this.getGrippyRow();
   316     let sel = this.getTree().view.selection;
   317     if (grippyRow === 0) {
   318       is(sel.getRangeCount(), 0,
   319          "Grippy row is 0, so no tree selection should exist");
   320     }
   321     else {
   322       is(sel.getRangeCount(), 1,
   323          "Grippy row > 0, so only one tree selection range should exist");
   324       let min = {};
   325       let max = {};
   326       sel.getRangeAt(0, min, max);
   327       is(min.value, 0, "Tree selection should start at first row");
   328       is(max.value, grippyRow - 1,
   329          "Tree selection should end at row before grippy");
   330     }
   331   },
   333   /**
   334    * The grippy should always be visible when it's moved directly.  This method
   335    * ensures that.
   336    */
   337   ensureGrippyIsVisible: function () {
   338     let tbo = this.getTree().treeBoxObject;
   339     let firstVis = tbo.getFirstVisibleRow();
   340     let lastVis = tbo.getLastVisibleRow();
   341     let grippyRow = this.getGrippyRow();
   342     ok(firstVis <= grippyRow && grippyRow <= lastVis,
   343        "Grippy row should be visible; this inequality should be true: " +
   344        firstVis + " <= " + grippyRow + " <= " + lastVis);
   345   },
   347   /**
   348    * @return The dialog's duration dropdown
   349    */
   350   getDurationDropdown: function () {
   351     return this.win.document.getElementById("sanitizeDurationChoice");
   352   },
   354   /**
   355    * @return The grippy row index
   356    */
   357   getGrippyRow: function () {
   358     return this.win.gContiguousSelectionTreeHelper.getGrippyRow();
   359   },
   361   /**
   362    * @return The tree's row count (includes the grippy row)
   363    */
   364   getRowCount: function () {
   365     return this.getTree().view.rowCount;
   366   },
   368   /**
   369    * @return The tree
   370    */
   371   getTree: function () {
   372     return this.win.gContiguousSelectionTreeHelper.tree;
   373   },
   375   /**
   376    * @return True if the "Everything" warning panel is visible (as opposed to
   377    *         the tree)
   378    */
   379   isWarningPanelVisible: function () {
   380     return this.win.document.getElementById("durationDeck").selectedIndex == 1;
   381   },
   383   /**
   384    * @return True if the tree is visible (as opposed to the warning panel)
   385    */
   386   isTreeVisible: function () {
   387     return this.win.document.getElementById("durationDeck").selectedIndex == 0;
   388   },
   390   /**
   391    * Moves the grippy one row at a time in the direction and magnitude specified.
   392    * If aDelta < 0, moves the grippy up; if aDelta > 0, moves it down.
   393    *
   394    * @param aDelta
   395    *        The amount and direction to move
   396    */
   397   moveGrippyBy: function (aDelta) {
   398     if (aDelta === 0)
   399       return;
   400     let key = aDelta < 0 ? "UP" : "DOWN";
   401     let abs = Math.abs(aDelta);
   402     let treechildren = this.getTree().treeBoxObject.treeBody;
   403     treechildren.focus();
   404     for (let i = 0; i < abs; i++) {
   405       EventUtils.sendKey(key);
   406     }
   407   },
   409   /**
   410    * Selects a duration in the duration dropdown.
   411    *
   412    * @param aDurVal
   413    *        One of the Sanitizer.TIMESPAN_* values
   414    */
   415   selectDuration: function (aDurVal) {
   416     this.getDurationDropdown().value = aDurVal;
   417     if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
   418       is(this.isTreeVisible(), false,
   419          "Tree should not be visible for TIMESPAN_EVERYTHING");
   420       is(this.isWarningPanelVisible(), true,
   421          "Warning panel should be visible for TIMESPAN_EVERYTHING");
   422     }
   423     else {
   424       is(this.isTreeVisible(), true,
   425          "Tree should be visible for non-TIMESPAN_EVERYTHING");
   426       is(this.isWarningPanelVisible(), false,
   427          "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
   428     }
   429   }
   430 };
   432 /**
   433  * Adds a download to history.
   434  *
   435  * @param aMinutesAgo
   436  *        The download will be downloaded this many minutes ago
   437  */
   438 function addDownloadWithMinutesAgo(aMinutesAgo) {
   439   let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
   440   let data = {
   441     id:        gDownloadId,
   442     name:      name,
   443     source:   "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
   444     target:    name,
   445     startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
   446     endTime:   now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
   447     state:     Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
   448     currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
   449     guid: "a1bcD23eF4g5"
   450   };
   452   let db = dm.DBConnection;
   453   let stmt = db.createStatement(
   454     "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
   455       "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
   456     "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
   457       ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
   458   try {
   459     for (let prop in data) {
   460       stmt.params[prop] = data[prop];
   461     }
   462     stmt.execute();
   463   }
   464   finally {
   465     stmt.reset();
   466   }
   468   is(downloadExists(gDownloadId), true,
   469      "Sanity check: download " + gDownloadId +
   470      " should exist after creating it");
   472   return gDownloadId++;
   473 }
   475 /**
   476  * Adds a form entry to history.
   477  *
   478  * @param aMinutesAgo
   479  *        The entry will be added this many minutes ago
   480  */
   481 function addFormEntryWithMinutesAgo(aMinutesAgo) {
   482   let name = aMinutesAgo + "-minutes-ago";
   483   formhist.addEntry(name, "dummy");
   485   // Artifically age the entry to the proper vintage.
   486   let db = formhist.DBConnection;
   487   let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
   488   db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
   489                       timestamp +  " WHERE fieldname = '" + name + "'");
   491   is(formhist.nameExists(name), true,
   492      "Sanity check: form entry " + name + " should exist after creating it");
   493   return name;
   494 }
   496 /**
   497  * Removes all history visits, downloads, and form entries.
   498  */
   499 function blankSlate() {
   500   PlacesUtils.bhistory.removeAllPages();
   501   dm.cleanUp();
   502   formhist.removeAllEntries();
   503 }
   505 /**
   506  * Checks to see if the download with the specified ID exists.
   507  *
   508  * @param  aID
   509  *         The ID of the download to check
   510  * @return True if the download exists, false otherwise
   511  */
   512 function downloadExists(aID)
   513 {
   514   let db = dm.DBConnection;
   515   let stmt = db.createStatement(
   516     "SELECT * " +
   517     "FROM moz_downloads " +
   518     "WHERE id = :id"
   519   );
   520   stmt.params.id = aID;
   521   let rows = stmt.executeStep();
   522   stmt.finalize();
   523   return !!rows;
   524 }
   526 /**
   527  * Runs the next test in the gAllTests array.  If all tests have been run,
   528  * finishes the entire suite.
   529  */
   530 function doNextTest() {
   531   if (gAllTests.length <= gCurrTest) {
   532     blankSlate();
   533     waitForAsyncUpdates(finish);
   534   }
   535   else {
   536     let ct = gCurrTest;
   537     gCurrTest++;
   538     gAllTests[ct]();
   539   }
   540 }
   542 /**
   543  * Ensures that the specified downloads are either cleared or not.
   544  *
   545  * @param aDownloadIDs
   546  *        Array of download database IDs
   547  * @param aShouldBeCleared
   548  *        True if each download should be cleared, false otherwise
   549  */
   550 function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
   551   let niceStr = aShouldBeCleared ? "no longer" : "still";
   552   aDownloadIDs.forEach(function (id) {
   553     is(downloadExists(id), !aShouldBeCleared,
   554        "download " + id + " should " + niceStr + " exist");
   555   });
   556 }
   558 /**
   559  * Ensures that the specified form entries are either cleared or not.
   560  *
   561  * @param aFormEntries
   562  *        Array of form entry names
   563  * @param aShouldBeCleared
   564  *        True if each form entry should be cleared, false otherwise
   565  */
   566 function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
   567   let niceStr = aShouldBeCleared ? "no longer" : "still";
   568   aFormEntries.forEach(function (entry) {
   569     is(formhist.nameExists(entry), !aShouldBeCleared,
   570        "form entry " + entry + " should " + niceStr + " exist");
   571   });
   572 }
   574 /**
   575  * Opens the sanitize dialog and runs a callback once it's finished loading.
   576  * 
   577  * @param aOnloadCallback
   578  *        A function that will be called once the dialog has loaded
   579  */
   580 function openWindow(aOnloadCallback) {
   581   function windowObserver(aSubject, aTopic, aData) {
   582     if (aTopic != "domwindowopened")
   583       return;
   585     Services.ww.unregisterNotification(windowObserver);
   586     let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
   587     win.addEventListener("load", function onload(event) {
   588       win.removeEventListener("load", onload, false);
   589       executeSoon(function () {
   590         // Some exceptions that reach here don't reach the test harness, but
   591         // ok()/is() do...
   592         try {
   593           Task.spawn(function() {
   594             aOnloadCallback(win);
   595           }).then(function() {
   596             waitForAsyncUpdates(doNextTest);
   597           });
   598         }
   599         catch (exc) {
   600           win.close();
   601           ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
   602           finish();
   603         }
   604       });
   605     }, false);
   606   }
   607   Services.ww.registerNotification(windowObserver);
   608   Services.ww.openWindow(null,
   609                          "chrome://browser/content/sanitize.xul",
   610                          "Sanitize",
   611                          "chrome,titlebar,dialog,centerscreen,modal",
   612                          null);
   613 }
   615 /**
   616  * Creates a visit time.
   617  *
   618  * @param aMinutesAgo
   619  *        The visit will be visited this many minutes ago
   620  */
   621 function visitTimeForMinutesAgo(aMinutesAgo) {
   622   return now_uSec - (aMinutesAgo * 60 * 1000000);
   623 }
   625 ///////////////////////////////////////////////////////////////////////////////
   627 function test() {
   628   blankSlate();
   629   waitForExplicitFinish();
   630   // Kick off all the tests in the gAllTests array.
   631   waitForAsyncUpdates(doNextTest);
   632 }

mercurial