1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/* This is a JavaScript module (JSM) to be imported via
6 * Components.utils.import() and acts as a singleton. Only the following
7 * listed symbols will exposed on import, and only when and where imported.
8 */
9
10var EXPORTED_SYMBOLS = ["HistoryEntry", "DumpHistory"];
11
12const { PlacesUtils } = ChromeUtils.import(
13  "resource://gre/modules/PlacesUtils.jsm"
14);
15const { PlacesSyncUtils } = ChromeUtils.import(
16  "resource://gre/modules/PlacesSyncUtils.jsm"
17);
18const { Logger } = ChromeUtils.import("resource://tps/logger.jsm");
19
20var DumpHistory = async function TPS_History__DumpHistory() {
21  let query = PlacesUtils.history.getNewQuery();
22  let options = PlacesUtils.history.getNewQueryOptions();
23  let root = PlacesUtils.history.executeQuery(query, options).root;
24  root.containerOpen = true;
25  Logger.logInfo("\n\ndumping history\n", true);
26  for (var i = 0; i < root.childCount; i++) {
27    let node = root.getChild(i);
28    let uri = node.uri;
29    let guid = await PlacesSyncUtils.history
30      .fetchGuidForURL(uri)
31      .catch(() => "?".repeat(12));
32    let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(uri);
33    for (var visit of curvisits) {
34      Logger.logInfo(
35        `GUID: ${guid}, URI: ${uri}, type=${visit.type}, date=${visit.date}`,
36        true
37      );
38    }
39  }
40  root.containerOpen = false;
41  Logger.logInfo("\nend history dump\n", true);
42};
43
44/**
45 * HistoryEntry object
46 *
47 * Contains methods for manipulating browser history entries.
48 */
49var HistoryEntry = {
50  /**
51   * Add
52   *
53   * Adds visits for a uri to the history database.  Throws on error.
54   *
55   * @param item An object representing one or more visits to a specific uri
56   * @param usSinceEpoch The number of microseconds from Epoch to
57   *        the time the current Crossweave run was started
58   * @return nothing
59   */
60  async Add(item, msSinceEpoch) {
61    Logger.AssertTrue(
62      "visits" in item && "uri" in item,
63      "History entry in test file must have both 'visits' " +
64        "and 'uri' properties"
65    );
66    let place = {
67      url: item.uri,
68      visits: [],
69    };
70    for (let visit of item.visits) {
71      let date = new Date(
72        Math.round(msSinceEpoch + visit.date * 60 * 60 * 1000)
73      );
74      place.visits.push({ date, transition: visit.type });
75    }
76    if ("title" in item) {
77      place.title = item.title;
78    }
79    return PlacesUtils.history.insert(place);
80  },
81
82  /**
83   * Find
84   *
85   * Finds visits for a uri to the history database.  Throws on error.
86   *
87   * @param item An object representing one or more visits to a specific uri
88   * @param usSinceEpoch The number of microseconds from Epoch to
89   *        the time the current Crossweave run was started
90   * @return true if all the visits for the uri are found, otherwise false
91   */
92  async Find(item, msSinceEpoch) {
93    Logger.AssertTrue(
94      "visits" in item && "uri" in item,
95      "History entry in test file must have both 'visits' " +
96        "and 'uri' properties"
97    );
98    let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(item.uri);
99    for (let visit of curvisits) {
100      for (let itemvisit of item.visits) {
101        // Note: in microseconds.
102        let expectedDate =
103          itemvisit.date * 60 * 60 * 1000 * 1000 + msSinceEpoch * 1000;
104        if (visit.type == itemvisit.type) {
105          if (itemvisit.date === undefined || visit.date == expectedDate) {
106            itemvisit.found = true;
107          }
108        }
109      }
110    }
111
112    let all_items_found = true;
113    for (let itemvisit of item.visits) {
114      all_items_found = all_items_found && "found" in itemvisit;
115      Logger.logInfo(
116        `History entry for ${item.uri}, type: ${itemvisit.type}, date: ${itemvisit.date}` +
117          `(${itemvisit.date *
118            60 *
119            60 *
120            1000 *
121            1000}), found = ${!!itemvisit.found}`
122      );
123    }
124    return all_items_found;
125  },
126
127  /**
128   * Delete
129   *
130   * Removes visits from the history database. Throws on error.
131   *
132   * @param item An object representing items to delete
133   * @param usSinceEpoch The number of microseconds from Epoch to
134   *        the time the current Crossweave run was started
135   * @return nothing
136   */
137  async Delete(item, msSinceEpoch) {
138    if ("uri" in item) {
139      let removedAny = await PlacesUtils.history.remove(item.uri);
140      if (!removedAny) {
141        Logger.log("Warning: Removed 0 history visits for uri " + item.uri);
142      }
143    } else if ("host" in item) {
144      await PlacesUtils.history.removeByFilter({ host: item.host });
145    } else if ("begin" in item && "end" in item) {
146      let filter = {
147        beginDate: new Date(msSinceEpoch + item.begin * 60 * 60 * 1000),
148        endDate: new Date(msSinceEpoch + item.end * 60 * 60 * 1000),
149      };
150      let removedAny = await PlacesUtils.history.removeVisitsByFilter(filter);
151      if (!removedAny) {
152        Logger.log(
153          "Warning: Removed 0 history visits with " +
154            JSON.stringify({ item, filter })
155        );
156      }
157    } else {
158      Logger.AssertTrue(
159        false,
160        "invalid entry in delete history " + JSON.stringify(item)
161      );
162    }
163  },
164};
165