Bug 1348911 - Added lastAccessed property to tabs.Tab. r?zombie draft
authorRedHatter <timothy@idioticdev.com>
Wed, 03 May 2017 13:49:16 -0700
changeset 572166 fac0fddc946317e7ee2fbc2407cfa140ad690640
parent 572160 96605941c0021795376d9c6ec1e458de2fac329e
child 626963 d3dacda65d67258f6387d49c9669fbdd7c33e9d8
push id57012
push userbmo:timothy@idioticdev.com
push dateWed, 03 May 2017 21:25:49 +0000
reviewerszombie
bugs1348911
milestone55.0a1
Bug 1348911 - Added lastAccessed property to tabs.Tab. r?zombie MozReview-Commit-ID: 4PbCOtuFnki
browser/components/extensions/ext-utils.js
browser/components/extensions/schemas/tabs.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_tabs_lastAccessed.js
mobile/android/components/extensions/schemas/tabs.json
mobile/android/components/extensions/test/mochitest/mochitest.ini
mobile/android/components/extensions/test/mochitest/test_ext_tabs_lastAccess.html
toolkit/components/extensions/ExtensionTabs.jsm
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -569,16 +569,17 @@ class Tab extends TabBase {
     let result = {
       sessionId: String(tabData.closedId),
       index: tabData.pos ? tabData.pos : 0,
       windowId: window && windowTracker.getId(window),
       highlighted: false,
       active: false,
       pinned: false,
       incognito: Boolean(tabData.state && tabData.state.isPrivate),
+      lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
     };
 
     if (extension.tabManager.hasTabPermission(tabData)) {
       let entries = tabData.state ? tabData.state.entries : tabData.entries;
       let entry = entries[entries.length - 1];
       result.url = entry.url;
       result.title = entry.title;
       if (tabData.image) {
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -59,16 +59,17 @@
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
           "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted."},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+          "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."},
           "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
           "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -107,16 +107,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_executeScript_good.js]
 [browser_ext_tabs_executeScript_bad.js]
 [browser_ext_tabs_executeScript_multiple.js]
 [browser_ext_tabs_executeScript_no_create.js]
 [browser_ext_tabs_executeScript_runAt.js]
 [browser_ext_tabs_getCurrent.js]
 [browser_ext_tabs_insertCSS.js]
+[browser_ext_tabs_lastAccessed.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_tabs_onUpdated.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_lastAccessed.js
@@ -0,0 +1,25 @@
+"use strict";
+
+add_task(async function testLastAcessed () {
+  let extension = ExtensionTestUtils.loadExtension({
+    background: async function () {
+      let tab1 = await browser.tabs.create({url: "https://example.com/"});
+      let tab2 = await browser.tabs.create({url: "https://example.com/"});
+
+      let now = Date.now();
+      let past = now - 1000 * 5;
+
+      browser.test.assertTrue(past < tab1.lastAccessed < tab2.lastAccessed <= now, 0,
+        "tab.lastAccessed contains a valid timestamp.");
+
+      await browser.tabs.remove([tab1.id, tab2.id]);
+
+      browser.test.notifyPass("tabs.lastAccessed");
+
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("tabs.lastAccessed");
+  await extension.unload();
+});
--- a/mobile/android/components/extensions/schemas/tabs.json
+++ b/mobile/android/components/extensions/schemas/tabs.json
@@ -59,16 +59,17 @@
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
           "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted."},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+          "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."},
           "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
           "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
--- a/mobile/android/components/extensions/test/mochitest/mochitest.ini
+++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini
@@ -14,16 +14,17 @@ tags = webextensions
 
 [test_ext_all_apis.html]
 [test_ext_tab_runtimeConnect.html]
 [test_ext_tabs_captureVisibleTab.html]
 [test_ext_tabs_create.html]
 [test_ext_tabs_events.html]
 [test_ext_tabs_executeScript.html]
 [test_ext_tabs_executeScript_bad.html]
+[test_ext_tabs_lastAccess.html]
 skip-if = true # Currently fails in emulator runs
 [test_ext_tabs_executeScript_good.html]
 [test_ext_tabs_executeScript_no_create.html]
 [test_ext_tabs_executeScript_runAt.html]
 [test_ext_tabs_getCurrent.html]
 [test_ext_tabs_insertCSS.html]
 [test_ext_tabs_reload.html]
 [test_ext_tabs_reload_bypass_cache.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_lastAccess.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Tabs onUpdated Test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(async function testLastAcessed () {
+  let extension = ExtensionTestUtils.loadExtension({
+    background: async function () {
+      let tab1 = await browser.tabs.create({url: "https://example.com/"});
+      let tab2 = await browser.tabs.create({url: "https://example.com/"});
+
+      let now = Date.now();
+      let past = now - 1000 * 5;
+
+      browser.test.assertTrue(past < tab1.lastAccessed < tab2.lastAccessed <= now, 0,
+        "tab.lastAccessed contains a valid timestamp.");
+
+      await browser.tabs.remove([tab1.id, tab2.id]);
+
+      browser.test.notifyPass("tabs.lastAccessed");
+
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("tabs.lastAccessed");
+  await extension.unload();
+});
+</script>
+
+</body>
+</html>
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -260,16 +260,26 @@ class TabBase {
    */
   get favIconUrl() {
     if (this.hasTabPermission) {
       return this._favIconUrl;
     }
   }
 
   /**
+   * @property {integer} lastAccessed
+   *        Returns the last time the tab was accessed as the number of
+   *        milliseconds since epoch.
+   *        @readonly
+   */
+  get lastAccessed() {
+    return this.nativeTab.lastAccessed;
+  }
+
+  /**
    * @property {boolean} audible
    *        Returns true if the tab is currently playing audio, false otherwise.
    *        @readonly
    *        @abstract
    */
   get audible() {
     throw new Error("Not implemented");
   }
@@ -475,16 +485,17 @@ class TabBase {
       windowId: this.windowId,
       highlighted: this.selected,
       active: this.selected,
       pinned: this.pinned,
       status: this.status,
       incognito: this.incognito,
       width: this.width,
       height: this.height,
+      lastAccessed: this.lastAccessed,
       audible: this.audible,
       mutedInfo: this.mutedInfo,
     };
 
     // If the tab has not been fully layed-out yet, fallback to the geometry
     // from a different tab (usually the currently active tab).
     if (fallbackTab && (!result.width || !result.height)) {
       result.width = fallbackTab.width;