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