Bug 1265836 - Part 4: Implement browser.history.addUrl. r=aswan
☠☠ backed out by 3790e961ef61 ☠ ☠
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 19 May 2016 21:55:37 -0400
changeset 338120 1151e3a09e77a29b6089657df1b3e395430480e9
parent 338119 548660e408d00c05ece753fdc6a29621226aba8a
child 338121 3e2d76bd1f08108fc90859b51366efc3c8fa6c5c
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1265836
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1265836 - Part 4: Implement browser.history.addUrl. r=aswan MozReview-Commit-ID: CBcKLvRLj3w
browser/components/extensions/ext-history.js
browser/components/extensions/schemas/history.json
browser/components/extensions/test/browser/browser_ext_history.js
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -1,24 +1,40 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-XPCOMUtils.defineLazyGetter(this, "History", () => {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-  return PlacesUtils.history;
-});
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+let History = PlacesUtils.history;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 const {
   normalizeTime,
 } = ExtensionUtils;
 
+const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([
+  ["link", History.TRANSITION_LINK],
+  ["typed", History.TRANSITION_TYPED],
+  ["auto_bookmark", History.TRANSITION_BOOKMARK],
+  ["auto_subframe", History.TRANSITION_EMBED],
+  ["manual_subframe", History.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 +59,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();
+});