DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (dcc6d7a0dc00)

VCS Links

Line Code
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
/* 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");
Cu.import("resource://services-sync/main.js");

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, "\\$&");
}

// 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] in Iterator(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;
}

// An observer to invalidate _items.
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;

    default:
      break;
  }
}

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

// 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 (!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);
        // create the record we return for auto-complete.
        let record = {
          url, title,
          icon: tab.icon,
          deviceClass: Weave.Service.clientsEngine.isMobile(clientId) ? "mobile" : "desktop",
          deviceName: client.clientName,
        };
        matches.push(record);
      }
    }
    return Promise.resolve(matches);
  },
}