Bug 1797764 - Add MV3 support for menus API and support for '_execute_action' command and the 'action' context. r=mkmelin
Differential Revision: https://phabricator.services.mozilla.com/D164499 --HG-- extra : amend_source : 6a74f23222e6c75718549dce1f166e52af860b3d
This commit is contained in:
Родитель
7063053c36
Коммит
51e3986fe6
|
@ -32,6 +32,7 @@ XPCOMUtils.defineLazyGetter(lazy, "messageDisplayActionFor", () => {
|
|||
return lazy.ExtensionParent.apiManager.global.messageDisplayActionFor;
|
||||
});
|
||||
|
||||
const EXECUTE_ACTION = "_execute_action";
|
||||
const EXECUTE_BROWSER_ACTION = "_execute_browser_action";
|
||||
const EXECUTE_MSG_DISPLAY_ACTION = "_execute_message_display_action";
|
||||
const EXECUTE_COMPOSE_ACTION = "_execute_compose_action";
|
||||
|
@ -60,7 +61,12 @@ class MailExtensionShortcuts extends ExtensionShortcuts {
|
|||
// therefore the listeners for these elements will be garbage collected.
|
||||
keyElement.addEventListener("command", event => {
|
||||
let action;
|
||||
if (name == EXECUTE_BROWSER_ACTION) {
|
||||
if (
|
||||
name == EXECUTE_BROWSER_ACTION &&
|
||||
this.extension.manifestVersion < 3
|
||||
) {
|
||||
action = lazy.browserActionFor(this.extension);
|
||||
} else if (name == EXECUTE_ACTION && this.extension.manifestVersion > 2) {
|
||||
action = lazy.browserActionFor(this.extension);
|
||||
} else if (name == EXECUTE_COMPOSE_ACTION) {
|
||||
action = lazy.composeActionFor(this.extension);
|
||||
|
|
|
@ -117,16 +117,24 @@ class ContextMenusClickPropHandler {
|
|||
|
||||
this.menus = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
let onClickedProp = new ContextMenusClickPropHandler(context);
|
||||
let pendingMenuEvent;
|
||||
|
||||
return {
|
||||
menus: {
|
||||
create(createProperties, callback) {
|
||||
if (createProperties.id === null) {
|
||||
let caller = context.getCaller();
|
||||
|
||||
if (extension.persistentBackground && createProperties.id === null) {
|
||||
createProperties.id = ++gNextMenuItemID;
|
||||
}
|
||||
let { onclick } = createProperties;
|
||||
if (onclick && !context.extension.persistentBackground) {
|
||||
throw new ExtensionError(
|
||||
`Property "onclick" cannot be used in menus.create, replace with an "onClicked" event listener.`
|
||||
);
|
||||
}
|
||||
delete createProperties.onclick;
|
||||
context.childManager
|
||||
.callParentAsyncFunction("menus.create", [createProperties])
|
||||
|
@ -139,7 +147,7 @@ this.menus = class extends ExtensionAPI {
|
|||
}
|
||||
})
|
||||
.catch(error => {
|
||||
context.withLastError(error, null, () => {
|
||||
context.withLastError(error, caller, () => {
|
||||
if (callback) {
|
||||
context.runSafeWithoutClone(callback);
|
||||
}
|
||||
|
@ -150,6 +158,11 @@ this.menus = class extends ExtensionAPI {
|
|||
|
||||
update(id, updateProperties) {
|
||||
let { onclick } = updateProperties;
|
||||
if (onclick && !context.extension.persistentBackground) {
|
||||
throw new ExtensionError(
|
||||
`Property "onclick" cannot be used in menus.update, replace with an "onClicked" event listener.`
|
||||
);
|
||||
}
|
||||
delete updateProperties.onclick;
|
||||
return context.childManager
|
||||
.callParentAsyncFunction("menus.update", [id, updateProperties])
|
||||
|
@ -253,6 +266,24 @@ this.menus = class extends ExtensionAPI {
|
|||
Services.obs.addObserver(pendingMenuEvent, "on-prepare-contextmenu");
|
||||
Services.tm.dispatchToMainThread(pendingMenuEvent);
|
||||
},
|
||||
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menus.onClicked",
|
||||
register: fire => {
|
||||
let listener = (info, tab) => {
|
||||
withHandlingUserInput(context.contentWindow, () =>
|
||||
fire.sync(info, tab)
|
||||
);
|
||||
};
|
||||
|
||||
let event = context.childManager.getParentEvent("menus.onClicked");
|
||||
event.addListener(listener);
|
||||
return () => {
|
||||
event.removeListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -228,6 +228,9 @@
|
|||
"scopes": [
|
||||
"addon_parent"
|
||||
],
|
||||
"events": [
|
||||
"startup"
|
||||
],
|
||||
"paths": [
|
||||
[
|
||||
"menus"
|
||||
|
|
|
@ -125,12 +125,14 @@ this.browserAction = class extends ToolbarButtonAPI {
|
|||
// This needs to work in normal window and message window.
|
||||
let tab = tabTracker.activeTab;
|
||||
let browser = tab.linkedBrowser || tab.getBrowser();
|
||||
const action =
|
||||
this.extension.manifestVersion < 3 ? "onBrowserAction" : "onAction";
|
||||
|
||||
global.actionContextMenu({
|
||||
tab,
|
||||
pageUrl: browser.currentURI.spec,
|
||||
extension: this.extension,
|
||||
onBrowserAction: true,
|
||||
[action]: true,
|
||||
menu,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ var { DefaultMap, ExtensionError } = ExtensionUtils;
|
|||
var { ExtensionParent } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionParent.jsm"
|
||||
);
|
||||
var { IconDetails } = ExtensionParent;
|
||||
var { IconDetails, StartupCache } = ExtensionParent;
|
||||
|
||||
var { ExtensionCommon } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionCommon.jsm"
|
||||
|
@ -38,6 +38,12 @@ const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
|
|||
// this cannot be a weak map.
|
||||
var gMenuMap = new Map();
|
||||
|
||||
// Map[Extension -> Map[ID -> MenuCreateProperties]]
|
||||
// The map object for each extension is a reference to the same
|
||||
// object in StartupCache.menus. This provides a non-async
|
||||
// getter for that object.
|
||||
var gStartupCache = new Map();
|
||||
|
||||
// Map[Extension -> MenuItem]
|
||||
var gRootItems = new Map();
|
||||
|
||||
|
@ -124,6 +130,7 @@ var gMenuBuilder = {
|
|||
|
||||
let rootElements;
|
||||
if (
|
||||
contextData.onAction ||
|
||||
contextData.onBrowserAction ||
|
||||
contextData.onComposeAction ||
|
||||
contextData.onMessageDisplayAction
|
||||
|
@ -255,7 +262,10 @@ var gMenuBuilder = {
|
|||
if (forceManifestIcons) {
|
||||
for (let rootElement of children) {
|
||||
// Display the extension icon on the root element.
|
||||
if (root.extension.manifest.icons) {
|
||||
if (
|
||||
root.extension.manifest.icons &&
|
||||
rootElement.getAttribute("type") !== "checkbox"
|
||||
) {
|
||||
this.setMenuItemIcon(
|
||||
rootElement,
|
||||
root.extension,
|
||||
|
@ -432,17 +442,22 @@ var gMenuBuilder = {
|
|||
info.modifiers = clickModifiersFromEvent(event);
|
||||
|
||||
info.button = button;
|
||||
let _execute_action =
|
||||
item.extension.manifestVersion < 3
|
||||
? "_execute_browser_action"
|
||||
: "_execute_action";
|
||||
|
||||
// Allow menus to open various actions supported in webext prior
|
||||
// to notifying onclicked.
|
||||
let actionFor = {
|
||||
_execute_browser_action: global.browserActionFor,
|
||||
[_execute_action]: global.browserActionFor,
|
||||
_execute_compose_action: global.composeActionFor,
|
||||
_execute_message_display_action: global.messageDisplayActionFor,
|
||||
}[item.command];
|
||||
if (actionFor) {
|
||||
let win = event.target.ownerGlobal;
|
||||
actionFor(item.extension).triggerAction(win);
|
||||
return;
|
||||
}
|
||||
|
||||
item.extension.emit(
|
||||
|
@ -549,6 +564,7 @@ var gMenuBuilder = {
|
|||
}
|
||||
|
||||
if (
|
||||
contextData.onAction ||
|
||||
contextData.onBrowserAction ||
|
||||
contextData.onComposeAction ||
|
||||
contextData.onMessageDisplayAction
|
||||
|
@ -594,7 +610,7 @@ var gMenuBuilder = {
|
|||
itemsToCleanUp: new Set(),
|
||||
};
|
||||
|
||||
// Called from pageAction or browserAction popup.
|
||||
// Called from different action popups.
|
||||
global.actionContextMenu = function(contextData) {
|
||||
contextData.originalViewType = "tab";
|
||||
gMenuBuilder.build(contextData);
|
||||
|
@ -610,6 +626,7 @@ const contextsMap = {
|
|||
isTextSelected: "selection",
|
||||
onVideo: "video",
|
||||
|
||||
onAction: "action",
|
||||
onBrowserAction: "browser_action",
|
||||
onComposeAction: "compose_action",
|
||||
onMessageDisplayAction: "message_display_action",
|
||||
|
@ -775,42 +792,46 @@ async function addMenuEventInfo(
|
|||
}
|
||||
}
|
||||
|
||||
function MenuItem(extension, createProperties, isRoot = false) {
|
||||
this.extension = extension;
|
||||
this.children = [];
|
||||
this.parent = null;
|
||||
this.tabManager = extension.tabManager;
|
||||
class MenuItem {
|
||||
constructor(extension, createProperties, isRoot = false) {
|
||||
this.extension = extension;
|
||||
this.children = [];
|
||||
this.parent = null;
|
||||
this.tabManager = extension.tabManager;
|
||||
|
||||
this.setDefaults();
|
||||
this.setProps(createProperties);
|
||||
this.setDefaults();
|
||||
this.setProps(createProperties);
|
||||
|
||||
if (!this.hasOwnProperty("_id")) {
|
||||
this.id = gNextMenuItemID++;
|
||||
if (!this.hasOwnProperty("_id")) {
|
||||
this.id = gNextMenuItemID++;
|
||||
}
|
||||
// If the item is not the root and has no parent
|
||||
// it must be a child of the root.
|
||||
if (!isRoot && !this.parent) {
|
||||
this.root.addChild(this);
|
||||
}
|
||||
}
|
||||
// If the item is not the root and has no parent
|
||||
// it must be a child of the root.
|
||||
if (!isRoot && !this.parent) {
|
||||
this.root.addChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem.prototype = {
|
||||
setProps(createProperties) {
|
||||
for (let propName in createProperties) {
|
||||
if (createProperties[propName] === null) {
|
||||
static mergeProps(obj, properties) {
|
||||
for (let propName in properties) {
|
||||
if (properties[propName] === null) {
|
||||
// Omitted optional argument.
|
||||
continue;
|
||||
}
|
||||
this[propName] = createProperties[propName];
|
||||
obj[propName] = properties[propName];
|
||||
}
|
||||
|
||||
if ("icons" in createProperties) {
|
||||
if (createProperties.icons === null) {
|
||||
this.icons = null;
|
||||
} else if (typeof createProperties.icons == "string") {
|
||||
this.icons = { 16: createProperties.icons };
|
||||
if ("icons" in properties) {
|
||||
if (properties.icons === null) {
|
||||
obj.icons = null;
|
||||
} else if (typeof properties.icons == "string") {
|
||||
obj.icons = { 16: properties.icons };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setProps(createProperties) {
|
||||
MenuItem.mergeProps(this, createProperties);
|
||||
|
||||
if (createProperties.documentUrlPatterns != null) {
|
||||
this.documentUrlMatchPattern = new MatchPatternSet(
|
||||
|
@ -834,7 +855,7 @@ MenuItem.prototype = {
|
|||
if (createProperties.parentId && !createProperties.contexts) {
|
||||
this.contexts = this.parent.contexts;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
setDefaults() {
|
||||
this.setProps({
|
||||
|
@ -844,7 +865,7 @@ MenuItem.prototype = {
|
|||
enabled: true,
|
||||
visible: true,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
if (this.hasOwnProperty("_id")) {
|
||||
|
@ -855,11 +876,11 @@ MenuItem.prototype = {
|
|||
throw new ExtensionError(`ID already exists: ${id}`);
|
||||
}
|
||||
this._id = id;
|
||||
},
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
},
|
||||
}
|
||||
|
||||
get elementId() {
|
||||
let id = this.id;
|
||||
|
@ -871,7 +892,7 @@ MenuItem.prototype = {
|
|||
id = `_${id}`;
|
||||
}
|
||||
return `${makeWidgetId(this.extension.id)}-menuitem-${id}`;
|
||||
},
|
||||
}
|
||||
|
||||
ensureValidParentId(parentId) {
|
||||
if (parentId === undefined) {
|
||||
|
@ -890,7 +911,25 @@ MenuItem.prototype = {
|
|||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* When updating menu properties we need to ensure parents exist
|
||||
* in the cache map before children. That allows the menus to be
|
||||
* created in the correct sequence on startup. This reparents the
|
||||
* tree starting from this instance of MenuItem.
|
||||
*/
|
||||
reparentInCache() {
|
||||
let { id, extension } = this;
|
||||
let cachedMap = gStartupCache.get(extension);
|
||||
let createProperties = cachedMap.get(id);
|
||||
cachedMap.delete(id);
|
||||
cachedMap.set(id, createProperties);
|
||||
|
||||
for (let child of this.children) {
|
||||
child.reparentInCache();
|
||||
}
|
||||
}
|
||||
|
||||
set parentId(parentId) {
|
||||
this.ensureValidParentId(parentId);
|
||||
|
@ -905,11 +944,11 @@ MenuItem.prototype = {
|
|||
let menuMap = gMenuMap.get(this.extension);
|
||||
menuMap.get(parentId).addChild(this);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
get parentId() {
|
||||
return this.parent ? this.parent.id : undefined;
|
||||
},
|
||||
}
|
||||
|
||||
addChild(child) {
|
||||
if (child.parent) {
|
||||
|
@ -917,7 +956,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
this.children.push(child);
|
||||
child.parent = this;
|
||||
},
|
||||
}
|
||||
|
||||
detachChild(child) {
|
||||
let idx = this.children.indexOf(child);
|
||||
|
@ -926,7 +965,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
this.children.splice(idx, 1);
|
||||
child.parent = null;
|
||||
},
|
||||
}
|
||||
|
||||
get root() {
|
||||
let extension = this.extension;
|
||||
|
@ -940,7 +979,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return gRootItems.get(extension);
|
||||
},
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.parent) {
|
||||
|
@ -953,10 +992,14 @@ MenuItem.prototype = {
|
|||
|
||||
let menuMap = gMenuMap.get(this.extension);
|
||||
menuMap.delete(this.id);
|
||||
// Menu items are saved if !extension.persistentBackground.
|
||||
if (gStartupCache.get(this.extension)?.delete(this.id)) {
|
||||
StartupCache.save();
|
||||
}
|
||||
if (this.root == this) {
|
||||
gRootItems.delete(this.extension);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async getClickInfo(contextData, wasChecked) {
|
||||
let info = {
|
||||
|
@ -974,7 +1017,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return info;
|
||||
},
|
||||
}
|
||||
|
||||
enabledForContext(contextData) {
|
||||
if (!this.visible) {
|
||||
|
@ -1035,8 +1078,8 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// While any extensions are active, this Tracker registers to observe/listen
|
||||
// for menu events from both Tools and context menus, both content and chrome.
|
||||
|
@ -1209,7 +1252,7 @@ const menuTracker = {
|
|||
},
|
||||
};
|
||||
|
||||
this.menus = class extends ExtensionAPI {
|
||||
this.menus = class extends ExtensionAPIPersistent {
|
||||
constructor(extension) {
|
||||
super(extension);
|
||||
|
||||
|
@ -1219,6 +1262,38 @@ this.menus = class extends ExtensionAPI {
|
|||
gMenuMap.set(extension, new Map());
|
||||
}
|
||||
|
||||
restoreFromCache() {
|
||||
let { extension } = this;
|
||||
// ensure extension has not shutdown
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
for (let createProperties of gStartupCache.get(extension).values()) {
|
||||
// The order of menu creation is significant, see reparentInCache.
|
||||
let menuItem = new MenuItem(extension, createProperties);
|
||||
gMenuMap.get(extension).set(menuItem.id, menuItem);
|
||||
}
|
||||
// Used for testing
|
||||
extension.emit("webext-menus-created", gMenuMap.get(extension));
|
||||
}
|
||||
|
||||
async onStartup() {
|
||||
let { extension } = this;
|
||||
if (extension.persistentBackground) {
|
||||
return;
|
||||
}
|
||||
// Using the map retains insertion order.
|
||||
let cachedMenus = await StartupCache.menus.get(extension.id, () => {
|
||||
return new Map();
|
||||
});
|
||||
gStartupCache.set(extension, cachedMenus);
|
||||
if (!cachedMenus.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.restoreFromCache();
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
let { extension } = this;
|
||||
|
||||
|
@ -1226,6 +1301,7 @@ this.menus = class extends ExtensionAPI {
|
|||
gMenuMap.delete(extension);
|
||||
gRootItems.delete(extension);
|
||||
gShownMenuItems.delete(extension);
|
||||
gStartupCache.delete(extension);
|
||||
gOnShownSubscribers.delete(extension);
|
||||
if (!gMenuMap.size) {
|
||||
menuTracker.unregister();
|
||||
|
@ -1233,6 +1309,116 @@ this.menus = class extends ExtensionAPI {
|
|||
}
|
||||
}
|
||||
|
||||
PERSISTENT_EVENTS = {
|
||||
onShown({ fire }) {
|
||||
let { extension } = this;
|
||||
let listener = async (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
|
||||
let nativeTab = contextData.tab;
|
||||
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let contextUrl = contextData.inFrame
|
||||
? contextData.frameUrl
|
||||
: contextData.pageUrl;
|
||||
|
||||
let ownerDocumentUrl = contextData.menu.ownerDocument.location.href;
|
||||
|
||||
let contextScheme;
|
||||
if (contextUrl) {
|
||||
contextScheme = Services.io.newURI(contextUrl).scheme;
|
||||
}
|
||||
|
||||
let includeSensitiveData =
|
||||
(nativeTab &&
|
||||
extension.tabManager.hasActiveTabPermission(nativeTab)) ||
|
||||
(contextUrl && extension.allowedOrigins.matches(contextUrl)) ||
|
||||
(MESSAGE_PROTOCOLS.includes(contextScheme) &&
|
||||
extension.hasPermission("messagesRead")) ||
|
||||
(ownerDocumentUrl ==
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml" &&
|
||||
extension.hasPermission("compose"));
|
||||
|
||||
await addMenuEventInfo(
|
||||
info,
|
||||
contextData,
|
||||
extension,
|
||||
includeSensitiveData
|
||||
);
|
||||
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.get(extension).add(listener);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return {
|
||||
unregister() {
|
||||
const listeners = gOnShownSubscribers.get(extension);
|
||||
listeners.delete(listener);
|
||||
if (listeners.size === 0) {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
}
|
||||
extension.off("webext-menu-shown", listener);
|
||||
},
|
||||
convert(_fire) {
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
},
|
||||
onHidden({ fire }) {
|
||||
let { extension } = this;
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return {
|
||||
unregister() {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
},
|
||||
convert(_fire) {
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
},
|
||||
onClicked({ context, fire }) {
|
||||
let { extension } = this;
|
||||
let listener = async (event, info, nativeTab) => {
|
||||
let { linkedBrowser } = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
if (fire.wakeup) {
|
||||
// force the wakeup, thus the call to convert to get the context.
|
||||
await fire.wakeup();
|
||||
// If while waiting the tab disappeared we bail out.
|
||||
if (
|
||||
!linkedBrowser.ownerGlobal.gBrowser.getTabForBrowser(linkedBrowser)
|
||||
) {
|
||||
console.error(
|
||||
`menus.onClicked: target tab closed during background startup.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
context.withPendingBrowser(linkedBrowser, () => fire.sync(info, tab));
|
||||
};
|
||||
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return {
|
||||
unregister() {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
},
|
||||
convert(_fire, _context) {
|
||||
fire = _fire;
|
||||
context = _context;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
getAPI(context) {
|
||||
let { extension } = context;
|
||||
|
||||
|
@ -1244,90 +1430,74 @@ this.menus = class extends ExtensionAPI {
|
|||
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "menus.onShown",
|
||||
register: fire => {
|
||||
let listener = async (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
|
||||
let nativeTab = contextData.tab;
|
||||
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let contextUrl = contextData.inFrame
|
||||
? contextData.frameUrl
|
||||
: contextData.pageUrl;
|
||||
|
||||
let ownerDocumentUrl =
|
||||
contextData.menu.ownerDocument.location.href;
|
||||
|
||||
let contextScheme;
|
||||
if (contextUrl) {
|
||||
contextScheme = Services.io.newURI(contextUrl).scheme;
|
||||
}
|
||||
|
||||
let includeSensitiveData =
|
||||
(nativeTab &&
|
||||
extension.tabManager.hasActiveTabPermission(nativeTab)) ||
|
||||
(contextUrl && extension.allowedOrigins.matches(contextUrl)) ||
|
||||
(MESSAGE_PROTOCOLS.includes(contextScheme) &&
|
||||
extension.hasPermission("messagesRead")) ||
|
||||
(ownerDocumentUrl ==
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml" &&
|
||||
extension.hasPermission("compose"));
|
||||
|
||||
await addMenuEventInfo(
|
||||
info,
|
||||
contextData,
|
||||
extension,
|
||||
includeSensitiveData
|
||||
);
|
||||
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.get(extension).add(context);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return () => {
|
||||
const contexts = gOnShownSubscribers.get(extension);
|
||||
contexts.delete(context);
|
||||
if (contexts.size === 0) {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
}
|
||||
extension.off("webext-menu-shown", listener);
|
||||
};
|
||||
},
|
||||
module: "menus",
|
||||
event: "onShown",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onHidden: new EventManager({
|
||||
context,
|
||||
name: "menus.onHidden",
|
||||
register: fire => {
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
};
|
||||
},
|
||||
module: "menus",
|
||||
event: "onHidden",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
module: "menus",
|
||||
event: "onClicked",
|
||||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
create(createProperties) {
|
||||
// event pages require id
|
||||
if (!extension.persistentBackground) {
|
||||
if (!createProperties.id) {
|
||||
throw new ExtensionError(
|
||||
"menus.create requires an id for non-persistent background scripts."
|
||||
);
|
||||
}
|
||||
if (gMenuMap.get(extension).has(createProperties.id)) {
|
||||
throw new ExtensionError(
|
||||
`The menu id ${createProperties.id} already exists in menus.create.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the id is required by the schema. If the addon did not set
|
||||
// it, the implementation of menus.create in the child should
|
||||
// have added it.
|
||||
// it, the implementation of menus.create in the child will add it for
|
||||
// extensions with persistent backgrounds, but not otherwise.
|
||||
let menuItem = new MenuItem(extension, createProperties);
|
||||
gMenuMap.get(extension).set(menuItem.id, menuItem);
|
||||
if (!extension.persistentBackground) {
|
||||
// Only cache properties that are necessary.
|
||||
let cached = {};
|
||||
MenuItem.mergeProps(cached, createProperties);
|
||||
gStartupCache.get(extension).set(menuItem.id, cached);
|
||||
StartupCache.save();
|
||||
}
|
||||
},
|
||||
|
||||
update(id, updateProperties) {
|
||||
let menuItem = gMenuMap.get(extension).get(id);
|
||||
if (menuItem) {
|
||||
menuItem.setProps(updateProperties);
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
menuItem.setProps(updateProperties);
|
||||
|
||||
// Update the startup cache for non-persistent extensions.
|
||||
if (extension.persistentBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cached = gStartupCache.get(extension).get(id);
|
||||
let reparent =
|
||||
updateProperties.parentId != null &&
|
||||
cached.parentId != updateProperties.parentId;
|
||||
MenuItem.mergeProps(cached, updateProperties);
|
||||
if (reparent) {
|
||||
// The order of menu creation is significant, see reparentInCache.
|
||||
menuItem.reparentInCache();
|
||||
}
|
||||
StartupCache.save();
|
||||
},
|
||||
|
||||
remove(id) {
|
||||
|
@ -1342,27 +1512,15 @@ this.menus = class extends ExtensionAPI {
|
|||
if (root) {
|
||||
root.remove();
|
||||
}
|
||||
// Should be empty, just extra assurance.
|
||||
if (!extension.persistentBackground) {
|
||||
let cached = gStartupCache.get(extension);
|
||||
if (cached.size) {
|
||||
cached.clear();
|
||||
StartupCache.save();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menus.onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (event, info, nativeTab) => {
|
||||
let { linkedBrowser } = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
context.withPendingBrowser(linkedBrowser, () =>
|
||||
fire.sync(info, tab)
|
||||
);
|
||||
};
|
||||
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"commands": {
|
||||
"type": "object",
|
||||
"optional": true,
|
||||
"description": "A <em>dictionary object</em> defining one or more commands as <em>name-value</em> pairs, the <em>name</em> being the name of the command and the <em>value</em> being a :ref:`commands.CommandsShortcut`. The <em>name</em> may also be one of the following built-in special shortcuts: \n * <value>_execute_browser_action</value> \n * <value>_execute_compose_action</value> \n * <value>_execute_message_display_action</value>\nExample: <literalinclude>includes/commands/manifest.json<lang>JSON</lang></literalinclude>",
|
||||
"description": "A <em>dictionary object</em> defining one or more commands as <em>name-value</em> pairs, the <em>name</em> being the name of the command and the <em>value</em> being a :ref:`commands.CommandsShortcut`. The <em>name</em> may also be one of the following built-in special shortcuts: \n * <value>_execute_action</value> \n * <value>_execute_compose_action</value> \n * <value>_execute_message_display_action</value>\nExample: <literalinclude>includes/commands/manifest.json<lang>JSON</lang></literalinclude>",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
|
|
@ -44,28 +44,45 @@
|
|||
"types": [
|
||||
{
|
||||
"id": "ContextType",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all",
|
||||
"page",
|
||||
"frame",
|
||||
"selection",
|
||||
"link",
|
||||
"editable",
|
||||
"password",
|
||||
"image",
|
||||
"video",
|
||||
"audio",
|
||||
"browser_action",
|
||||
"compose_action",
|
||||
"message_display_action",
|
||||
"tab",
|
||||
"message_list",
|
||||
"folder_pane",
|
||||
"compose_attachments",
|
||||
"message_attachments",
|
||||
"all_message_attachments",
|
||||
"tools_menu"
|
||||
"choices": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all",
|
||||
"page",
|
||||
"frame",
|
||||
"selection",
|
||||
"link",
|
||||
"editable",
|
||||
"password",
|
||||
"image",
|
||||
"video",
|
||||
"audio",
|
||||
"compose_action",
|
||||
"message_display_action",
|
||||
"tab",
|
||||
"message_list",
|
||||
"folder_pane",
|
||||
"compose_attachments",
|
||||
"message_attachments",
|
||||
"all_message_attachments",
|
||||
"tools_menu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"max_manifest_version": 2,
|
||||
"enum": [
|
||||
"browser_action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"min_manifest_version": 3,
|
||||
"enum": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "The different contexts a menu can appear in. Specifying <value>all</value> is equivalent to the combination of all other contexts excluding <value>tab</value> and <value>tools_menu</value>. More information about each context can be found in the `Supported UI Elements <|link-ui-elements|>`__ article on developer.thunderbird.net."
|
||||
},
|
||||
|
@ -490,7 +507,7 @@
|
|||
"command": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "Specifies a command to issue for the context click. Currently supports internal commands <value>_execute_browser_action</value>, <value>_execute_compose_action</value> and <value>_execute_message_display_action</value>."
|
||||
"description": "Specifies a command to issue for the context click. Currently supports internal commands <value>_execute_action</value>, <value>_execute_compose_action</value> and <value>_execute_message_display_action</value>."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -9,13 +9,16 @@ prefs =
|
|||
mailnews.start_page.override_url=about:blank
|
||||
mailnews.start_page.url=about:blank
|
||||
subsuite = thunderbird
|
||||
support-files = ../xpcshell/data/utils.js
|
||||
support-files =
|
||||
head_menus.js
|
||||
../xpcshell/data/utils.js
|
||||
tags = webextensions
|
||||
|
||||
[browser_ext_addressBooksUI.js]
|
||||
tags = addrbook
|
||||
[browser_ext_browserAction.js]
|
||||
[browser_ext_browserAction_popup_click.js]
|
||||
[browser_ext_browserAction_popup_click_mv3_event_pages.js]
|
||||
[browser_ext_browserAction_properties.js]
|
||||
[browser_ext_cloudFile.js]
|
||||
support-files = data/cloudFile1.txt data/cloudFile2.txt
|
||||
|
@ -45,6 +48,7 @@ support-files = data/cloudFile1.txt data/cloudFile2.txt
|
|||
[browser_ext_compose_sendMessage.js]
|
||||
[browser_ext_composeAction.js]
|
||||
[browser_ext_composeAction_popup_click.js]
|
||||
[browser_ext_composeAction_popup_click_mv3_event_pages.js]
|
||||
[browser_ext_composeAction_properties.js]
|
||||
[browser_ext_composeScripts.js]
|
||||
[browser_ext_contentScripts.js]
|
||||
|
@ -69,6 +73,7 @@ skip-if = true
|
|||
reason = FixMe: This is messing up msgHdr of test messages and breaks the following tests.
|
||||
[browser_ext_messageDisplayAction.js]
|
||||
[browser_ext_messageDisplayAction_popup_click.js]
|
||||
[browser_ext_messageDisplayAction_popup_click_mv3_event_pages.js]
|
||||
[browser_ext_messageDisplayAction_properties.js]
|
||||
[browser_ext_messageDisplayScripts.js]
|
||||
[browser_ext_quickFilter.js]
|
||||
|
|
|
@ -33,7 +33,7 @@ add_setup(async () => {
|
|||
});
|
||||
|
||||
// This test uses a command from the menus API to open the popup.
|
||||
add_task(async function test_popup_open_with_menu_command() {
|
||||
add_task(async function test_popup_open_with_menu_command_mv2() {
|
||||
info("3-pane tab");
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
|
@ -81,6 +81,56 @@ add_task(async function test_popup_open_with_menu_command() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_popup_open_with_menu_command_mv3() {
|
||||
info("3-pane tab");
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
actionType: "action",
|
||||
testType: "open-with-menu-command",
|
||||
default_area: area,
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
actionType: "action",
|
||||
testType: "open-with-menu-command",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_theme_icons() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
|
|
|
@ -94,65 +94,3 @@ add_task(async function test_popup_open_with_click() {
|
|||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: area,
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
|
||||
let account;
|
||||
let messages;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
createMessages(subFolders[0], 10);
|
||||
messages = subFolders[0].messages;
|
||||
|
||||
// This tests selects a folder, so make sure the folder pane is visible.
|
||||
if (
|
||||
document.getElementById("folderpane_splitter").getAttribute("state") ==
|
||||
"collapsed"
|
||||
) {
|
||||
window.MsgToggleFolderPane();
|
||||
}
|
||||
if (window.IsMessagePaneCollapsed()) {
|
||||
window.MsgToggleMessagePane();
|
||||
}
|
||||
|
||||
window.gFolderTreeView.selectFolder(subFolders[0]);
|
||||
window.gFolderDisplay.selectViewIndex(0);
|
||||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
for (let area of [null, "tabstoolbar"]) {
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_area: area,
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
actionType: "action",
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
testType: "open-with-mouse-click",
|
||||
default_windows: ["messageDisplay"],
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
|
@ -2,7 +2,7 @@
|
|||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
async function testExecuteBrowserActionWithOptions(options = {}) {
|
||||
async function testExecuteBrowserActionWithOptions_mv2(options = {}) {
|
||||
// Make sure the mouse isn't hovering over the browserAction widget.
|
||||
let folderTree = document.getElementById("folderTree");
|
||||
EventUtils.synthesizeMouseAtCenter(folderTree, { type: "mouseover" }, window);
|
||||
|
@ -47,11 +47,9 @@ async function testExecuteBrowserActionWithOptions(options = {}) {
|
|||
extensionOptions.background = () => {
|
||||
browser.test.onMessage.addListener((message, withPopup) => {
|
||||
browser.commands.onCommand.addListener(commandName => {
|
||||
if (commandName == "_execute_browser_action") {
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for _execute_browser_action."
|
||||
);
|
||||
}
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for a valid _execute_* command."
|
||||
);
|
||||
});
|
||||
|
||||
browser.browserAction.onClicked.addListener(() => {
|
||||
|
@ -101,12 +99,120 @@ async function testExecuteBrowserActionWithOptions(options = {}) {
|
|||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_execute_browser_action_with_popup() {
|
||||
await testExecuteBrowserActionWithOptions({
|
||||
add_task(async function test_execute_browser_action_with_popup_mv2() {
|
||||
await testExecuteBrowserActionWithOptions_mv2({
|
||||
withPopup: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_execute_browser_action_without_popup() {
|
||||
await testExecuteBrowserActionWithOptions();
|
||||
add_task(async function test_execute_browser_action_without_popup_mv2() {
|
||||
await testExecuteBrowserActionWithOptions_mv2();
|
||||
});
|
||||
|
||||
async function testExecuteActionWithOptions_mv3(options = {}) {
|
||||
// Make sure the mouse isn't hovering over the action widget.
|
||||
let folderTree = document.getElementById("folderTree");
|
||||
EventUtils.synthesizeMouseAtCenter(folderTree, { type: "mouseover" }, window);
|
||||
|
||||
let extensionOptions = {};
|
||||
|
||||
extensionOptions.manifest = {
|
||||
manifest_version: 3,
|
||||
commands: {
|
||||
_execute_action: {
|
||||
suggested_key: {
|
||||
default: "Alt+Shift+J",
|
||||
},
|
||||
},
|
||||
},
|
||||
action: {
|
||||
browser_style: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (options.withPopup) {
|
||||
extensionOptions.manifest.action.default_popup = "popup.html";
|
||||
|
||||
extensionOptions.files = {
|
||||
"popup.html": `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="popup.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
Popup
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
"popup.js": function() {
|
||||
browser.runtime.sendMessage("from-action-popup");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
extensionOptions.background = () => {
|
||||
browser.test.onMessage.addListener((message, withPopup) => {
|
||||
browser.commands.onCommand.addListener(commandName => {
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for a valid _execute_* command."
|
||||
);
|
||||
});
|
||||
|
||||
browser.action.onClicked.addListener(() => {
|
||||
if (withPopup) {
|
||||
browser.test.fail(
|
||||
"The onClick listener should never fire if the action has a popup."
|
||||
);
|
||||
browser.test.notifyFail("execute-action-on-clicked-fired");
|
||||
} else {
|
||||
browser.test.notifyPass("execute-action-on-clicked-fired");
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener(msg => {
|
||||
if (msg == "from-action-popup") {
|
||||
browser.test.notifyPass("execute-action-popup-opened");
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("send-keys");
|
||||
});
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionOptions);
|
||||
|
||||
extension.onMessage("send-keys", () => {
|
||||
EventUtils.synthesizeKey("j", { altKey: true, shiftKey: true });
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
await SimpleTest.promiseFocus(window);
|
||||
|
||||
// trigger setup of listeners in background and the send-keys msg
|
||||
extension.sendMessage("withPopup", options.withPopup);
|
||||
|
||||
if (options.withPopup) {
|
||||
await extension.awaitFinish("execute-action-popup-opened");
|
||||
|
||||
if (!getBrowserActionPopup(extension)) {
|
||||
await awaitExtensionPanel(extension);
|
||||
}
|
||||
await closeBrowserAction(extension);
|
||||
} else {
|
||||
await extension.awaitFinish("execute-action-on-clicked-fired");
|
||||
}
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_execute_browser_action_with_popup_mv3() {
|
||||
await testExecuteActionWithOptions_mv3({
|
||||
withPopup: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_execute_browser_action_without_popup_mv3() {
|
||||
await testExecuteActionWithOptions_mv3();
|
||||
});
|
||||
|
|
|
@ -64,11 +64,9 @@ async function testExecuteComposeActionWithOptions(options = {}) {
|
|||
|
||||
browser.test.onMessage.addListener((message, withPopup) => {
|
||||
browser.commands.onCommand.addListener(commandName => {
|
||||
if (commandName == "_execute_compose_action") {
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for _execute_compose_action."
|
||||
);
|
||||
}
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for a valid _execute_* command."
|
||||
);
|
||||
});
|
||||
|
||||
browser.composeAction.onClicked.addListener(() => {
|
||||
|
|
|
@ -52,11 +52,9 @@ async function testExecuteMessageDisplayActionWithOptions(msg, options = {}) {
|
|||
extensionOptions.background = () => {
|
||||
browser.test.onMessage.addListener((message, withPopup) => {
|
||||
browser.commands.onCommand.addListener(commandName => {
|
||||
if (commandName == "_execute_message_display_action") {
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for _execute_message_display_action."
|
||||
);
|
||||
}
|
||||
browser.test.fail(
|
||||
"The onCommand listener should never fire for a valid _execute_* command."
|
||||
);
|
||||
});
|
||||
|
||||
browser.messageDisplayAction.onClicked.addListener(() => {
|
||||
|
|
|
@ -51,45 +51,3 @@ add_task(async function test_popup_open_with_click() {
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
for (let area of [null, "formattoolbar"]) {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
composeWindow.close();
|
||||
Services.xulStore.removeDocument(
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml"
|
||||
);
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
let account;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
addIdentity(account);
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
for (let area of [null, "formattoolbar"]) {
|
||||
let composeWindow = await openComposeWindow(account);
|
||||
await focusWindow(composeWindow);
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "compose_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: composeWindow,
|
||||
default_area: area,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
composeWindow.close();
|
||||
Services.xulStore.removeDocument(
|
||||
"chrome://messenger/content/messengercompose/messengercompose.xhtml"
|
||||
);
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -2,8 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
|
@ -105,90 +103,3 @@ add_task(async function test_popup_open_with_click() {
|
|||
messageWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
{
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message tab");
|
||||
{
|
||||
await openMessageInTab(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
|
||||
let account;
|
||||
let messages;
|
||||
|
||||
add_setup(async () => {
|
||||
account = createAccount();
|
||||
let rootFolder = account.incomingServer.rootFolder;
|
||||
let subFolders = rootFolder.subFolders;
|
||||
createMessages(subFolders[0], 10);
|
||||
messages = subFolders[0].messages;
|
||||
|
||||
// This tests selects a folder, so make sure the folder pane is visible.
|
||||
if (
|
||||
document.getElementById("folderpane_splitter").getAttribute("state") ==
|
||||
"collapsed"
|
||||
) {
|
||||
window.MsgToggleFolderPane();
|
||||
}
|
||||
if (window.IsMessagePaneCollapsed()) {
|
||||
window.MsgToggleMessagePane();
|
||||
}
|
||||
|
||||
window.gFolderTreeView.selectFolder(subFolders[0]);
|
||||
window.gFolderDisplay.selectViewIndex(0);
|
||||
await BrowserTestUtils.browserLoaded(window.getMessagePaneBrowser());
|
||||
});
|
||||
|
||||
async function subtest_popup_open_with_click_MV3_event_pages(
|
||||
terminateBackground
|
||||
) {
|
||||
info("3-pane tab");
|
||||
{
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
}
|
||||
|
||||
info("Message tab");
|
||||
{
|
||||
await openMessageInTab(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
document.getElementById("tabmail").closeTab();
|
||||
}
|
||||
|
||||
info("Message window");
|
||||
{
|
||||
let messageWindow = await openMessageInWindow(messages.getNext());
|
||||
let testConfig = {
|
||||
manifest_version: 3,
|
||||
terminateBackground,
|
||||
actionType: "message_display_action",
|
||||
testType: "open-with-mouse-click",
|
||||
window: messageWindow,
|
||||
};
|
||||
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
disable_button: true,
|
||||
});
|
||||
await run_popup_test({
|
||||
...testConfig,
|
||||
use_default_popup: true,
|
||||
});
|
||||
|
||||
messageWindow.close();
|
||||
}
|
||||
}
|
||||
// This MV3 test clicks on the action button to open the popup.
|
||||
add_task(async function test_event_pages_without_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(false);
|
||||
});
|
||||
// This MV3 test clicks on the action button to open the popup (background termination).
|
||||
add_task(async function test_event_pages_with_background_termination() {
|
||||
await subtest_popup_open_with_click_MV3_event_pages(true);
|
||||
});
|
|
@ -0,0 +1,533 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { ExtensionPermissions } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionPermissions.jsm"
|
||||
);
|
||||
|
||||
const { mailTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/mailnews/MailTestUtils.jsm"
|
||||
);
|
||||
|
||||
const treeClick = mailTestUtils.treeClick.bind(null, EventUtils, window);
|
||||
|
||||
var URL_BASE =
|
||||
"http://mochi.test:8888/browser/comm/mail/components/extensions/test/browser/data";
|
||||
|
||||
/**
|
||||
* Left-click on something and wait for the context menu to appear.
|
||||
* For elements in the parent process only.
|
||||
*
|
||||
* @param {Element} menu - The <menu> that should appear.
|
||||
* @param {Element} element - The element to be clicked on.
|
||||
* @returns {Promise} A promise that resolves when the menu appears.
|
||||
*/
|
||||
function leftClick(menu, element) {
|
||||
let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(element, {}, element.ownerGlobal);
|
||||
return shownPromise;
|
||||
}
|
||||
/**
|
||||
* Right-click on something and wait for the context menu to appear.
|
||||
* For elements in the parent process only.
|
||||
*
|
||||
* @param {Element} menu - The <menu> that should appear.
|
||||
* @param {Element} element - The element to be clicked on.
|
||||
* @returns {Promise} A promise that resolves when the menu appears.
|
||||
*/
|
||||
function rightClick(menu, element) {
|
||||
let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
element,
|
||||
{ type: "contextmenu" },
|
||||
element.ownerGlobal
|
||||
);
|
||||
return shownPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-click on something in a content document and wait for the context
|
||||
* menu to appear.
|
||||
*
|
||||
* @param {Element} menu - The <menu> that should appear.
|
||||
* @param {string} selector - CSS selector of the element to be clicked on.
|
||||
* @param {Element} browser - <browser> containing the element.
|
||||
* @returns {Promise} A promise that resolves when the menu appears.
|
||||
*/
|
||||
async function rightClickOnContent(menu, selector, browser) {
|
||||
let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
selector,
|
||||
{ type: "contextmenu" },
|
||||
browser
|
||||
);
|
||||
return shownPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the parameters of a browser.onShown event was fired.
|
||||
*
|
||||
* @see mail/components/extensions/schemas/menus.json
|
||||
*
|
||||
* @param extension
|
||||
* @param {object} expectedInfo
|
||||
* @param {Array?} expectedInfo.menuIds
|
||||
* @param {Array?} expectedInfo.contexts
|
||||
* @param {Array?} expectedInfo.attachments
|
||||
* @param {object?} expectedInfo.displayedFolder
|
||||
* @param {object?} expectedInfo.selectedFolder
|
||||
* @param {Array?} expectedInfo.selectedMessages
|
||||
* @param {RegExp?} expectedInfo.pageUrl
|
||||
* @param {string?} expectedInfo.selectionText
|
||||
* @param {object} expectedTab
|
||||
* @param {boolean} expectedTab.active
|
||||
* @param {integer} expectedTab.index
|
||||
* @param {boolean} expectedTab.mailTab
|
||||
*/
|
||||
async function checkShownEvent(extension, expectedInfo, expectedTab) {
|
||||
let [info, tab] = await extension.awaitMessage("onShown");
|
||||
Assert.deepEqual(info.menuIds, expectedInfo.menuIds);
|
||||
Assert.deepEqual(info.contexts, expectedInfo.contexts);
|
||||
|
||||
Assert.equal(
|
||||
!!info.attachments,
|
||||
!!expectedInfo.attachments,
|
||||
"attachments in info"
|
||||
);
|
||||
if (expectedInfo.attachments) {
|
||||
Assert.equal(info.attachments.length, expectedInfo.attachments.length);
|
||||
for (let i = 0; i < expectedInfo.attachments.length; i++) {
|
||||
Assert.equal(info.attachments[i].name, expectedInfo.attachments[i].name);
|
||||
Assert.equal(info.attachments[i].size, expectedInfo.attachments[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
for (let infoKey of ["displayedFolder", "selectedFolder"]) {
|
||||
Assert.equal(
|
||||
!!info[infoKey],
|
||||
!!expectedInfo[infoKey],
|
||||
`${infoKey} in info`
|
||||
);
|
||||
if (expectedInfo[infoKey]) {
|
||||
Assert.equal(info[infoKey].accountId, expectedInfo[infoKey].accountId);
|
||||
Assert.equal(info[infoKey].path, expectedInfo[infoKey].path);
|
||||
Assert.ok(Array.isArray(info[infoKey].subFolders));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.equal(
|
||||
!!info.selectedMessages,
|
||||
!!expectedInfo.selectedMessages,
|
||||
"selectedMessages in info"
|
||||
);
|
||||
if (expectedInfo.selectedMessages) {
|
||||
Assert.equal(info.selectedMessages.id, null);
|
||||
Assert.equal(
|
||||
info.selectedMessages.messages.length,
|
||||
expectedInfo.selectedMessages.messages.length
|
||||
);
|
||||
for (let i = 0; i < expectedInfo.selectedMessages.messages.length; i++) {
|
||||
Assert.equal(
|
||||
info.selectedMessages.messages[i].subject,
|
||||
expectedInfo.selectedMessages.messages[i].subject
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.equal(!!info.pageUrl, !!expectedInfo.pageUrl, "pageUrl in info");
|
||||
if (expectedInfo.pageUrl) {
|
||||
if (typeof expectedInfo.pageUrl == "string") {
|
||||
Assert.equal(info.pageUrl, expectedInfo.pageUrl);
|
||||
} else {
|
||||
Assert.ok(info.pageUrl.match(expectedInfo.pageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.equal(
|
||||
!!info.selectionText,
|
||||
!!expectedInfo.selectionText,
|
||||
"selectionText in info"
|
||||
);
|
||||
if (expectedInfo.selectionText) {
|
||||
Assert.equal(info.selectionText, expectedInfo.selectionText);
|
||||
}
|
||||
|
||||
Assert.equal(tab.active, expectedTab.active, "tab is active");
|
||||
Assert.equal(tab.index, expectedTab.index, "tab index");
|
||||
Assert.equal(tab.mailTab, expectedTab.mailTab, "tab is mailTab");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the parameters of a browser.onClicked event was fired.
|
||||
*
|
||||
* @see mail/components/extensions/schemas/menus.json
|
||||
*
|
||||
* @param extension
|
||||
* @param {object} expectedInfo
|
||||
* @param {string?} expectedInfo.selectionText
|
||||
* @param {string?} expectedInfo.linkText
|
||||
* @param {RegExp?} expectedInfo.pageUrl
|
||||
* @param {RegExp?} expectedInfo.linkUrl
|
||||
* @param {RegExp?} expectedInfo.srcUrl
|
||||
* @param {object} expectedTab
|
||||
* @param {boolean} expectedTab.active
|
||||
* @param {integer} expectedTab.index
|
||||
* @param {boolean} expectedTab.mailTab
|
||||
*/
|
||||
async function checkClickedEvent(extension, expectedInfo, expectedTab) {
|
||||
let [info, tab] = await extension.awaitMessage("onClicked");
|
||||
|
||||
Assert.equal(info.selectionText, expectedInfo.selectionText, "selectionText");
|
||||
Assert.equal(info.linkText, expectedInfo.linkText, "linkText");
|
||||
if (expectedInfo.menuItemId) {
|
||||
Assert.equal(info.menuItemId, expectedInfo.menuItemId, "menuItemId");
|
||||
}
|
||||
|
||||
for (let infoKey of ["pageUrl", "linkUrl", "srcUrl"]) {
|
||||
Assert.equal(
|
||||
!!info[infoKey],
|
||||
!!expectedInfo[infoKey],
|
||||
`${infoKey} in info`
|
||||
);
|
||||
if (expectedInfo[infoKey]) {
|
||||
if (typeof expectedInfo[infoKey] == "string") {
|
||||
Assert.equal(info[infoKey], expectedInfo[infoKey]);
|
||||
} else {
|
||||
Assert.ok(info[infoKey].match(expectedInfo[infoKey]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.equal(tab.active, expectedTab.active, "tab is active");
|
||||
Assert.equal(tab.index, expectedTab.index, "tab index");
|
||||
Assert.equal(tab.mailTab, expectedTab.mailTab, "tab is mailTab");
|
||||
}
|
||||
|
||||
async function getMenuExtension(manifest) {
|
||||
let details = {
|
||||
files: {
|
||||
"background.js": async () => {
|
||||
let contexts = [
|
||||
"audio",
|
||||
"compose_action",
|
||||
"message_display_action",
|
||||
"editable",
|
||||
"frame",
|
||||
"image",
|
||||
"link",
|
||||
"page",
|
||||
"password",
|
||||
"selection",
|
||||
"tab",
|
||||
"video",
|
||||
"message_list",
|
||||
"folder_pane",
|
||||
"compose_attachments",
|
||||
"tools_menu",
|
||||
];
|
||||
if (browser.runtime.getManifest().manifest_version > 2) {
|
||||
contexts.push("action");
|
||||
} else {
|
||||
contexts.push("browser_action");
|
||||
}
|
||||
|
||||
for (let context of contexts) {
|
||||
browser.menus.create({
|
||||
id: context,
|
||||
title: context,
|
||||
contexts: [context],
|
||||
});
|
||||
}
|
||||
|
||||
browser.menus.onShown.addListener((...args) => {
|
||||
browser.test.sendMessage("onShown", args);
|
||||
});
|
||||
|
||||
browser.menus.onClicked.addListener((...args) => {
|
||||
browser.test.sendMessage("onClicked", args);
|
||||
});
|
||||
browser.test.sendMessage("menus-created");
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: "menus@mochi.test",
|
||||
},
|
||||
},
|
||||
background: { scripts: ["background.js"] },
|
||||
...manifest,
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
};
|
||||
|
||||
if (!details.manifest.permissions) {
|
||||
details.manifest.permissions = [];
|
||||
}
|
||||
details.manifest.permissions.push("menus");
|
||||
console.log(JSON.stringify(details, 2));
|
||||
let extension = ExtensionTestUtils.loadExtension(details);
|
||||
if (details.manifest.host_permissions) {
|
||||
// MV3 has to manually grant the requested permission.
|
||||
await ExtensionPermissions.add("menus@mochi.test", {
|
||||
permissions: [],
|
||||
origins: details.manifest.host_permissions,
|
||||
});
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
async function subtest_content(
|
||||
extension,
|
||||
extensionHasPermission,
|
||||
browser,
|
||||
pageUrl,
|
||||
tab
|
||||
) {
|
||||
if (
|
||||
browser.webProgress?.isLoadingDocument ||
|
||||
!browser.currentURI ||
|
||||
browser.currentURI?.spec == "about:blank"
|
||||
) {
|
||||
await BrowserTestUtils.browserLoaded(
|
||||
browser,
|
||||
undefined,
|
||||
url => url != "about:blank"
|
||||
);
|
||||
}
|
||||
|
||||
let ownerDocument = browser.ownerDocument;
|
||||
let menu = ownerDocument.getElementById(browser.getAttribute("context"));
|
||||
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser);
|
||||
|
||||
info("Test a part of the page with no content.");
|
||||
|
||||
await rightClickOnContent(menu, "body", browser);
|
||||
Assert.ok(menu.querySelector("#menus_mochi_test-menuitem-_page"));
|
||||
let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
|
||||
menu.hidePopup();
|
||||
await hiddenPromise;
|
||||
// Sometimes, the popup will open then instantly disappear. It seems to
|
||||
// still be hiding after the previous appearance. If we wait a little bit,
|
||||
// this doesn't happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
|
||||
await checkShownEvent(
|
||||
extension,
|
||||
{
|
||||
menuIds: ["page"],
|
||||
contexts: ["page", "all"],
|
||||
pageUrl: extensionHasPermission ? pageUrl : undefined,
|
||||
},
|
||||
tab
|
||||
);
|
||||
|
||||
info("Test selection.");
|
||||
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
let text = content.document.querySelector("p");
|
||||
content.getSelection().selectAllChildren(text);
|
||||
});
|
||||
await rightClickOnContent(menu, "p", browser);
|
||||
Assert.ok(menu.querySelector("#menus_mochi_test-menuitem-_selection"));
|
||||
await checkShownEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl: extensionHasPermission ? pageUrl : undefined,
|
||||
selectionText: extensionHasPermission ? "This is text." : undefined,
|
||||
menuIds: ["selection"],
|
||||
contexts: ["selection", "all"],
|
||||
},
|
||||
tab
|
||||
);
|
||||
|
||||
hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
|
||||
let clickedPromise = checkClickedEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl,
|
||||
selectionText: "This is text.",
|
||||
},
|
||||
tab
|
||||
);
|
||||
menu.activateItem(
|
||||
menu.querySelector("#menus_mochi_test-menuitem-_selection")
|
||||
);
|
||||
await clickedPromise;
|
||||
await hiddenPromise;
|
||||
|
||||
// Sometimes, the popup will open then instantly disappear. It seems to
|
||||
// still be hiding after the previous appearance. If we wait a little bit,
|
||||
// this doesn't happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser); // Select nothing.
|
||||
|
||||
info("Test link.");
|
||||
|
||||
await rightClickOnContent(menu, "a", browser);
|
||||
Assert.ok(menu.querySelector("#menus_mochi_test-menuitem-_link"));
|
||||
await checkShownEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl: extensionHasPermission ? pageUrl : undefined,
|
||||
menuIds: ["link"],
|
||||
contexts: ["link", "all"],
|
||||
},
|
||||
tab
|
||||
);
|
||||
|
||||
hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
|
||||
clickedPromise = checkClickedEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl,
|
||||
linkUrl: "http://mochi.test:8888/",
|
||||
linkText: "This is a link with text.",
|
||||
},
|
||||
tab
|
||||
);
|
||||
menu.activateItem(menu.querySelector("#menus_mochi_test-menuitem-_link"));
|
||||
await clickedPromise;
|
||||
await hiddenPromise;
|
||||
// Sometimes, the popup will open then instantly disappear. It seems to
|
||||
// still be hiding after the previous appearance. If we wait a little bit,
|
||||
// this doesn't happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
|
||||
info("Test image.");
|
||||
|
||||
await rightClickOnContent(menu, "img", browser);
|
||||
Assert.ok(menu.querySelector("#menus_mochi_test-menuitem-_image"));
|
||||
await checkShownEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl: extensionHasPermission ? pageUrl : undefined,
|
||||
menuIds: ["image"],
|
||||
contexts: ["image", "all"],
|
||||
},
|
||||
tab
|
||||
);
|
||||
|
||||
hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
|
||||
clickedPromise = checkClickedEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl,
|
||||
srcUrl: `${URL_BASE}/tb-logo.png`,
|
||||
},
|
||||
tab
|
||||
);
|
||||
menu.activateItem(menu.querySelector("#menus_mochi_test-menuitem-_image"));
|
||||
await clickedPromise;
|
||||
await hiddenPromise;
|
||||
// Sometimes, the popup will open then instantly disappear. It seems to
|
||||
// still be hiding after the previous appearance. If we wait a little bit,
|
||||
// this doesn't happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
}
|
||||
|
||||
// Test UI elements which have been made accessible for the menus API.
|
||||
// Assumed to be run after subtest_content, so we know everything has finished
|
||||
// loading.
|
||||
async function subtest_element(
|
||||
extension,
|
||||
extensionHasPermission,
|
||||
element,
|
||||
pageUrl,
|
||||
tab
|
||||
) {
|
||||
for (let selectedTest of [false, true]) {
|
||||
element.focus();
|
||||
if (selectedTest) {
|
||||
element.value = "This is selected text.";
|
||||
element.select();
|
||||
} else {
|
||||
element.value = "";
|
||||
}
|
||||
|
||||
let event = await rightClick(element.ownerGlobal, element);
|
||||
let menu = event.target;
|
||||
let trigger = menu.triggerNode;
|
||||
let menuitem = menu.querySelector("#menus_mochi_test-menuitem-_editable");
|
||||
Assert.equal(
|
||||
element.id,
|
||||
trigger.id,
|
||||
"Contextmenu of correct element has been triggered."
|
||||
);
|
||||
Assert.equal(
|
||||
menuitem.id,
|
||||
"menus_mochi_test-menuitem-_editable",
|
||||
"Contextmenu includes menu."
|
||||
);
|
||||
|
||||
await checkShownEvent(
|
||||
extension,
|
||||
{
|
||||
menuIds: selectedTest ? ["editable", "selection"] : ["editable"],
|
||||
contexts: selectedTest
|
||||
? ["editable", "selection", "all"]
|
||||
: ["editable", "all"],
|
||||
pageUrl: extensionHasPermission ? pageUrl : undefined,
|
||||
selectionText:
|
||||
extensionHasPermission && selectedTest
|
||||
? "This is selected text."
|
||||
: undefined,
|
||||
},
|
||||
tab
|
||||
);
|
||||
|
||||
// With text being selected, there will be two "context" entries in an
|
||||
// extension submenu. Open the submenu.
|
||||
let submenu = null;
|
||||
if (selectedTest) {
|
||||
for (let foundMenu of menu.querySelectorAll(
|
||||
"[id^='menus_mochi_test-menuitem-']"
|
||||
)) {
|
||||
if (!foundMenu.id.startsWith("menus_mochi_test-menuitem-_")) {
|
||||
submenu = foundMenu;
|
||||
}
|
||||
}
|
||||
Assert.ok(submenu, "Submenu found.");
|
||||
let submenuPromise = BrowserTestUtils.waitForEvent(
|
||||
element.ownerGlobal,
|
||||
"popupshown"
|
||||
);
|
||||
submenu.openMenu(true);
|
||||
await submenuPromise;
|
||||
}
|
||||
|
||||
let hiddenPromise = BrowserTestUtils.waitForEvent(
|
||||
element.ownerGlobal,
|
||||
"popuphidden"
|
||||
);
|
||||
let clickedPromise = checkClickedEvent(
|
||||
extension,
|
||||
{
|
||||
pageUrl,
|
||||
selectionText: selectedTest ? "This is selected text." : undefined,
|
||||
},
|
||||
tab
|
||||
);
|
||||
if (submenu) {
|
||||
submenu.menupopup.activateItem(menuitem);
|
||||
} else {
|
||||
menu.activateItem(menuitem);
|
||||
}
|
||||
await clickedPromise;
|
||||
await hiddenPromise;
|
||||
|
||||
// Sometimes, the popup will open then instantly disappear. It seems to
|
||||
// still be hiding after the previous appearance. If we wait a little bit,
|
||||
// this doesn't happen.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче