summaryrefslogtreecommitdiff
path: root/toolkit/components/places/PlacesRemoteTabsAutocompleteProvider.jsm
blob: c3bc501eb27d75bd783a57fd347bd7605fd87c84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* 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/. */

/*
 * Provides functions to handle remote tabs (ie, tabs known by Sync) in
 * the awesomebar.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["PlacesRemoteTabsAutocompleteProvider"];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
  return Cc["@mozilla.org/weave/service;1"]
           .getService(Ci.nsISupports)
           .wrappedJSObject;
});

XPCOMUtils.defineLazyGetter(this, "Weave", () => {
  try {
    let {Weave} = Cu.import("resource://services-sync/main.js", {});
    return Weave;
  } catch (ex) {
    // The app didn't build Sync.
  }
  return null;
});

// from MDN...
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// Build the in-memory structure we use.
function buildItems() {
  let clients = new Map(); // keyed by client guid, value is client
  let tabs = new Map(); // keyed by string URL, value is {clientId, tab}

  // If Sync isn't initialized (either due to lag at startup or due to no user
  // being signed in), don't reach in to Weave.Service as that may initialize
  // Sync unnecessarily - we'll get an observer notification later when it
  // becomes ready and has synced a list of tabs.
  if (weaveXPCService.ready) {
    let engine = Weave.Service.engineManager.get("tabs");

    for (let [guid, client] of Object.entries(engine.getAllClients())) {
      clients.set(guid, client);
      for (let tab of client.tabs) {
        let url = tab.urlHistory[0];
        tabs.set(url, { clientId: guid, tab });
      }
    }
  }
  return { clients, tabs };
}

// Manage the cache of the items we use.
// The cache itself.
let _items = null;

// Ensure the cache is good.
function ensureItems() {
  if (!_items) {
    _items = buildItems();
  }
  return _items;
}

// A preference used to disable the showing of icons in remote tab records.
const PREF_SHOW_REMOTE_ICONS = "services.sync.syncedTabs.showRemoteIcons";
let showRemoteIcons;

// An observer to invalidate _items and watch for changed prefs.
function observe(subject, topic, data) {
  switch (topic) {
    case "weave:engine:sync:finish":
      if (data == "tabs") {
        // The tabs engine just finished syncing, so may have a different list
        // of tabs then we previously cached.
        _items = null;
      }
      break;

    case "weave:service:start-over":
      // Sync is being reset due to the user disconnecting - we must invalidate
      // the cache so we don't supply tabs from a different user.
      _items = null;
      break;

    case "nsPref:changed":
      if (data == PREF_SHOW_REMOTE_ICONS) {
        showRemoteIcons = Services.prefs.getBoolPref(PREF_SHOW_REMOTE_ICONS, true);
      }
      break;

    default:
      break;
  }
}

Services.obs.addObserver(observe, "weave:engine:sync:finish", false);
Services.obs.addObserver(observe, "weave:service:start-over", false);

// Observe the pref for showing remote icons and prime our bool that reflects its value.
Services.prefs.addObserver(PREF_SHOW_REMOTE_ICONS, observe, false);
observe(null, "nsPref:changed", PREF_SHOW_REMOTE_ICONS);

// This public object is a static singleton.
this.PlacesRemoteTabsAutocompleteProvider = {
  // a promise that resolves with an array of matching remote tabs.
  getMatches(searchString) {
    // If Sync isn't configured we bail early.
    if (Weave === null ||
        !Services.prefs.prefHasUserValue("services.sync.username")) {
      return Promise.resolve([]);
    }

    let re = new RegExp(escapeRegExp(searchString), "i");
    let matches = [];
    let { tabs, clients } = ensureItems();
    for (let [url, { clientId, tab }] of tabs) {
      let title = tab.title;
      if (url.match(re) || (title && title.match(re))) {
        // lookup the client record.
        let client = clients.get(clientId);
        let icon = showRemoteIcons ? tab.icon : null;
        // create the record we return for auto-complete.
        let record = {
          url, title, icon,
          deviceClass: Weave.Service.clientsEngine.isMobile(clientId) ? "mobile" : "desktop",
          deviceName: client.clientName,
        };
        matches.push(record);
      }
    }
    return Promise.resolve(matches);
  },
}