diff --git a/mobile/android/components/extensions/ext-tabs.js b/mobile/android/components/extensions/ext-tabs.js index 749e3b3e23a6..924d17d3f0cb 100644 --- a/mobile/android/components/extensions/ext-tabs.js +++ b/mobile/android/components/extensions/ext-tabs.js @@ -36,6 +36,11 @@ let tabListener = { let { BrowserApp } = browser.ownerGlobal; let nativeTab = BrowserApp.getTabForBrowser(browser); + // Ignore initial about:blank + if (!request && this.initializingTabs.has(nativeTab)) { + return; + } + // Now we are certain that the first page in the tab was loaded. this.initializingTabs.delete(nativeTab); diff --git a/mobile/android/components/extensions/ext-utils.js b/mobile/android/components/extensions/ext-utils.js index 14c4f112c53a..0fb82b5ea26c 100644 --- a/mobile/android/components/extensions/ext-utils.js +++ b/mobile/android/components/extensions/ext-utils.js @@ -8,6 +8,12 @@ ChromeUtils.defineModuleGetter( "resource://gre/modules/PrivateBrowsingUtils.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "GeckoViewTabBridge", + "resource://gre/modules/GeckoViewTab.jsm" +); + /* globals EventDispatcher */ var { EventDispatcher } = ChromeUtils.import( "resource://gre/modules/Messaging.jsm" @@ -93,6 +99,23 @@ class BrowserProgressListener { } } +const PROGRESS_LISTENER_FLAGS = + Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION; + +class GeckoViewProgressListenerWrapper { + constructor(window, listener) { + this.listener = new BrowserProgressListener( + window.BrowserApp.selectedBrowser, + listener, + PROGRESS_LISTENER_FLAGS + ); + } + + destroy() { + this.listener.destroy(); + } +} + /** * Handles wrapping a tab progress listener in browser-specific * BrowserProgressListener instances, an attaching them to each tab in a given @@ -103,15 +126,12 @@ class BrowserProgressListener { * @param {object} listener * The tab progress listener to wrap. */ -class ProgressListenerWrapper { +class FennecProgressListenerWrapper { constructor(window, listener) { this.window = window; this.listener = listener; this.listeners = new WeakMap(); - this.flags = - Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION; - for (let nativeTab of this.window.BrowserApp.tabs) { this.addBrowserProgressListener(nativeTab.browser); } @@ -178,6 +198,10 @@ class ProgressListenerWrapper { } } +const ProgressListenerWrapper = Services.androidBridge.isFennec + ? FennecProgressListenerWrapper + : GeckoViewProgressListenerWrapper; + class WindowTracker extends WindowTrackerBase { constructor(...args) { super(...args); @@ -189,6 +213,10 @@ class WindowTracker extends WindowTrackerBase { return Services.wm.getMostRecentWindow(WINDOW_TYPE); } + get topNonPBWindow() { + return Services.wm.getMostRecentNonPBWindow(WINDOW_TYPE); + } + isBrowserWindow(window) { let { documentElement } = window.document; return documentElement.getAttribute("windowtype") === WINDOW_TYPE; @@ -255,7 +283,80 @@ global.makeGlobalEvent = function makeGlobalEvent( }).api(); }; -class TabTracker extends TabTrackerBase { +class GeckoViewTabTracker extends TabTrackerBase { + init() { + if (this.initialized) { + return; + } + this.initialized = true; + + windowTracker.addOpenListener(window => { + const nativeTab = window.BrowserApp.selectedTab; + this.emit("tab-created", { nativeTab }); + }); + + windowTracker.addCloseListener(window => { + const nativeTab = window.BrowserApp.selectedTab; + const { windowId, tabId } = this.getBrowserData( + window.BrowserApp.selectedBrowser + ); + this.emit("tab-removed", { + nativeTab, + tabId, + windowId, + // In GeckoView, it is not meaningful to speak of "window closed", because a tab is a window. + // Until we have a meaningful way to group tabs (and close multiple tabs at once), + // let's use isWindowClosing: false + isWindowClosing: false, + }); + }); + } + + getId(nativeTab) { + return nativeTab.id; + } + + getTab(id, default_ = undefined) { + const windowId = GeckoViewTabBridge.tabIdToWindowId(id); + const win = windowTracker.getWindow(windowId, null, false); + + if (win && win.BrowserApp) { + let nativeTab = win.BrowserApp.selectedTab; + if (nativeTab) { + return nativeTab; + } + } + + if (default_ !== undefined) { + return default_; + } + throw new ExtensionError(`Invalid tab ID: ${id}`); + } + + getBrowserData(browser) { + const window = browser.ownerGlobal; + if (!window.BrowserApp) { + return { + tabId: -1, + windowId: -1, + }; + } + return { + tabId: this.getId(window.BrowserApp.selectedTab), + windowId: windowTracker.getId(window), + }; + } + + get activeTab() { + let win = windowTracker.topWindow; + if (win && win.BrowserApp) { + return win.BrowserApp.selectedTab; + } + return null; + } +} + +class FennecTabTracker extends TabTrackerBase { constructor() { super(); @@ -486,7 +587,11 @@ class TabTracker extends TabTrackerBase { } windowTracker = new WindowTracker(); -tabTracker = new TabTracker(); +if (Services.androidBridge.isFennec) { + tabTracker = new FennecTabTracker(); +} else { + tabTracker = new GeckoViewTabTracker(); +} Object.assign(global, { tabTracker, windowTracker }); diff --git a/mobile/android/components/extensions/test/mochitest/mochitest.ini b/mobile/android/components/extensions/test/mochitest/mochitest.ini index e2144c12e2b1..72da3f28ba3b 100644 --- a/mobile/android/components/extensions/test/mochitest/mochitest.ini +++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini @@ -17,21 +17,18 @@ tags = webextensions [test_ext_downloads_saveAs.html] skip-if = !is_fennec # times out [test_ext_tab_runtimeConnect.html] -skip-if = !is_fennec # times out +skip-if = !is_fennec # times out; bug 1534640 webextension url [test_ext_tabs_captureVisibleTab.html] [test_ext_tabs_create.html] -skip-if = !is_fennec # times out; bug 1507167 +skip-if = !is_fennec # times out; bug 1507167; bug 1534640 webextension url [test_ext_tabs_events.html] -skip-if = !is_fennec # times out [test_ext_tabs_executeScript.html] -skip-if = !is_fennec # tabs.query returns empty list [test_ext_tabs_executeScript_bad.html] skip-if = true # Currently fails in emulator runs [test_ext_tabs_executeScript_good.html] [test_ext_tabs_executeScript_no_create.html] -skip-if = !is_fennec # depends on bug 1539144 [test_ext_tabs_executeScript_runAt.html] -skip-if = !is_fennec # tabs.query returns empty list +[test_ext_tabs_get.html] [test_ext_tabs_getCurrent.html] skip-if = !is_fennec # times out [test_ext_tabs_insertCSS.html] @@ -42,10 +39,8 @@ skip-if = !is_fennec # times out [test_ext_tabs_reload_bypass_cache.html] skip-if = !is_fennec # times out [test_ext_tabs_onUpdated.html] -skip-if = !is_fennec # times out [test_ext_tabs_query.html] -skip-if = !is_fennec # tabs.onCreated not working, test uses BrowserApp.addTab. [test_ext_tabs_sendMessage.html] -skip-if = !is_fennec # times out [test_ext_tabs_update_url.html] -skip-if = !is_fennec # tabs.update rejects any call. +skip-if = !is_fennec # bug 1534640 webextension url +[test_ext_webNavigation_onCommitted.html] diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_events.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_events.html index 8bf36ba52825..e214c50cb291 100644 --- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_events.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_events.html @@ -151,6 +151,11 @@ add_task(async function testTabRemovalEvent() { }); add_task(async function testTabActivationEvent() { + if (!SpecialPowers.Services.androidBridge.isFennec) { + // TODO bug 1565536: tabs.onActivated is not supported in GeckoView. + info("skipping testTabActivationEvent"); + return; + } async function background() { function makeExpectable() { let expectation = null, resolver = null; diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html index 706a9eb1c245..d71ce7b88b4d 100644 --- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html @@ -22,6 +22,11 @@ add_task(async function testExecuteScript() { async function background() { try { let [tab] = await browser.tabs.query({active: true, currentWindow: true}); + // TODO bug 1565536: tab.active is broken in GeckoView. + if (!SpecialPowers.Services.androidBridge.isFennec) { + browser.test.assertEq(undefined, tab, "currentWindow's tab is not active (bug 1565536)"); + [tab] = await browser.tabs.query({currentWindow: true}); + } let frames = await browser.webNavigation.getAllFrames({tabId: tab.id}); browser.test.log(`FRAMES: ${frames[1].frameId} ${JSON.stringify(frames)}\n`); diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html index 864d5038034e..18cc1c2d016e 100644 --- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html @@ -48,6 +48,11 @@ add_task(async function testExecuteScript() { try { [tab] = await browser.tabs.query({active: true, currentWindow: true}); + // TODO bug 1565536: tab.active is broken in GeckoView. + if (!SpecialPowers.Services.androidBridge.isFennec) { + browser.test.assertEq(undefined, tab, "currentWindow's tab is not active (bug 1565536)"); + [tab] = await browser.tabs.query({currentWindow: true}); + } let success = false; for (let tries = 0; !success && tries < MAX_TRIES; tries++) { diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_get.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_get.html new file mode 100644 index 000000000000..42129bbdd7f3 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_get.html @@ -0,0 +1,38 @@ + + +
+