Bug 1562844 - GeckoView webextensions tabs and webnavigation listeners support r=robwu,rpl,snorp

This changes provide basic support for webextenion tabs and webNavigation listeners by implementing missing objects on which Fennec implementation was relying.

Differential Revision: https://phabricator.services.mozilla.com/D36575

--HG--
extra : moz-landing-system : lando
This commit is contained in:
chrmod 2019-08-13 18:59:55 +00:00
Родитель 16f6820be1
Коммит 3aafa3960e
14 изменённых файлов: 239 добавлений и 30 удалений

Просмотреть файл

@ -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);

Просмотреть файл

@ -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 });

Просмотреть файл

@ -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]

Просмотреть файл

@ -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;

Просмотреть файл

@ -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`);

Просмотреть файл

@ -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++) {

Просмотреть файл

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Tabs get Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
async background() {
const tab1 = await browser.tabs.create({});
const tab2 = await browser.tabs.create({});
browser.test.assertEq(tab1.id, (await browser.tabs.get(tab1.id)).id, "tabs.get should return tab with given id");
browser.test.assertEq(tab2.id, (await browser.tabs.get(tab2.id)).id, "tabs.get should return tab with given id");
await browser.tabs.remove(tab1.id);
await browser.tabs.remove(tab2.id);
browser.test.notifyPass("tabs.get");
},
});
await extension.startup();
await extension.awaitFinish("tabs.get");
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -27,9 +27,15 @@ 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();
}
let collectedSequence = [];
let tabId;

Просмотреть файл

@ -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();
});
</script>

Просмотреть файл

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebNavigation onCommitted Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["webNavigation", "tabs"],
},
async background() {
const url = "http://mochi.test:8888/";
const [tab, tabDetails] = await Promise.all([
browser.tabs.create({url}),
new Promise(resolve => {
browser.webNavigation.onCommitted.addListener(details => {
if (details.url === "about:blank") {
// skip initial about:blank
return;
}
resolve(details);
});
}),
]);
browser.test.assertEq(url, tabDetails.url, "webNavigation.onCommitted detects correct url");
browser.test.assertEq(tab.id, tabDetails.tabId, "webNavigation.onCommitted fire for proper tabId");
await browser.tabs.remove(tab.id);
browser.test.notifyPass("webNavigation.onCommitted");
},
});
await extension.startup();
await extension.awaitFinish("webNavigation.onCommitted");
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -198,7 +198,7 @@ public class TestRunnerActivity extends Activity {
@Override
public GeckoResult<AllowOrDeny> onCloseTab(WebExtension source, GeckoSession session) {
closeSession(session);
return GeckoResult.ALLOW;
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
});
sRuntime.setDelegate(() -> {

Просмотреть файл

@ -172,7 +172,7 @@ public class GeckoViewActivity extends AppCompatActivity {
public GeckoResult<AllowOrDeny> onCloseTab(WebExtension source, GeckoSession session) {
TabSession tabSession = mTabSessionManager.getSession(session);
closeTab(tabSession);
return GeckoResult.ALLOW;
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
});
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -141,6 +141,7 @@ class WebNavigationEventManager extends EventManager {
if (
chromeWin &&
chromeWin.gBrowser &&
chromeWin.gBrowserInit &&
chromeWin.gBrowserInit.isAdoptingTab() &&
chromeWin.gBrowser.selectedBrowser === data.browser
) {