Bug 1265836 - Part 4: Implement browser.history.addUrl, r?aswan
MozReview-Commit-ID: CBcKLvRLj3w
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -9,16 +9,35 @@ XPCOMUtils.defineLazyGetter(this, "Histo
return PlacesUtils.history;
});
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
normalizeTime,
} = ExtensionUtils;
+let historySvc = Ci.nsINavHistoryService;
+const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([
+ ["link", historySvc.TRANSITION_LINK],
+ ["typed", historySvc.TRANSITION_TYPED],
+ ["auto_bookmark", historySvc.TRANSITION_BOOKMARK],
+ ["auto_subframe", historySvc.TRANSITION_EMBED],
+ ["manual_subframe", historySvc.TRANSITION_FRAMED_LINK],
+]);
+
+function 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;
+}
+
/*
* Converts a nsINavHistoryResultNode into a plain object
*
* https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
*/
function convertNavHistoryResultNode(node) {
return {
id: node.pageGuid,
@@ -43,16 +62,42 @@ function convertNavHistoryContainerResul
}
container.containerOpen = false;
return results;
}
extensions.registerSchemaAPI("history", "history", (extension, 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 History.insert(pageInfo).then(() => undefined);
+ } catch (error) {
+ return Promise.reject({message: error.message});
+ }
+ },
deleteAll: function() {
return History.clear();
},
deleteRange: function(filter) {
let newFilter = {
beginDate: normalizeTime(filter.startTime),
endDate: normalizeTime(filter.endTime),
};
--- a/browser/components/extensions/schemas/history.json
+++ b/browser/components/extensions/schemas/history.json
@@ -182,28 +182,42 @@
}
}
]
}
]
},
{
"name": "addUrl",
- "unsupported": true,
"type": "function",
- "description": "Adds a URL to the history at the current time with a $(topic:transition-types)[transition type] of \"link\".",
+ "description": "Adds a URL to the history with a default visitTime of the current time and a default $(topic:transition-types)[transition type] of \"link\".",
"async": "callback",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"url": {
"type": "string",
- "description": "The URL to add."
+ "description": "The URL to add. Must be a valid URL that can be added to history."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The title of the page."
+ },
+ "transition": {
+ "$ref": "TransitionType",
+ "optional": true,
+ "description": "The $(topic:transition-types)[transition type] for this visit from its referrer."
+ },
+ "visitTime": {
+ "$ref": "HistoryTime",
+ "optional": true,
+ "description": "The date when this visit occurred."
}
}
},
{
"name": "callback",
"type": "function",
"optional": true,
"parameters": []
--- a/browser/components/extensions/test/browser/browser_ext_history.js
+++ b/browser/components/extensions/test/browser/browser_ext_history.js
@@ -1,14 +1,18 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
+ "resource://gre/modules/ExtensionUtils.jsm");
add_task(function* test_delete() {
function background() {
browser.test.onMessage.addListener((msg, arg) => {
if (msg === "delete-url") {
browser.history.deleteUrl({url: arg}).then(result => {
browser.test.assertEq(undefined, result, "browser.history.deleteUrl returns nothing");
browser.test.sendMessage("url-deleted");
@@ -25,17 +29,16 @@ add_task(function* test_delete() {
});
}
});
browser.test.sendMessage("ready");
}
const REFERENCE_DATE = new Date(1999, 9, 9, 9, 9);
- let {PlacesUtils} = Cu.import("resource://gre/modules/PlacesUtils.jsm", {});
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["history"],
},
background: `(${background})()`,
});
@@ -186,8 +189,91 @@ add_task(function* test_search() {
results = yield extension.awaitMessage("max-results-search");
is(results.length, 1, "history.search returned 1 result");
checkResult(results, DOUBLE_VISIT_URL, 2);
yield extension.awaitFinish("search");
yield extension.unload();
yield PlacesTestUtils.clearHistory();
});
+
+add_task(function* test_add_url() {
+ function background() {
+ const TEST_DOMAIN = "http://example.com/";
+
+ browser.test.onMessage.addListener((msg, testData) => {
+ let [details, type] = testData;
+ details.url = details.url || `${TEST_DOMAIN}${type}`;
+ if (msg === "add-url") {
+ details.title = `Title for ${type}`;
+ browser.history.addUrl(details).then(() => {
+ return browser.history.search({text: details.url});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "1 result found when searching for added URL");
+ browser.test.sendMessage("url-added", {details, result: results[0]});
+ });
+ } else if (msg === "expect-failure") {
+ let expectedMsg = testData[2];
+ browser.history.addUrl(details).then(() => {
+ browser.test.fail(`Expected error thrown for ${type}`);
+ }, error => {
+ browser.test.assertTrue(
+ error.message.includes(expectedMsg),
+ `"Expected error thrown when trying to add a URL with ${type}`
+ );
+ browser.test.sendMessage("add-failed");
+ });
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let addTestData = [
+ [{}, "default"],
+ [{visitTime: new Date()}, "with_date"],
+ [{visitTime: Date.now()}, "with_ms_number"],
+ [{visitTime: Date.now().toString()}, "with_ms_string"],
+ [{visitTime: new Date().toISOString()}, "with_iso_string"],
+ [{transition: "typed"}, "valid_transition"],
+ ];
+
+ let failTestData = [
+ [{transition: "generated"}, "an invalid transition", "|generated| is not a supported transition for history"],
+ [{visitTime: Date.now() + 1000000}, "a future date", "cannot be a future date"],
+ [{url: "about.config"}, "an invalid url", "about.config is not a valid URL"],
+ ];
+
+ function* checkUrl(results) {
+ ok(yield PlacesTestUtils.isPageInDB(results.details.url), `${results.details.url} found in history database`);
+ ok(PlacesUtils.isValidGuid(results.result.id), "URL was added with a valid id");
+ is(results.result.title, results.details.title, "URL was added with the correct title");
+ if (results.details.visitTime) {
+ is(results.result.lastVisitTime,
+ Number(ExtensionUtils.normalizeTime(results.details.visitTime)),
+ "URL was added with the correct date");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})()`,
+ });
+
+ yield PlacesTestUtils.clearHistory();
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ for (let data of addTestData) {
+ extension.sendMessage("add-url", data);
+ let results = yield extension.awaitMessage("url-added");
+ yield checkUrl(results);
+ }
+
+ for (let data of failTestData) {
+ extension.sendMessage("expect-failure", data);
+ yield extension.awaitMessage("add-failed");
+ }
+
+ yield extension.unload();
+});