Bug 1348911 - Added lastAccessed property to tabs.Tab. r?zombie
MozReview-Commit-ID: 4PbCOtuFnki
--- 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;