Files
tubestation/browser/components/extensions/parent/ext-history.js
Csoregi Natalia 9ef5ee3b2c Backed out 4 changesets (bug 1678618) for node failures. CLOSED TREE
Backed out changeset e714440f6c0d (bug 1678618)
Backed out changeset ae83fda183dd (bug 1678618)
Backed out changeset bef0ea72ded8 (bug 1678618)
Backed out changeset ec96259a3f81 (bug 1678618)
2021-02-15 05:49:54 +02:00

310 lines
9.7 KiB
JavaScript

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
var { normalizeTime } = ExtensionCommon;
let nsINavHistoryService = Ci.nsINavHistoryService;
const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([
["link", nsINavHistoryService.TRANSITION_LINK],
["typed", nsINavHistoryService.TRANSITION_TYPED],
["auto_bookmark", nsINavHistoryService.TRANSITION_BOOKMARK],
["auto_subframe", nsINavHistoryService.TRANSITION_EMBED],
["manual_subframe", nsINavHistoryService.TRANSITION_FRAMED_LINK],
["reload", nsINavHistoryService.TRANSITION_RELOAD],
]);
let TRANSITION_TYPE_TO_TRANSITIONS_MAP = new Map();
for (let [transition, transitionType] of TRANSITION_TO_TRANSITION_TYPES_MAP) {
TRANSITION_TYPE_TO_TRANSITIONS_MAP.set(transitionType, transition);
}
const getTransitionType = transition => {
// cannot set a default value for the transition argument as the framework sets it to null
transition = transition || "link";
let transitionType = TRANSITION_TO_TRANSITION_TYPES_MAP.get(transition);
if (!transitionType) {
throw new Error(
`|${transition}| is not a supported transition for history`
);
}
return transitionType;
};
const getTransition = transitionType => {
return TRANSITION_TYPE_TO_TRANSITIONS_MAP.get(transitionType) || "link";
};
/*
* Converts a nsINavHistoryResultNode into a HistoryItem
*
* https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
*/
const convertNodeToHistoryItem = node => {
return {
id: node.pageGuid,
url: node.uri,
title: node.title,
lastVisitTime: PlacesUtils.toDate(node.time).getTime(),
visitCount: node.accessCount,
};
};
/*
* Converts a nsINavHistoryResultNode into a VisitItem
*
* https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
*/
const convertNodeToVisitItem = node => {
return {
id: node.pageGuid,
visitId: String(node.visitId),
visitTime: PlacesUtils.toDate(node.time).getTime(),
referringVisitId: String(node.fromVisitId),
transition: getTransition(node.visitType),
};
};
/*
* Converts a nsINavHistoryContainerResultNode into an array of objects
*
* https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryContainerResultNode
*/
const convertNavHistoryContainerResultNode = (container, converter) => {
let results = [];
container.containerOpen = true;
for (let i = 0; i < container.childCount; i++) {
let node = container.getChild(i);
results.push(converter(node));
}
container.containerOpen = false;
return results;
};
var _observer;
const getHistoryObserver = () => {
if (!_observer) {
_observer = new (class extends EventEmitter {
onDeleteURI(uri, guid, reason) {
this.emit("visitRemoved", { allHistory: false, urls: [uri.spec] });
}
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
onDeleteVisits(uri, partialRemoval, guid, reason) {
if (!partialRemoval) {
this.emit("visitRemoved", { allHistory: false, urls: [uri.spec] });
}
}
})();
PlacesUtils.history.addObserver(_observer);
}
return _observer;
};
this.history = class extends ExtensionAPI {
getAPI(context) {
return {
history: {
addUrl: function(details) {
let transition, date;
try {
transition = getTransitionType(details.transition);
} catch (error) {
return Promise.reject({ message: error.message });
}
if (details.visitTime) {
date = normalizeTime(details.visitTime);
}
let pageInfo = {
title: details.title,
url: details.url,
visits: [
{
transition,
date,
},
],
};
try {
return PlacesUtils.history.insert(pageInfo).then(() => undefined);
} catch (error) {
return Promise.reject({ message: error.message });
}
},
deleteAll: function() {
return PlacesUtils.history.clear();
},
deleteRange: function(filter) {
let newFilter = {
beginDate: normalizeTime(filter.startTime),
endDate: normalizeTime(filter.endTime),
};
// History.removeVisitsByFilter returns a boolean, but our API should return nothing
return PlacesUtils.history
.removeVisitsByFilter(newFilter)
.then(() => undefined);
},
deleteUrl: function(details) {
let url = details.url;
// History.remove returns a boolean, but our API should return nothing
return PlacesUtils.history.remove(url).then(() => undefined);
},
search: function(query) {
let beginTime =
query.startTime == null
? PlacesUtils.toPRTime(Date.now() - 24 * 60 * 60 * 1000)
: PlacesUtils.toPRTime(normalizeTime(query.startTime));
let endTime =
query.endTime == null
? Number.MAX_VALUE
: PlacesUtils.toPRTime(normalizeTime(query.endTime));
if (beginTime > endTime) {
return Promise.reject({
message: "The startTime cannot be after the endTime",
});
}
let options = PlacesUtils.history.getNewQueryOptions();
options.includeHidden = true;
options.sortingMode = options.SORT_BY_DATE_DESCENDING;
options.maxResults = query.maxResults || 100;
let historyQuery = PlacesUtils.history.getNewQuery();
historyQuery.searchTerms = query.text;
historyQuery.beginTime = beginTime;
historyQuery.endTime = endTime;
let queryResult = PlacesUtils.history.executeQuery(
historyQuery,
options
).root;
let results = convertNavHistoryContainerResultNode(
queryResult,
convertNodeToHistoryItem
);
return Promise.resolve(results);
},
getVisits: function(details) {
let url = details.url;
if (!url) {
return Promise.reject({
message: "A URL must be provided for getVisits",
});
}
let options = PlacesUtils.history.getNewQueryOptions();
options.includeHidden = true;
options.sortingMode = options.SORT_BY_DATE_DESCENDING;
options.resultType = options.RESULTS_AS_VISIT;
let historyQuery = PlacesUtils.history.getNewQuery();
historyQuery.uri = Services.io.newURI(url);
let queryResult = PlacesUtils.history.executeQuery(
historyQuery,
options
).root;
let results = convertNavHistoryContainerResultNode(
queryResult,
convertNodeToVisitItem
);
return Promise.resolve(results);
},
onVisited: new EventManager({
context,
name: "history.onVisited",
register: fire => {
const listener = events => {
for (const event of events) {
const visit = {
id: event.pageGuid,
url: event.url,
title: event.lastKnownTitle || "",
lastVisitTime: event.visitTime,
visitCount: event.visitCount,
typedCount: event.typedCount,
};
fire.sync(visit);
}
};
PlacesUtils.observers.addListener(["page-visited"], listener);
return () => {
PlacesUtils.observers.removeListener(["page-visited"], listener);
};
},
}).api(),
onVisitRemoved: new EventManager({
context,
name: "history.onVisitRemoved",
register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
const historyClearedListener = events => {
fire.sync({ allHistory: true, urls: [] });
};
getHistoryObserver().on("visitRemoved", listener);
PlacesUtils.observers.addListener(
["history-cleared"],
historyClearedListener
);
return () => {
getHistoryObserver().off("visitRemoved", listener);
PlacesUtils.observers.removeListener(
["history-cleared"],
historyClearedListener
);
};
},
}).api(),
onTitleChanged: new EventManager({
context,
name: "history.onTitleChanged",
register: fire => {
const listener = events => {
for (const event of events) {
const titleChanged = {
id: event.pageGuid,
url: event.url,
title: event.title,
};
fire.sync(titleChanged);
}
};
PlacesUtils.observers.addListener(["page-title-changed"], listener);
return () => {
PlacesUtils.observers.removeListener(
["page-title-changed"],
listener
);
};
},
}).api(),
},
};
}
};