From a6e05ab37ab2af998e0735b1ca47ccc77cec5612 Mon Sep 17 00:00:00 2001 From: "jonathandicarlo@jonathan-dicarlos-macbook-pro.local" Date: Tue, 17 Mar 2009 17:57:53 -0700 Subject: [PATCH] TabTracker now keeps track of when each tab was most recently used; tabStore wraps this data for sync, and fennec UI uses it to sort incoming tabs by most-recently-used date. This fixes 481326. --- services/sync/modules/engines/tabs.js | 162 +++++++++++++++------ services/sync/modules/type_records/tabs.js | 6 +- 2 files changed, 121 insertions(+), 47 deletions(-) diff --git a/services/sync/modules/engines/tabs.js b/services/sync/modules/engines/tabs.js index b65efbace7f..0b13deebc68 100644 --- a/services/sync/modules/engines/tabs.js +++ b/services/sync/modules/engines/tabs.js @@ -41,6 +41,8 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; +const TAB_TIME_ATTR = "weave.tabEngine.lastUsed.timeStamp"; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://weave/util.js"); Cu.import("resource://weave/async.js"); @@ -190,6 +192,13 @@ TabStore.prototype = { return this._sessionStore; }, + get _windowMediator() { + let wm = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator); + this.__defineGetter__("_windowMediator", function() { return wm;}); + return this._windowMediator; + }, + get _json() { let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); this.__defineGetter__("_json", function() {return json;}); @@ -211,38 +220,46 @@ TabStore.prototype = { }, _addFirefoxTabsToRecord: function TabStore__addFirefoxTabs(record) { - let session = this._json.decode(this._sessionStore.getBrowserState()); - for (let i = 0; i < session.windows.length; i++) { - let window = session.windows[i]; - /* For some reason, session store uses one-based array index - references, (f.e. in the "selectedWindow" and each tab's - "index" properties), so we convert them to and from - JavaScript's zero-based indexes as needed. */ - let windowID = i + 1; - - for (let j = 0; j < window.tabs.length; j++) { - let tab = window.tabs[j]; - //this._sessionStore.getTabState(tab)); + // Iterate through each tab of each window + let enumerator = this._windowMediator.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + let window = enumerator.getNext(); + let tabContainer = window.getBrowser().tabContainer; + for each (let tabChild in tabContainer.childNodes) { + if (!tabChild.QueryInterface) + continue; + let tab = tabChild.QueryInterface(Ci.nsIDOMNode); + if (!tab) + continue; + let tabState = this._json.decode(this._sessionStore.getTabState(tab)); // Skip empty (i.e. just-opened, no history yet) tabs: - if (tab.entries.length == 0) + if (tabState.entries.length == 0) continue; - let currentPage = tab.entries[tab.entries.length - 1]; + + // Get the time the tab was last used + let lastUsedTimestamp = tab.getAttribute(TAB_TIME_ATTR); + + // Get title of current page + let currentPage = tabState.entries[tabState.entries.length - 1]; /* TODO not always accurate -- if you've hit Back in this tab, * then the current page might not be the last entry. Deal - * with this later. - */ - this._log.debug("Wrapping a tab with title " + currentPage.title); + * with this later. */ + + // Get url history let urlHistory = []; // Include URLs in reverse order; max out at 10, and skip nulls. - for (let i = tab.entries.length -1; i >= 0; i--) { - let entry = tab.entries[i]; + for (let i = tabState.entries.length -1; i >= 0; i--) { + let entry = tabState.entries[i]; if (entry && entry.url) urlHistory.push(entry.url); if (urlHistory.length >= 10) break; } - // TODO add last-visited date for this tab... but how? - record.addTab(currentPage.title, urlHistory); + + // add tab to record + this._log.debug("Wrapping a tab with title " + currentPage.title); + this._log.debug("And timestamp " + lastUsedTimestamp); + record.addTab(currentPage.title, urlHistory, lastUsedTimestamp); } } }, @@ -339,9 +356,6 @@ TabStore.prototype = { }; -/* TODO let's have TabTracker keep track of open/close switch events - * and maintain most-recently used date of each tab... - */ function TabTracker() { this._TabTracker_init(); @@ -356,41 +370,96 @@ TabTracker.prototype = { _TabTracker_init: function TabTracker__init() { this._init(); - // Register me as an observer!! Listen for tabs opening and closing: - // TODO We need to also register with any windows that are ALREDY - // open. On Fennec maybe try to get this from getBrowser(), which is - // defined differently but should still exist... + // TODO Figure out how this will work on Fennec. + + // Register as an observer so we can catch windows opening and closing: var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] .getService(Ci.nsIWindowWatcher); ww.registerNotification(this); + + /* Also directly register the listeners for any browser window alread + * open: */ + let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + let enumerator = wm.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + this._registerListenersForWindow(enumerator.getNext()); + } + }, + + _registerListenersForWindow: function TabTracker__registerListen(window) { + if (! window.getBrowser) { + return; + } + let browser = window.getBrowser(); + if (! browser.tabContainer) { + return; + } + //this._log.trace("Registering tab listeners in new window.\n"); + let container = browser.tabContainer; + container.addEventListener("TabOpen", this.onTabOpened, false); + container.addEventListener("TabClose", this.onTabClosed, false); + container.addEventListener("TabSelect", this.onTabSelected, false); + }, + + _unRegisterListenersForWindow: function TabTracker__unregister(window) { + if (! window.getBrowser) { + return; + } + let browser = window.getBrowser(); + if (! browser.tabContainer) { + return; + } + let container = browser.tabContainer; + container.removeEventListener("TabOpen", this.onTabOpened, false); + container.removeEventListener("TabClose", this.onTabClosed, false); + container.removeEventListener("TabSelect", this.onTabSelected, false); }, observe: function TabTracker_observe(aSubject, aTopic, aData) { - this._log.trace("Spotted window open/close"); + /* Called when a window opens or closes. Make sure that every + * window has the appropriate listeners registered. */ let window = aSubject.QueryInterface(Ci.nsIDOMWindow); - // Ignore windows that don't have tabContainers. - // TODO: Fennec windows don't have tabContainers, but we still want - // to register an observer in them. - if (! window.getBrowser) - return; - let browser = window.getBrowser(); - if (! browser.tabContainer) - return; - let container = browser.tabContainer; + // TODO figure out how this will work in Fennec. if (aTopic == "domwindowopened") { - container.addEventListener("TabOpen", this.onTabChanged, false); - container.addEventListener("TabClose", this.onTabChanged, false); + this._registerListenersForWindow(window); } else if (aTopic == "domwindowclosed") { - container.removeEventListener("TabOpen", this.onTabChanged, false); - container.removeEventListener("TabClose", this.onTabChanged, false); + this._unRegisterListenersForWindow(window); } - // TODO }, - onTabChanged: function TabTracker_onTabChanged(event) { - this._score += 10; // meh? meh. + onTabOpened: function TabTracker_onTabOpened(event) { + // Store a timestamp in the tab to track when it was last used + //this._log.trace("Tab opened.\n"); + /*if (Cc["@mozilla.org/browser/sessionstore;1"]) { + let ss = Cc["@mozilla.org/browser/sessionstore;1"] + .getService(Ci.nsISessionStore); + ss.setTabValue(event.target, TAB_TIME_ATTR, event.timeStamp); + }*/ + event.target.setAttribute(TAB_TIME_ATTR, event.timeStamp); + //this._log.debug("Tab timestamp set to " + event.target.getAttribute(TAB_TIME_ATTR) + "\n"); + this._score += 50; }, + onTabClosed: function TabTracker_onTabSelected(event) { + //this._log.trace("Tab closed.\n"); + this._score += 10; + }, + + onTabSelected: function TabTracker_onTabSelected(event) { + // Update the tab's timestamp + //this._log.trace("Tab selected.\n"); + /*if (Cc["@mozilla.org/browser/sessionstore;1"]) { + let ss = Cc["@mozilla.org/browser/sessionstore;1"] + .getService(Ci.nsISessionStore); + ss.setTabValue(event.target, TAB_TIME_ATTR, event.timeStamp); + }*/ + event.target.setAttribute(TAB_TIME_ATTR, event.timeStamp); + //this._log.debug("Tab timestamp set to " + event.target.getAttribute(TAB_TIME_ATTR) + "\n"); + this._score += 10; + }, + // TODO: Also listen for tabs loading new content? + get changedIDs() { // The record for my own client is always the only changed record. let obj = {}; @@ -400,6 +469,9 @@ TabTracker.prototype = { // TODO this hard-coded score is a hack; replace with maybe +25 or +35 // per tab open event. + // Actually maybe it should just stay this way? Otherwise we're missing + // something really important which is laod-page-in-tab events, which are + // a powerful motivation to sync get score() { return 100; } diff --git a/services/sync/modules/type_records/tabs.js b/services/sync/modules/type_records/tabs.js index 5ad165d375b..4b4d1188c1b 100644 --- a/services/sync/modules/type_records/tabs.js +++ b/services/sync/modules/type_records/tabs.js @@ -63,10 +63,12 @@ TabSetRecord.prototype = { }; }, - addTab: function TabSetRecord_addTab(title, urlHistory) { + addTab: function TabSetRecord_addTab(title, urlHistory, lastUsed) { if (!this.cleartext.tabs) this.cleartext.tabs = []; - this.cleartext.tabs.push( {title: title, urlHistory: urlHistory }); + this.cleartext.tabs.push( {title: title, + urlHistory: urlHistory, + lastUsed: lastUsed}); }, getAllTabs: function TabSetRecord_getAllTabs() {