1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/* This is a JavaScript module (JSM) to be imported via
6   Components.utils.import() and acts as a singleton.
7   Only the following listed symbols will exposed on import, and only when
8   and where imported. */
9
10const EXPORTED_SYMBOLS = ["BrowserTabs"];
11
12const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13const { Weave } = ChromeUtils.import("resource://services-sync/main.js");
14const { TabStateFlusher } = ChromeUtils.import(
15  "resource:///modules/sessionstore/TabStateFlusher.jsm"
16);
17const { Logger } = ChromeUtils.import("resource://tps/logger.jsm");
18
19// Unfortunately, due to where TPS is run, we can't directly reuse the logic from
20// BrowserTestUtils.jsm. Moreover, we can't resolve the URI it loads the content
21// frame script from ("chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js"),
22// hence the hackiness here and in BrowserTabs.Add.
23Services.mm.loadFrameScript(
24  "data:application/javascript;charset=utf-8," +
25    encodeURIComponent(`
26  Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
27  addEventListener("load", function(event) {
28    let subframe = event.target != content.document;
29    sendAsyncMessage("tps:loadEvent", {subframe: subframe, url: event.target.documentURI});
30  }, true)`),
31  true,
32  true
33);
34
35var BrowserTabs = {
36  /**
37   * Add
38   *
39   * Opens a new tab in the current browser window for the
40   * given uri. Rejects on error.
41   *
42   * @param uri The uri to load in the new tab
43   * @return Promise
44   */
45  async Add(uri) {
46    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
47    let browser = mainWindow.gBrowser;
48    let newtab = browser.addTrustedTab(uri);
49
50    // Wait for the tab to load.
51    await new Promise(resolve => {
52      let mm = browser.ownerGlobal.messageManager;
53      mm.addMessageListener("tps:loadEvent", function onLoad(msg) {
54        mm.removeMessageListener("tps:loadEvent", onLoad);
55        resolve();
56      });
57    });
58
59    browser.selectedTab = newtab;
60    // We might sync before SessionStore is done recording information, so try
61    // and force it to record everything. This is overkill, but effective.
62    await TabStateFlusher.flushWindow(mainWindow);
63  },
64
65  /**
66   * Find
67   *
68   * Finds the specified uri and title in Weave's list of remote tabs
69   * for the specified profile.
70   *
71   * @param uri The uri of the tab to find
72   * @param title The page title of the tab to find
73   * @param profile The profile to search for tabs
74   * @return true if the specified tab could be found, otherwise false
75   */
76  Find(uri, title, profile) {
77    // Find the uri in Weave's list of tabs for the given profile.
78    let tabEngine = Weave.Service.engineManager.get("tabs");
79    for (let client of Weave.Service.clientsEngine.remoteClients) {
80      let tabClient = tabEngine.getClientById(client.id);
81      if (!tabClient || !tabClient.tabs) {
82        continue;
83      }
84      for (let key in tabClient.tabs) {
85        let tab = tabClient.tabs[key];
86        let weaveTabUrl = tab.urlHistory[0];
87        if (uri == weaveTabUrl && profile == client.name) {
88          if (title == undefined || title == tab.title) {
89            return true;
90          }
91        }
92      }
93      Logger.logInfo(
94        `Dumping tabs for ${client.name}...\n` +
95          JSON.stringify(tabClient.tabs, null, 2)
96      );
97    }
98    return false;
99  },
100};
101