mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Bug 1383160 - Fix Android pageAction popup behavior. r=mixedpuppy
MozReview-Commit-ID: 66PnjFv4IIx --HG-- extra : rebase_source : c5fdbe75cea18bcbfeedc8916c2b9c00ec20a429
This commit is contained in:
parent
d0c4b71df2
commit
b1d2a209c5
@ -50,11 +50,7 @@ class PageAction extends EventEmitter {
|
||||
|
||||
let popup = this.tabContext.get(tab.id).popup || this.defaults.popup;
|
||||
if (popup) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.BrowserApp.addTab(popup, {
|
||||
selected: true,
|
||||
parentId: win.BrowserApp.selectedTab.id,
|
||||
});
|
||||
tabTracker.openExtensionPopupTab(popup);
|
||||
} else {
|
||||
this.emit("click", tab);
|
||||
}
|
||||
|
@ -250,6 +250,13 @@ global.WindowEventManager = class extends EventManager {
|
||||
};
|
||||
|
||||
class TabTracker extends TabTrackerBase {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Keep track of the extension popup tab.
|
||||
this._extensionPopupTabWeak = null;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
@ -258,6 +265,59 @@ class TabTracker extends TabTrackerBase {
|
||||
|
||||
windowTracker.addListener("TabClose", this);
|
||||
windowTracker.addListener("TabOpen", this);
|
||||
|
||||
// Register a listener for the Tab:Selected global event,
|
||||
// so that we can close the popup when a popup tab has been
|
||||
// unselected.
|
||||
GlobalEventDispatcher.registerListener(this, [
|
||||
"Tab:Selected",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently opened popup tab if any
|
||||
*/
|
||||
get extensionPopupTab() {
|
||||
if (this._extensionPopupTabWeak) {
|
||||
const tab = this._extensionPopupTabWeak.get();
|
||||
|
||||
// Return the native tab only if the tab has not been removed in the meantime.
|
||||
if (tab.browser) {
|
||||
return tab;
|
||||
}
|
||||
|
||||
// Clear the tracked popup tab if it has been closed in the meantime.
|
||||
this._extensionPopupTabWeak = null;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a pageAction/browserAction popup url in a tab and keep track of
|
||||
* its weak reference (to be able to customize the activedTab using the tab parentId,
|
||||
* to skip it in the tabs.query and to set the parent tab as active when the popup
|
||||
* tab is currently selected).
|
||||
*
|
||||
* @param {string} popup
|
||||
* The popup url to open in a tab.
|
||||
*/
|
||||
openExtensionPopupTab(popup) {
|
||||
let win = windowTracker.topWindow;
|
||||
if (!win) {
|
||||
throw new ExtensionError(`Unable to open a popup without an active window`);
|
||||
}
|
||||
|
||||
if (this.extensionPopupTab) {
|
||||
win.BrowserApp.closeTab(this.extensionPopupTab);
|
||||
}
|
||||
|
||||
this.init();
|
||||
|
||||
this._extensionPopupTabWeak = Cu.getWeakReference(win.BrowserApp.addTab(popup, {
|
||||
selected: true,
|
||||
parentId: win.BrowserApp.selectedTab.id,
|
||||
}));
|
||||
}
|
||||
|
||||
getId(nativeTab) {
|
||||
@ -288,7 +348,7 @@ class TabTracker extends TabTrackerBase {
|
||||
*/
|
||||
handleEvent(event) {
|
||||
const {BrowserApp} = event.target.ownerGlobal;
|
||||
let nativeTab = BrowserApp.getTabForBrowser(event.target);
|
||||
const nativeTab = BrowserApp.getTabForBrowser(event.target);
|
||||
|
||||
switch (event.type) {
|
||||
case "TabOpen":
|
||||
@ -301,6 +361,31 @@ class TabTracker extends TabTrackerBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by the GlobalEventDispatcher module. This event will get
|
||||
* called whenever one of the registered listeners fires.
|
||||
* @param {string} event The event which fired.
|
||||
* @param {object} data Information about the event which fired.
|
||||
*/
|
||||
onEvent(event, data) {
|
||||
const {BrowserApp} = windowTracker.topWindow;
|
||||
|
||||
switch (event) {
|
||||
case "Tab:Selected": {
|
||||
// If a new tab has been selected while an extension popup tab is still open,
|
||||
// close it immediately.
|
||||
const nativeTab = BrowserApp.getTabForId(data.id);
|
||||
|
||||
const popupTab = tabTracker.extensionPopupTab;
|
||||
if (popupTab && popupTab !== nativeTab) {
|
||||
BrowserApp.closeTab(popupTab);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a "tab-created" event for the given tab element.
|
||||
*
|
||||
@ -326,6 +411,17 @@ class TabTracker extends TabTrackerBase {
|
||||
let windowId = windowTracker.getId(nativeTab.browser.ownerGlobal);
|
||||
let tabId = this.getId(nativeTab);
|
||||
|
||||
if (this.extensionPopupTab && this.extensionPopupTab === nativeTab) {
|
||||
this._extensionPopupTabWeak = null;
|
||||
|
||||
// Select the parent tab when the closed tab was an extension popup tab.
|
||||
const {BrowserApp} = windowTracker.topWindow;
|
||||
const popupParentTab = BrowserApp.getTabForId(nativeTab.parentId);
|
||||
if (popupParentTab) {
|
||||
BrowserApp.selectTab(popupParentTab);
|
||||
}
|
||||
}
|
||||
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing});
|
||||
});
|
||||
@ -351,10 +447,19 @@ class TabTracker extends TabTrackerBase {
|
||||
}
|
||||
|
||||
get activeTab() {
|
||||
let window = windowTracker.topWindow;
|
||||
if (window && window.BrowserApp) {
|
||||
return window.BrowserApp.selectedTab;
|
||||
let win = windowTracker.topWindow;
|
||||
if (win && win.BrowserApp) {
|
||||
const selectedTab = win.BrowserApp.selectedTab;
|
||||
|
||||
// If the current tab is an extension popup tab, we use the parentId to retrieve
|
||||
// and return the tab that was selected when the popup tab has been opened.
|
||||
if (selectedTab === this.extensionPopupTab) {
|
||||
return win.BrowserApp.getTabForId(selectedTab.parentId);
|
||||
}
|
||||
|
||||
return selectedTab;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -406,6 +511,23 @@ class Tab extends TabBase {
|
||||
}
|
||||
|
||||
get active() {
|
||||
// If there is an extension popup tab and it is active,
|
||||
// then the parent tab of the extension popup tab is active
|
||||
// (while the extension popup tab will not be included in the
|
||||
// tabs.query results).
|
||||
if (tabTracker.extensionPopupTab) {
|
||||
if (tabTracker.extensionPopupTab.getActive() &&
|
||||
this.nativeTab.id === tabTracker.extensionPopupTab.parentId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Never return true for an active extension popup, e.g. so that
|
||||
// the popup tab will not be part of the results of querying
|
||||
// all the active tabs.
|
||||
if (tabTracker.extensionPopupTab === this.nativeTab) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.nativeTab.getActive();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
var {BrowserActions} = SpecialPowers.Cu.import("resource://gre/modules/BrowserActions.jsm", {});
|
||||
var {PageActions} = SpecialPowers.Cu.import("resource://gre/modules/PageActions.jsm", {});
|
||||
var {Services} = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
function pageLoadedContentScript() {
|
||||
browser.test.sendMessage("page-loaded", window.location.href);
|
||||
@ -202,6 +203,138 @@ add_task(async function test_activeTab_browserAction() {
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_activeTab_pageAction_popup() {
|
||||
async function background() {
|
||||
await browser.tabs.create({url: "http://example.com#test_activeTab_pageAction_popup"});
|
||||
const tabs = await browser.tabs.query({active: true});
|
||||
await browser.pageAction.show(tabs[0].id);
|
||||
|
||||
browser.test.log(`pageAction shown on tab ${tabs[0].id}`);
|
||||
|
||||
browser.test.sendMessage("background_page.ready", {activeTabId: tabs[0].id});
|
||||
}
|
||||
|
||||
async function popupScript() {
|
||||
function contentScriptCode() {
|
||||
browser.test.log("content script executed");
|
||||
|
||||
return "tabs.executeScript result";
|
||||
}
|
||||
|
||||
const tabs = await browser.tabs.query({active: true});
|
||||
const tab = tabs[0];
|
||||
|
||||
browser.test.log(`extension popup tab opened loaded for activeTab ${tab.id}`);
|
||||
|
||||
browser.test.sendMessage("extension_popup.activeTab", tab.id);
|
||||
|
||||
const [result] = await browser.tabs.executeScript(tab.id, {
|
||||
code: `(${contentScriptCode})()`,
|
||||
}).catch(error => {
|
||||
// Make the test to fail fast if something goes wrong.
|
||||
browser.test.fail(`Unexpected exception on tabs.executeScript: ${error}`);
|
||||
browser.test.notifyFail("page_action_popup.activeTab.done");
|
||||
throw error;
|
||||
});
|
||||
|
||||
browser.test.assertEq("tabs.executeScript result", result,
|
||||
"Got the expected result from tabs.executeScript");
|
||||
|
||||
browser.test.notifyPass("page_action_popup.activeTab.done");
|
||||
}
|
||||
|
||||
let popupHtml = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Extension Popup</h1>
|
||||
<script src="popup.js"><\/script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
"name": "PageAction Extension",
|
||||
"page_action": {
|
||||
"default_title": "Page Action",
|
||||
"default_icon": {
|
||||
"18": "extension.png",
|
||||
},
|
||||
"default_popup": "popup.html",
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"js": ["page_loaded.js"],
|
||||
"matches": ["http://example.com/*"],
|
||||
"run_at": "document_end",
|
||||
},
|
||||
],
|
||||
"permissions": ["activeTab"],
|
||||
},
|
||||
files: {
|
||||
"extension.png": TEST_ICON_ARRAYBUFFER,
|
||||
"page_loaded.js": pageLoadedContentScript,
|
||||
"popup.html": popupHtml,
|
||||
"popup.js": popupScript,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
const {activeTabId} = await extension.awaitMessage("background_page.ready");
|
||||
|
||||
const uuid = `{${extension.uuid}}`;
|
||||
|
||||
ok(PageActions.isShown(uuid), "page action is shown");
|
||||
|
||||
info("Wait the new tab to be loaded");
|
||||
const loadedURL = await extension.awaitMessage("page-loaded");
|
||||
|
||||
is(loadedURL, "http://example.com/#test_activeTab_pageAction_popup",
|
||||
"The expected URL has been loaded in a new tab");
|
||||
|
||||
PageActions.synthesizeClick(uuid);
|
||||
|
||||
const popupActiveTabId = await extension.awaitMessage("extension_popup.activeTab");
|
||||
|
||||
// Check that while the extension popup tab is selected the active tab is still the tab
|
||||
// from which the user has opened the extension popup.
|
||||
is(popupActiveTabId, activeTabId,
|
||||
"Got the expected tabId while the extension popup tab was selected");
|
||||
|
||||
await extension.awaitFinish("page_action_popup.activeTab.done");
|
||||
|
||||
const chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
const BrowserApp = chromeWin.BrowserApp;
|
||||
|
||||
const popupTab = BrowserApp.selectedTab;
|
||||
const popupTabId = popupTab.id;
|
||||
|
||||
let onceTabClosed = new Promise(resolve => {
|
||||
BrowserApp.deck.addEventListener("TabClose", resolve, {once: true});
|
||||
});
|
||||
|
||||
// Switch to the parent tab of the popup tab.
|
||||
// (which should make the extension popup tab to be closed automatically)
|
||||
BrowserApp.selectTab(BrowserApp.getTabForId(popupTab.parentId));
|
||||
|
||||
info("Wait for the extension popup tab to be closed once the parent tab has been selected");
|
||||
|
||||
await onceTabClosed;
|
||||
|
||||
is(BrowserApp.getTabForId(popupTabId), null,
|
||||
"The extension popup tab should have been closed");
|
||||
|
||||
// Close the tab that opened the extension popup before exiting the test.
|
||||
BrowserApp.closeTab(BrowserApp.selectedTab);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
Loading…
Reference in New Issue
Block a user