summaryrefslogtreecommitdiffstats
path: root/services/sync/modules/SyncedTabs.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/modules/SyncedTabs.jsm')
-rw-r--r--services/sync/modules/SyncedTabs.jsm301
1 files changed, 0 insertions, 301 deletions
diff --git a/services/sync/modules/SyncedTabs.jsm b/services/sync/modules/SyncedTabs.jsm
deleted file mode 100644
index 1a69e3564..000000000
--- a/services/sync/modules/SyncedTabs.jsm
+++ /dev/null
@@ -1,301 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["SyncedTabs"];
-
-
-const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
-Cu.import("resource://services-sync/main.js");
-Cu.import("resource://gre/modules/Preferences.jsm");
-
-// The Sync XPCOM service
-XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
- return Cc["@mozilla.org/weave/service;1"]
- .getService(Ci.nsISupports)
- .wrappedJSObject;
-});
-
-// from MDN...
-function escapeRegExp(string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-}
-
-// A topic we fire whenever we have new tabs available. This might be due
-// to a request made by this module to refresh the tab list, or as the result
-// of a regularly scheduled sync. The intent is that consumers just listen
-// for this notification and update their UI in response.
-const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";
-
-// The interval, in seconds, before which we consider the existing list
-// of tabs "fresh enough" and don't force a new sync.
-const TABS_FRESH_ENOUGH_INTERVAL = 30;
-
-let log = Log.repository.getLogger("Sync.RemoteTabs");
-// A new scope to do the logging thang...
-(function() {
- let level = Preferences.get("services.sync.log.logger.tabs");
- if (level) {
- let appender = new Log.DumpAppender();
- log.level = appender.level = Log.Level[level] || Log.Level.Debug;
- log.addAppender(appender);
- }
-})();
-
-
-// A private singleton that does the work.
-let SyncedTabsInternal = {
- /* Make a "tab" record. Returns a promise */
- _makeTab: Task.async(function* (client, tab, url, showRemoteIcons) {
- let icon;
- if (showRemoteIcons) {
- icon = tab.icon;
- }
- if (!icon) {
- try {
- icon = (yield PlacesUtils.promiseFaviconLinkUrl(url)).spec;
- } catch (ex) { /* no favicon avaiable */ }
- }
- if (!icon) {
- icon = "";
- }
- return {
- type: "tab",
- title: tab.title || url,
- url,
- icon,
- client: client.id,
- lastUsed: tab.lastUsed,
- };
- }),
-
- /* Make a "client" record. Returns a promise for consistency with _makeTab */
- _makeClient: Task.async(function* (client) {
- return {
- id: client.id,
- type: "client",
- name: Weave.Service.clientsEngine.getClientName(client.id),
- isMobile: Weave.Service.clientsEngine.isMobile(client.id),
- lastModified: client.lastModified * 1000, // sec to ms
- tabs: []
- };
- }),
-
- _tabMatchesFilter(tab, filter) {
- let reFilter = new RegExp(escapeRegExp(filter), "i");
- return tab.url.match(reFilter) || tab.title.match(reFilter);
- },
-
- getTabClients: Task.async(function* (filter) {
- log.info("Generating tab list with filter", filter);
- let result = [];
-
- // If Sync isn't ready, don't try and get anything.
- if (!weaveXPCService.ready) {
- log.debug("Sync isn't yet ready, so returning an empty tab list");
- return result;
- }
-
- // A boolean that controls whether we should show the icon from the remote tab.
- const showRemoteIcons = Preferences.get("services.sync.syncedTabs.showRemoteIcons", true);
-
- let engine = Weave.Service.engineManager.get("tabs");
-
- let seenURLs = new Set();
- let parentIndex = 0;
- let ntabs = 0;
-
- for (let [guid, client] of Object.entries(engine.getAllClients())) {
- if (!Weave.Service.clientsEngine.remoteClientExists(client.id)) {
- continue;
- }
- let clientRepr = yield this._makeClient(client);
- log.debug("Processing client", clientRepr);
-
- for (let tab of client.tabs) {
- let url = tab.urlHistory[0];
- log.debug("remote tab", url);
- // Note there are some issues with tracking "seen" tabs, including:
- // * We really can't return the entire urlHistory record as we are
- // only checking the first entry - others might be different.
- // * We don't update the |lastUsed| timestamp to reflect the
- // most-recently-seen time.
- // In a followup we should consider simply dropping this |seenUrls|
- // check and return duplicate records - it seems the user will be more
- // confused by tabs not showing up on a device (because it was detected
- // as a dupe so it only appears on a different device) than being
- // confused by seeing the same tab on different clients.
- if (!url || seenURLs.has(url)) {
- continue;
- }
- let tabRepr = yield this._makeTab(client, tab, url, showRemoteIcons);
- if (filter && !this._tabMatchesFilter(tabRepr, filter)) {
- continue;
- }
- seenURLs.add(url);
- clientRepr.tabs.push(tabRepr);
- }
- // We return all clients, even those without tabs - the consumer should
- // filter it if they care.
- ntabs += clientRepr.tabs.length;
- result.push(clientRepr);
- }
- log.info(`Final tab list has ${result.length} clients with ${ntabs} tabs.`);
- return result;
- }),
-
- syncTabs(force) {
- if (!force) {
- // Don't bother refetching tabs if we already did so recently
- let lastFetch = Preferences.get("services.sync.lastTabFetch", 0);
- let now = Math.floor(Date.now() / 1000);
- if (now - lastFetch < TABS_FRESH_ENOUGH_INTERVAL) {
- log.info("_refetchTabs was done recently, do not doing it again");
- return Promise.resolve(false);
- }
- }
-
- // If Sync isn't configured don't try and sync, else we will get reports
- // of a login failure.
- if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
- log.info("Sync client is not configured, so not attempting a tab sync");
- return Promise.resolve(false);
- }
- // Ask Sync to just do the tabs engine if it can.
- // Sync is currently synchronous, so do it after an event-loop spin to help
- // keep the UI responsive.
- return new Promise((resolve, reject) => {
- Services.tm.currentThread.dispatch(() => {
- try {
- log.info("Doing a tab sync.");
- Weave.Service.sync(["tabs"]);
- resolve(true);
- } catch (ex) {
- log.error("Sync failed", ex);
- reject(ex);
- };
- }, Ci.nsIThread.DISPATCH_NORMAL);
- });
- },
-
- observe(subject, topic, data) {
- log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
- switch (topic) {
- case "weave:engine:sync:finish":
- if (data != "tabs") {
- return;
- }
- // The tabs engine just finished syncing
- // Set our lastTabFetch pref here so it tracks both explicit sync calls
- // and normally scheduled ones.
- Preferences.set("services.sync.lastTabFetch", Math.floor(Date.now() / 1000));
- Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null);
- break;
- case "weave:service:start-over":
- // start-over needs to notify so consumers find no tabs.
- Preferences.reset("services.sync.lastTabFetch");
- Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null);
- break;
- case "nsPref:changed":
- Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED, null);
- break;
- default:
- break;
- }
- },
-
- // Returns true if Sync is configured to Sync tabs, false otherwise
- get isConfiguredToSyncTabs() {
- if (!weaveXPCService.ready) {
- log.debug("Sync isn't yet ready; assuming tab engine is enabled");
- return true;
- }
-
- let engine = Weave.Service.engineManager.get("tabs");
- return engine && engine.enabled;
- },
-
- get hasSyncedThisSession() {
- let engine = Weave.Service.engineManager.get("tabs");
- return engine && engine.hasSyncedThisSession;
- },
-};
-
-Services.obs.addObserver(SyncedTabsInternal, "weave:engine:sync:finish", false);
-Services.obs.addObserver(SyncedTabsInternal, "weave:service:start-over", false);
-// Observe the pref the indicates the state of the tabs engine has changed.
-// This will force consumers to re-evaluate the state of sync and update
-// accordingly.
-Services.prefs.addObserver("services.sync.engine.tabs", SyncedTabsInternal, false);
-
-// The public interface.
-this.SyncedTabs = {
- // A mock-point for tests.
- _internal: SyncedTabsInternal,
-
- // We make the topic for the observer notification public.
- TOPIC_TABS_CHANGED,
-
- // Returns true if Sync is configured to Sync tabs, false otherwise
- get isConfiguredToSyncTabs() {
- return this._internal.isConfiguredToSyncTabs;
- },
-
- // Returns true if a tab sync has completed once this session. If this
- // returns false, then getting back no clients/tabs possibly just means we
- // are waiting for that first sync to complete.
- get hasSyncedThisSession() {
- return this._internal.hasSyncedThisSession;
- },
-
- // Return a promise that resolves with an array of client records, each with
- // a .tabs array. Note that part of the contract for this module is that the
- // returned objects are not shared between invocations, so callers are free
- // to mutate the returned objects (eg, sort, truncate) however they see fit.
- getTabClients(query) {
- return this._internal.getTabClients(query);
- },
-
- // Starts a background request to start syncing tabs. Returns a promise that
- // resolves when the sync is complete, but there's no resolved value -
- // callers should be listening for TOPIC_TABS_CHANGED.
- // If |force| is true we always sync. If false, we only sync if the most
- // recent sync wasn't "recently".
- syncTabs(force) {
- return this._internal.syncTabs(force);
- },
-
- sortTabClientsByLastUsed(clients, maxTabs = Infinity) {
- // First sort and filter the list of tabs for each client. Note that
- // this module promises that the objects it returns are never
- // shared, so we are free to mutate those objects directly.
- for (let client of clients) {
- let tabs = client.tabs;
- tabs.sort((a, b) => b.lastUsed - a.lastUsed);
- if (Number.isFinite(maxTabs)) {
- client.tabs = tabs.slice(0, maxTabs);
- }
- }
- // Now sort the clients - the clients are sorted in the order of the
- // most recent tab for that client (ie, it is important the tabs for
- // each client are already sorted.)
- clients.sort((a, b) => {
- if (a.tabs.length == 0) {
- return 1; // b comes first.
- }
- if (b.tabs.length == 0) {
- return -1; // a comes first.
- }
- return b.tabs[0].lastUsed - a.tabs[0].lastUsed;
- });
- },
-};
-