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 @@ + + + + Tabs get Test + + + + + + + + + + + diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_onUpdated.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_onUpdated.html index d7cb9de60684..eab242a6e969 100644 --- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_onUpdated.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_onUpdated.html @@ -27,9 +27,18 @@ add_task(async function test_onUpdated() { let pageURL = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/context_tabs_onUpdated_page.html"; let expectedSequence = [ + {status: "loading"}, {status: "loading", url: pageURL}, {status: "complete"}, ]; + + if (SpecialPowers.Services.androidBridge.isFennec) { + // Fennec does not fire initial loading event - Firefox and GeckoView does + expectedSequence.shift(); + } else { + // GeckoView always fire completed status for about:blank for new tabs + expectedSequence.unshift({status: "complete", url: "about:blank"}); + } let collectedSequence = []; let tabId; diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_query.html b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_query.html index ebcd961ccfd5..45a5b237cb0b 100644 --- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_query.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_query.html @@ -16,6 +16,11 @@ var {BrowserActions} = SpecialPowers.Cu.import("resource://gre/modules/BrowserAc var {Services} = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {}); add_task(async function test_query_highlighted() { + if (!SpecialPowers.Services.androidBridge.isFennec) { + // GeckoView does not support extension popups + info('skipping test_query_highlighted'); + return; + } let extension = ExtensionTestUtils.loadExtension({ manifest: { "permissions": ["tabs"], @@ -100,11 +105,10 @@ add_task(async function test_query_index() { }, }); - const {BrowserApp} = Services.wm.getMostRecentWindow("navigator:browser"); await extension.startup(); - let tab = BrowserApp.addTab("http://example.com/"); + const win = window.open("http://example.com"); await extension.awaitFinish("tabs.query"); - BrowserApp.closeTab(tab); + win.close(); await extension.unload(); }); diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_webNavigation_onCommitted.html b/mobile/android/components/extensions/test/mochitest/test_ext_webNavigation_onCommitted.html new file mode 100644 index 000000000000..02acf312ed0d --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/test_ext_webNavigation_onCommitted.html @@ -0,0 +1,49 @@ + + + + WebNavigation onCommitted Test + + + + + + + + + + + diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java index 168462863390..f31ebb5f1112 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java @@ -198,7 +198,7 @@ public class TestRunnerActivity extends Activity { @Override public GeckoResult onCloseTab(WebExtension source, GeckoSession session) { closeSession(session); - return GeckoResult.ALLOW; + return GeckoResult.fromValue(AllowOrDeny.ALLOW); } }); sRuntime.setDelegate(() -> { diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java index ab1a00a9096e..6da8f952dbb4 100644 --- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java @@ -171,7 +171,7 @@ public class GeckoViewActivity extends AppCompatActivity { public GeckoResult onCloseTab(WebExtension source, GeckoSession session) { TabSession tabSession = mTabSessionManager.getSession(session); closeTab(tabSession); - return GeckoResult.ALLOW; + return GeckoResult.fromValue(AllowOrDeny.ALLOW); } }); } diff --git a/mobile/android/modules/geckoview/GeckoViewTab.jsm b/mobile/android/modules/geckoview/GeckoViewTab.jsm index e0596e76ddff..c9f56e3190f3 100644 --- a/mobile/android/modules/geckoview/GeckoViewTab.jsm +++ b/mobile/android/modules/geckoview/GeckoViewTab.jsm @@ -65,15 +65,6 @@ class BrowserAppShim { return this.selectedBrowser; } - // ext-tabs calls tabListener.initTabReady(); which rely on deck when initializing ProgressListeners. - // Deck will be removed by https://phabricator.services.mozilla.com/D36575. - get deck() { - return { - addEventListener() {}, - removeEventListener() {}, - }; - } - static getBrowserApp(window) { let { BrowserApp } = window; diff --git a/toolkit/components/extensions/parent/ext-webNavigation.js b/toolkit/components/extensions/parent/ext-webNavigation.js index 4c9c5e0325af..ecb69cab446a 100644 --- a/toolkit/components/extensions/parent/ext-webNavigation.js +++ b/toolkit/components/extensions/parent/ext-webNavigation.js @@ -141,6 +141,7 @@ class WebNavigationEventManager extends EventManager { if ( chromeWin && chromeWin.gBrowser && + chromeWin.gBrowserInit && chromeWin.gBrowserInit.isAdoptingTab() && chromeWin.gBrowser.selectedBrowser === data.browser ) {