Bug 1299371 - Show context menu in WebExtension popups; r=mixedpuppy

MozReview-Commit-ID: 5bty8uurFns

--HG--
extra : rebase_source : 6b49e37b6173d1d5c219f554ac48c04942060daf
This commit is contained in:
Martin Giger 2017-02-19 11:02:14 +01:00
Родитель 99ef55dcc5
Коммит df5c55c05d
13 изменённых файлов: 495 добавлений и 32 удалений

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

@ -215,15 +215,15 @@ nsContextMenu.prototype = {
initNavigationItems: function CM_initNavigationItems() {
var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
this.onCanvas || this.onVideo || this.onAudio ||
this.onTextInput || this.onSocial);
this.onTextInput) && this.inTabBrowser;
this.showItem("context-navigation", shouldShow);
this.showItem("context-sep-navigation", shouldShow);
let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
let stopReloadItem = "";
if (shouldShow || this.onSocial) {
stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
if (shouldShow || !this.inTabBrowser) {
stopReloadItem = (stopped || !this.inTabBrowser) ? "reload" : "stop";
}
this.showItem("context-reload", stopReloadItem == "reload");
@ -290,9 +290,12 @@ nsContextMenu.prototype = {
this.onImage || this.onCanvas ||
this.onVideo || this.onAudio ||
this.onLink || this.onTextInput);
var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
var showInspect = this.inTabBrowser && gPrefService.getBoolPref("devtools.inspector.enabled");
this.showItem("context-viewsource", shouldShow);
this.showItem("context-viewinfo", shouldShow);
// The page info is broken for WebExtension popups, as the browser is
// destroyed when the popup is closed.
this.setItemAttr("context-viewinfo", "disabled", this.webExtBrowserType === "popup");
this.showItem("inspect-separator", showInspect);
this.showItem("context-inspect", showInspect);
@ -341,6 +344,9 @@ nsContextMenu.prototype = {
.disabled = !this.hasBGImage;
this.showItem("context-viewimageinfo", this.onImage);
// The image info popup is broken for WebExtension popups, since the browser
// is destroyed when the popup is closed.
this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
},
@ -350,11 +356,12 @@ nsContextMenu.prototype = {
this.showItem(bookmarkPage,
!(this.isContentSelected || this.onTextInput || this.onLink ||
this.onImage || this.onVideo || this.onAudio || this.onSocial ||
this.onCanvas));
this.onCanvas || this.inWebExtBrowser));
bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
!this.onSocial) || this.onPlainTextLink);
!this.onSocial && !this.onMozExtLink) ||
this.onPlainTextLink);
this.showItem("context-keywordfield",
this.onTextInput && this.onKeywordField);
this.showItem("frame", this.inFrame);
@ -398,13 +405,14 @@ nsContextMenu.prototype = {
let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
let pageShare = shareEnabled && !(this.isContentSelected ||
this.onTextInput || this.onLink || this.onImage ||
this.onVideo || this.onAudio || this.onCanvas);
this.onVideo || this.onAudio || this.onCanvas ||
this.inWebExtBrowser);
this.showItem("context-sharepage", pageShare);
this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink && !this.onMozExtLink);
this.showItem("context-shareimage", shareEnabled && this.onImage);
this.showItem("context-sharevideo", shareEnabled && this.onVideo);
this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:") || this.mediaURL.startsWith("moz-extension:"));
},
initSpellingItems() {
@ -677,6 +685,10 @@ nsContextMenu.prototype = {
this.onCTPPlugin = false;
this.canSpellCheck = false;
this.onPassword = false;
this.webExtBrowserType = "";
this.inWebExtBrowser = false;
this.inTabBrowser = true;
this.onMozExtLink = false;
if (this.isRemote) {
this.selectionInfo = gContextMenuContentData.selectionInfo;
@ -713,6 +725,10 @@ nsContextMenu.prototype = {
this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
}
this.onSocial = !!this.browser.getAttribute("origin");
this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
this.inWebExtBrowser = !!this.webExtBrowserType;
this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
!!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
// Check if we are in a synthetic document (stand alone image, video, etc.).
this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
@ -869,6 +885,7 @@ nsContextMenu.prototype = {
this.linkTextStr = this.getLinkText();
this.linkProtocol = this.getLinkProtocol();
this.onMailtoLink = (this.linkProtocol == "mailto");
this.onMozExtLink = (this.linkProtocol == "moz-extension");
this.onSaveableLink = this.isLinkSaveable( this.link );
this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
try {
@ -1914,7 +1931,7 @@ nsContextMenu.prototype = {
_getTelemetryPageContextInfo() {
let rv = [];
for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
"onTextInput", "onSocial"]) {
"onTextInput", "onSocial", "inWebExtBrowser", "inTabBrowser"]) {
if (this[k]) {
rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
}

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

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

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

@ -0,0 +1,6 @@
[DEFAULT]
support-files =
!/browser/base/content/test/general/contextmenu_common.js
subtst_contextmenu_webext.html
[browser_contextmenu_mozextension.js]

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

@ -0,0 +1,82 @@
"use strict";
var { SocialService } = Cu.import("resource:///modules/SocialService.jsm", {});
let contextMenu;
let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
// A social share provider
let manifest = {
name: "provider 1",
origin: "https://example.com",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
};
add_task(function* test_setup() {
const example_base = "http://example.com/browser/browser/base/content/test/contextMenu/";
const url = example_base + "subtst_contextmenu_webext.html";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const contextmenu_common = chrome_base + "contextmenu_common.js";
/* import-globals-from ../general/contextmenu_common.js */
Services.scriptloader.loadSubScript(contextmenu_common, this);
// Enable social sharing functions in the browser, so the context menu item is shown.
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
yield new Promise((resolve) => SocialService.addProvider(manifest, resolve));
ok(SocialShare.shareButton && !SocialShare.shareButton.disabled, "Sharing is enabled");
});
add_task(function* test_link() {
// gets hidden for this case.
yield test_contextmenu("#link",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-savelink", true,
"context-copylink", true,
"context-searchselect", true]);
});
add_task(function* test_video() {
yield test_contextmenu("#video",
["context-media-play", null,
"context-media-mute", null,
"context-media-playbackrate", null,
["context-media-playbackrate-050x", null,
"context-media-playbackrate-100x", null,
"context-media-playbackrate-125x", null,
"context-media-playbackrate-150x", null,
"context-media-playbackrate-200x", null], null,
"context-media-loop", null,
"context-media-showcontrols", null,
"context-video-fullscreen", null,
"---", null,
"context-viewvideo", null,
"context-copyvideourl", null,
"---", null,
"context-savevideo", null,
"context-sharevideo", false,
"context-video-saveimage", null,
"context-sendvideo", null,
"context-castvideo", null,
[], null
]);
});
add_task(function* test_cleanup() {
lastElementSelector = null;
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
yield new Promise((resolve) => {
return SocialService.disableProvider(manifest.origin, resolve);
});
});

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

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for browser context menu</title>
</head>
<body>
Browser context menu subtest.
<a href="moz-extension://foo-bar/tab.html" id="link">Link to an extension resource</a>
<video src="moz-extension://foo-bar/video.ogg" id="video"></video>
</body>
</html>

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

@ -17,6 +17,7 @@ MOCHITEST_CHROME_MANIFESTS += [
BROWSER_CHROME_MANIFESTS += [
'content/test/alerts/browser.ini',
'content/test/captivePortal/browser.ini',
'content/test/contextMenu/browser.ini',
'content/test/forms/browser.ini',
'content/test/general/browser.ini',
'content/test/newtab/browser.ini',

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

@ -233,6 +233,7 @@ class BasePopup {
browser.setAttribute("class", "webextension-popup-browser");
browser.setAttribute("webextension-view-type", "popup");
browser.setAttribute("tooltip", "aHTMLTooltip");
browser.setAttribute("contextmenu", "contentAreaContextMenu");
if (this.extension.remote) {
browser.setAttribute("remote", "true");
@ -286,6 +287,9 @@ class BasePopup {
setupBrowser(browser);
let mm = browser.messageManager;
// Sets the context information for context menus.
mm.loadFrameScript("chrome://browser/content/content.js", true);
mm.loadFrameScript(
"chrome://extensions/content/ext-browser-content.js", false);

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

@ -31,6 +31,7 @@ support-files =
[browser_ext_browserAction_area.js]
[browser_ext_browserAction_context.js]
[browser_ext_browserAction_contextMenu.js]
[browser_ext_browserAction_disabled.js]
[browser_ext_browserAction_pageAction_icon.js]
[browser_ext_browserAction_pageAction_icon_permissions.js]
@ -73,6 +74,7 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_optionsPage_browser_style.js]
[browser_ext_optionsPage_privileges.js]
[browser_ext_pageAction_context.js]
[browser_ext_pageAction_contextMenu.js]
[browser_ext_pageAction_popup.js]
[browser_ext_pageAction_popup_resize.js]
[browser_ext_pageAction_simple.js]
@ -93,6 +95,7 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_sessions_restore.js]
[browser_ext_sidebarAction.js]
[browser_ext_sidebarAction_context.js]
[browser_ext_sidebarAction_contextMenu.js]
[browser_ext_sidebarAction_tabs.js]
[browser_ext_sidebarAction_windows.js]
[browser_ext_simple.js]

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

@ -0,0 +1,106 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"browser_action": {
"default_popup": "popup.html",
},
},
useAddonManager: "temporary",
files: {
"popup.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
</head>
<body>
<span id="text">A Test Popup</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "disabled",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* browseraction_popup_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension);
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* browseraction_popup_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* browseraction_popup_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield clickBrowserAction(extension);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});

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

@ -0,0 +1,116 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"page_action": {
"default_popup": "popup.html",
},
},
useAddonManager: "temporary",
files: {
"popup.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
</head>
<body>
<span id="text">A Test Popup</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
browser.tabs.query({active: true, currentWindow: true}, tabs => {
const tabId = tabs[0].id;
browser.pageAction.show(tabId).then(() => {
browser.test.sendMessage("action-shown");
});
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "disabled",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* pageaction_popup_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension);
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* pageaction_popup_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* pageaction_popup_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
yield extension.awaitMessage("action-shown");
yield clickPageAction(extension, window);
let contentAreaContextMenu = yield openContextMenuInPopup(extension, "#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});

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

@ -4,7 +4,6 @@
let extData = {
manifest: {
"permissions": ["contextMenus"],
"sidebar_action": {
"default_panel": "sidebar.html",
},
@ -31,12 +30,6 @@ let extData = {
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
browser.test.onMessage.addListener(msg => {
if (msg === "set-panel") {
browser.sidebarAction.setPanel({panel: ""}).then(() => {
@ -103,20 +96,6 @@ add_task(function* sidebar_empty_panel() {
yield extension.unload();
});
add_task(function* sidebar_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar();
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* cleanup() {
// This is set on initial sidebar install.
Services.prefs.clearUserPref("extensions.sidebar-button.shown");

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

@ -0,0 +1,119 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
let extData = {
manifest: {
"permissions": ["contextMenus"],
"sidebar_action": {
"default_panel": "sidebar.html",
},
},
useAddonManager: "temporary",
files: {
"sidebar.html": `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/>
<script src="sidebar.js"></script>
</head>
<body>
<span id="text">A Test Sidebar</span>
<img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
</body></html>
`,
"sidebar.js": function() {
window.onload = () => {
browser.test.sendMessage("sidebar");
};
},
},
background: function() {
browser.contextMenus.create({
id: "clickme-page",
title: "Click me!",
contexts: ["all"],
});
},
};
let contextMenuItems = {
"context-navigation": "hidden",
"context-sep-navigation": "hidden",
"context-viewsource": "",
"context-viewinfo": "",
"inspect-separator": "hidden",
"context-inspect": "hidden",
"context-bookmarkpage": "hidden",
"context-sharepage": "hidden",
};
add_task(function* sidebar_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar();
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* sidebar_contextmenu_hidden_items() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar("#text");
let item, state;
for (const itemID in contextMenuItems) {
item = contentAreaContextMenu.querySelector(`#${itemID}`);
state = contextMenuItems[itemID];
if (state !== "") {
ok(item[state], `${itemID} is ${state}`);
if (state !== "hidden") {
ok(!item.hidden, `Disabled ${itemID} is not hidden`);
}
} else {
ok(!item.hidden, `${itemID} is not hidden`);
ok(!item.disabled, `${itemID} is not disabled`);
}
}
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* sidebar_image_contextmenu() {
let extension = ExtensionTestUtils.loadExtension(extData);
yield extension.startup();
// Test sidebar is opened on install
yield extension.awaitMessage("sidebar");
let contentAreaContextMenu = yield openContextMenuInSidebar("#testimg");
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(!item.disabled);
yield closeContextMenu(contentAreaContextMenu);
yield extension.unload();
});
add_task(function* cleanup() {
// This is set on initial sidebar install.
Services.prefs.clearUserPref("extensions.sidebar-button.shown");
});

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

@ -8,7 +8,8 @@
* getBrowserActionPopup getPageActionPopup
* closeBrowserAction closePageAction
* promisePopupShown promisePopupHidden
* openContextMenu closeContextMenu openContextMenuInSidebar
* openContextMenu closeContextMenu
* openContextMenuInSidebar openContextMenuInPopup
* openExtensionContextMenu closeExtensionContextMenu
* openActionContextMenu openSubmenu closeActionContextMenu
* openTabContextMenu closeTabContextMenu
@ -232,6 +233,16 @@ function closeBrowserAction(extension, win = window) {
return Promise.resolve();
}
async function openContextMenuInPopup(extension, selector = "body") {
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let browser = await awaitExtensionPanel(extension);
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "mousedown", button: 2}, browser);
await BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, browser);
await popupShownPromise;
return contentAreaContextMenu;
}
async function openContextMenuInSidebar(selector = "body") {
let contentAreaContextMenu = SidebarUI.browser.contentDocument.getElementById("contentAreaContextMenu");
let browser = SidebarUI.browser.contentDocument.getElementById("webext-panels-browser");