Bug 1265836 - Part 4: Implement browser.history.addUrl. r=aswan
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 19 May 2016 21:55:37 -0400
changeset 341214 463ee98e93e98715614dfc2229f88aa1c31417a6
parent 341213 d328f6474fcb7a701fafd4a58435674c53a29aeb
child 341215 98d69e7bb6090115e70bd86234a92c3a313a5d1c
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [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
@@ -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();
+});