--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -43,34 +43,16 @@ var BrowserPageActions = {
return this.mainViewBodyNode = this.mainViewNode.querySelector(".panel-subview-body");
},
/**
* Inits. Call to init.
*/
init() {
this.placeAllActions();
-
- // Add a click listener to #page-action-buttons for blocking clicks on
- // disabled actions in the urlbar. Normally we'd do this by setting
- // `pointer-events: none` in the CSS, but that also blocks context menu
- // events, and we want the context menu even on disabled actions so that
- // they can be removed from the urlbar.
- this.mainButtonNode.parentNode.addEventListener("click", event => {
- if (event.button == 2) {
- // Let context-clicks be handled normally.
- return;
- }
- let node = event.originalTarget;
- let action = this.actionForNode(node);
- if (action && action.getDisabled(window)) {
- event.preventDefault();
- event.stopPropagation();
- }
- }, true);
},
/**
* Places all registered actions.
*/
placeAllActions() {
// Place actions in the panel. Notify of onBeforePlacedInWindow too.
for (let action of PageActions.actions) {
@@ -81,17 +63,17 @@ var BrowserPageActions = {
// Place actions in the urlbar. Do this in reverse order. The reason is
// subtle. If there were no urlbar nodes already in markup (like the
// bookmark star button), then doing this in forward order would be fine.
// Forward order means that the insert-before relationship is always broken:
// there's never a next-sibling node before which to insert a new node, so
// node.insertBefore() is always passed null, and nodes are always appended.
// That will break the position of nodes that should be inserted before
// nodes that are in markup, which in turn can break other nodes.
- let actionsInUrlbar = PageActions.actionsInUrlbar;
+ let actionsInUrlbar = PageActions.actionsInUrlbar(window);
for (let i = actionsInUrlbar.length - 1; i >= 0; i--) {
let action = actionsInUrlbar[i];
this.placeActionInUrlbar(action);
}
},
/**
* Adds or removes as necessary DOM nodes for the given action.
@@ -107,31 +89,29 @@ var BrowserPageActions = {
/**
* Adds or removes as necessary DOM nodes for the action in the panel.
*
* @param action (PageActions.Action, required)
* The action to place.
*/
placeActionInPanel(action) {
- let insertBeforeID = PageActions.nextActionIDInPanel(action);
let id = this.panelButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
if (!node) {
let panelViewNode;
[node, panelViewNode] = this._makePanelButtonNodeForAction(action);
node.id = id;
- let insertBeforeNode = null;
- if (insertBeforeID) {
- let insertBeforeNodeID =
- this.panelButtonNodeIDForActionID(insertBeforeID);
- insertBeforeNode = document.getElementById(insertBeforeNodeID);
- }
+ let insertBeforeID = PageActions.nextActionIDInPanel(action);
+ let insertBeforeNode =
+ insertBeforeID ? this.panelButtonNodeForActionID(insertBeforeID) :
+ null;
this.mainViewBodyNode.insertBefore(node, insertBeforeNode);
this.updateAction(action);
+ this._updateActionDisabledInPanel(action);
action.onPlacedInPanel(node);
if (panelViewNode) {
action.subview.onPlaced(panelViewNode);
}
}
},
_makePanelButtonNodeForAction(action) {
@@ -351,21 +331,20 @@ var BrowserPageActions = {
/**
* Adds or removes as necessary a DOM node for the given action in the urlbar.
*
* @param action (PageActions.Action, required)
* The action to place.
*/
placeActionInUrlbar(action) {
- let insertBeforeID = PageActions.nextActionIDInUrlbar(action);
let id = this.urlbarButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
- if (!action.shownInUrlbar) {
+ if (!action.shouldShowInUrlbar(window)) {
if (node) {
if (action.__urlbarNodeInMarkup) {
node.hidden = true;
} else {
node.remove();
}
}
return;
@@ -377,49 +356,42 @@ var BrowserPageActions = {
node.hidden = false;
} else if (!node) {
newlyPlaced = true;
node = this._makeUrlbarButtonNode(action);
node.id = id;
}
if (newlyPlaced) {
- let parentNode = this.mainButtonNode.parentNode;
- let insertBeforeNode = null;
- if (insertBeforeID) {
- let insertBeforeNodeID =
- this.urlbarButtonNodeIDForActionID(insertBeforeID);
- insertBeforeNode = document.getElementById(insertBeforeNodeID);
- }
- parentNode.insertBefore(node, insertBeforeNode);
+ let insertBeforeID = PageActions.nextActionIDInUrlbar(window, action);
+ let insertBeforeNode =
+ insertBeforeID ? this.urlbarButtonNodeForActionID(insertBeforeID) :
+ null;
+ this.mainButtonNode.parentNode.insertBefore(node, insertBeforeNode);
this.updateAction(action);
action.onPlacedInUrlbar(node);
// urlbar buttons should always have tooltips, so if the node doesn't have
// one, then as a last resort use the label of the corresponding panel
// button. Why not set tooltiptext to action.title when the node is
// created? Because the consumer may set a title dynamically.
if (!node.hasAttribute("tooltiptext")) {
- let panelNodeID = this.panelButtonNodeIDForActionID(action.id);
- let panelNode = document.getElementById(panelNodeID);
+ let panelNode = this.panelButtonNodeForActionID(action.id);
if (panelNode) {
node.setAttribute("tooltiptext", panelNode.getAttribute("label"));
}
}
}
},
_makeUrlbarButtonNode(action) {
let buttonNode = document.createElement("image");
buttonNode.classList.add("urlbar-icon", "urlbar-page-action");
buttonNode.setAttribute("actionid", action.id);
buttonNode.setAttribute("role", "button");
- buttonNode.addEventListener("contextmenu", event => {
- BrowserPageActions.onContextMenu(event);
- });
if (action.nodeAttributes) {
for (let name in action.nodeAttributes) {
buttonNode.setAttribute(name, action.nodeAttributes[name]);
}
}
buttonNode.addEventListener("click", event => {
this.doCommandForAction(action, event, buttonNode);
});
@@ -434,18 +406,17 @@ var BrowserPageActions = {
*/
removeAction(action) {
this._removeActionFromPanel(action);
this._removeActionFromUrlbar(action);
action.onRemovedFromWindow(window);
},
_removeActionFromPanel(action) {
- let id = this.panelButtonNodeIDForActionID(action.id);
- let node = document.getElementById(id);
+ let node = this.panelButtonNodeForActionID(action.id);
if (node) {
node.remove();
}
if (action.subview) {
let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
let panelViewNode = document.getElementById(panelViewNodeID);
if (panelViewNode) {
panelViewNode.remove();
@@ -461,127 +432,120 @@ var BrowserPageActions = {
);
if (separator) {
separator.remove();
}
}
},
_removeActionFromUrlbar(action) {
- let id = this.urlbarButtonNodeIDForActionID(action.id);
- let node = document.getElementById(id);
+ let node = this.urlbarButtonNodeForActionID(action.id);
if (node) {
node.remove();
}
},
/**
* Updates the DOM nodes of an action to reflect either a changed property or
* all properties.
*
* @param action (PageActions.Action, required)
* The action to update.
- * @param nameToUpdate (string, optional)
- * The property's name. If not given, then DOM nodes will be updated
- * to reflect the current values of all properties.
+ * @param propertyName (string, optional)
+ * The name of the property to update. If not given, then DOM nodes
+ * will be updated to reflect the current values of all properties.
*/
- updateAction(action, nameToUpdate = null) {
- let names = nameToUpdate ? [nameToUpdate] : [
- "disabled",
+ updateAction(action, propertyName = null) {
+ let propertyNames = propertyName ? [propertyName] : [
"iconURL",
"title",
"tooltip",
];
- for (let name of names) {
+ for (let name of propertyNames) {
let upper = name[0].toUpperCase() + name.substr(1);
this[`_updateAction${upper}`](action);
}
},
_updateActionDisabled(action) {
- let nodeIDs = [
- this.panelButtonNodeIDForActionID(action.id),
- this.urlbarButtonNodeIDForActionID(action.id),
- ];
- for (let nodeID of nodeIDs) {
- let node = document.getElementById(nodeID);
- if (node) {
- if (action.getDisabled(window)) {
- node.setAttribute("disabled", "true");
- } else {
- node.removeAttribute("disabled");
- }
+ this._updateActionDisabledInPanel(action);
+ this.placeActionInUrlbar(action);
+ },
+
+ _updateActionDisabledInPanel(action) {
+ let panelButton = this.panelButtonNodeForActionID(action.id);
+ if (panelButton) {
+ if (action.getDisabled(window)) {
+ panelButton.setAttribute("disabled", "true");
+ } else {
+ panelButton.removeAttribute("disabled");
}
}
},
_updateActionIconURL(action) {
- let nodeIDs = [
- this.panelButtonNodeIDForActionID(action.id),
- this.urlbarButtonNodeIDForActionID(action.id),
- ];
- for (let nodeID of nodeIDs) {
- let node = document.getElementById(nodeID);
- if (node) {
- for (let size of [16, 32]) {
- let url = action.iconURLForSize(size, window);
- let prop = `--pageAction-image-${size}px`;
- if (url) {
- node.style.setProperty(prop, `url("${url}")`);
- } else {
- node.style.removeProperty(prop);
- }
+ let nodes = [
+ this.panelButtonNodeForActionID(action.id),
+ this.urlbarButtonNodeForActionID(action.id),
+ ].filter(n => !!n);
+ for (let node of nodes) {
+ for (let size of [16, 32]) {
+ let url = action.iconURLForSize(size, window);
+ let prop = `--pageAction-image-${size}px`;
+ if (url) {
+ node.style.setProperty(prop, `url("${url}")`);
+ } else {
+ node.style.removeProperty(prop);
}
}
}
},
_updateActionTitle(action) {
let title = action.getTitle(window);
if (!title) {
// `title` is a required action property, but the bookmark action's is an
// empty string since its actual title is set via
// BookmarkingUI.updateBookmarkPageMenuItem(). The purpose of this early
// return is to ignore that empty title.
return;
}
- let attrNamesByNodeIDFnName = {
- panelButtonNodeIDForActionID: "label",
- urlbarButtonNodeIDForActionID: "aria-label",
+ let attrNamesByNodeFnName = {
+ panelButtonNodeForActionID: "label",
+ urlbarButtonNodeForActionID: "aria-label",
};
- for (let [fnName, attrName] of Object.entries(attrNamesByNodeIDFnName)) {
- let nodeID = this[fnName](action.id);
- let node = document.getElementById(nodeID);
+ for (let [fnName, attrName] of Object.entries(attrNamesByNodeFnName)) {
+ let node = this[fnName](action.id);
if (node) {
node.setAttribute(attrName, title);
}
}
// tooltiptext falls back to the title, so update it, too.
this._updateActionTooltip(action);
},
_updateActionTooltip(action) {
- let node = document.getElementById(
- this.urlbarButtonNodeIDForActionID(action.id)
- );
+ let node = this.urlbarButtonNodeForActionID(action.id);
if (node) {
let tooltip = action.getTooltip(window) || action.getTitle(window);
node.setAttribute("tooltiptext", tooltip);
}
},
doCommandForAction(action, event, buttonNode) {
if (event && event.type == "click" && event.button != 0) {
return;
}
PageActions.logTelemetry("used", action, buttonNode);
// If we're in the panel, open a subview inside the panel:
// Note that we can't use this.panelNode.contains(buttonNode) here
- // because of XBL boundaries breaking ELement.contains.
- if (action.subview && buttonNode && buttonNode.closest("panel") == this.panelNode) {
+ // because of XBL boundaries breaking Element.contains.
+ if (action.subview &&
+ buttonNode &&
+ buttonNode.closest("panel") == this.panelNode) {
let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
let panelViewNode = document.getElementById(panelViewNodeID);
action.subview.onShowing(panelViewNode);
this.multiViewNode.showSubView(panelViewNode, buttonNode);
return;
}
// Otherwise, hide the main popup in case it was open:
this.panelNode.hidePopup();
@@ -623,27 +587,49 @@ var BrowserPageActions = {
actionID = this._actionIDForNodeID(n.id);
action = PageActions.actionForID(actionID);
}
}
return action;
},
/**
+ * The given action's top-level button in the main panel.
+ *
+ * @param actionID (string, required)
+ * The action ID.
+ * @return (DOM node) The action's button in the main panel.
+ */
+ panelButtonNodeForActionID(actionID) {
+ return document.getElementById(this.panelButtonNodeIDForActionID(actionID));
+ },
+
+ /**
* The ID of the given action's top-level button in the main panel.
*
* @param actionID (string, required)
* The action ID.
* @return (string) The ID of the action's button in the main panel.
*/
panelButtonNodeIDForActionID(actionID) {
return `pageAction-panel-${actionID}`;
},
/**
+ * The given action's button in the urlbar.
+ *
+ * @param actionID (string, required)
+ * The action ID.
+ * @return (DOM node) The action's urlbar button node.
+ */
+ urlbarButtonNodeForActionID(actionID) {
+ return document.getElementById(this.urlbarButtonNodeIDForActionID(actionID));
+ },
+
+ /**
* The ID of the given action's button in the urlbar.
*
* @param actionID (string, required)
* The action ID.
* @return (string) The ID of the action's urlbar button node.
*/
urlbarButtonNodeIDForActionID(actionID) {
let action = PageActions.actionForID(actionID);
@@ -717,86 +703,95 @@ var BrowserPageActions = {
* Show the page action panel
*
* @param event (DOM event, optional)
* The event that triggers showing the panel. (such as a mouse click,
* if the user clicked something to open the panel)
*/
showPanel(event = null) {
for (let action of PageActions.actions) {
- let buttonNodeID = this.panelButtonNodeIDForActionID(action.id);
- let buttonNode = document.getElementById(buttonNodeID);
+ let buttonNode = this.panelButtonNodeForActionID(action.id);
action.onShowingInPanel(buttonNode);
}
this.panelNode.hidden = false;
this.panelNode.addEventListener("popuphiding", () => {
this.mainButtonNode.removeAttribute("open");
}, {once: true});
this.mainButtonNode.setAttribute("open", "true");
this.panelNode.openPopup(this.mainButtonNode, {
position: "bottomcenter topright",
triggerEvent: event,
});
},
/**
- * Call this on the contextmenu event. Note that this is called before
- * onContextMenuShowing.
- *
- * @param event (DOM event, required)
- * The contextmenu event.
- */
- onContextMenu(event) {
- let node = event.originalTarget;
- this._contextAction = this.actionForNode(node);
- // Don't show the menu if there's no action where the user clicked!
- if (!this._contextAction) {
- event.preventDefault();
- }
- },
-
- /**
* Call this on the context menu's popupshowing event.
*
* @param event (DOM event, required)
* The popupshowing event.
* @param popup (DOM node, required)
* The context menu popup DOM node.
*/
onContextMenuShowing(event, popup) {
if (event.target != popup) {
return;
}
- // Right now there's only one item in the context menu, to toggle the
- // context action's shown-in-urlbar state. Update it now.
- let toggleItem = popup.firstChild;
- let toggleItemLabel = null;
- if (this._contextAction) {
- toggleItem.disabled = false;
- if (this._contextAction.shownInUrlbar) {
- toggleItemLabel = toggleItem.getAttribute("remove-label");
- }
+
+ this._contextAction = this.actionForNode(popup.triggerNode);
+ if (!this._contextAction) {
+ event.preventDefault();
+ return;
}
- if (!toggleItemLabel) {
- toggleItemLabel = toggleItem.getAttribute("add-label");
+
+ let state;
+ if (this._contextAction._isBuiltIn) {
+ state =
+ this._contextAction.pinnedToUrlbar ?
+ "builtInPinned" :
+ "builtInUnpinned";
+ } else {
+ state =
+ this._contextAction.pinnedToUrlbar ?
+ "extensionPinned" :
+ "extensionUnpinned";
}
- toggleItem.label = toggleItemLabel;
+ popup.setAttribute("state", state);
},
/**
- * Call this from the context menu's toggle menu item.
+ * Call this from the menu item in the context menu that toggles pinning.
*/
- toggleShownInUrlbarForContextAction() {
+ togglePinningForContextAction() {
if (!this._contextAction) {
return;
}
- let telemetryType = this._contextAction.shownInUrlbar ? "removed" : "added";
- PageActions.logTelemetry(telemetryType, this._contextAction);
- this._contextAction.shownInUrlbar = !this._contextAction.shownInUrlbar;
+ let action = this._contextAction;
+ this._contextAction = null;
+
+ let telemetryType = action.pinnedToUrlbar ? "removed" : "added";
+ PageActions.logTelemetry(telemetryType, action);
+
+ action.pinnedToUrlbar = !action.pinnedToUrlbar;
+ },
+
+ /**
+ * Call this from the menu item in the context menu that opens about:addons.
+ */
+ openAboutAddonsForContextAction() {
+ if (!this._contextAction) {
+ return;
+ }
+ let action = this._contextAction;
+ this._contextAction = null;
+
+ PageActions.logTelemetry("managed", action);
+
+ let viewID = "addons://detail/" + encodeURIComponent(action.extensionID);
+ window.BrowserOpenAddonsMgr(viewID);
},
_contextAction: null,
/**
* Titles for a few of the built-in actions are defined in DTD, but the
* actions are created in JS. So what we do is for each title, set an
* attribute in markup on the main page action panel whose value is the DTD
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1415,16 +1415,27 @@ toolbarpaletteitem[place="palette"][hidd
.pageAction-panel-button > .toolbarbutton-icon {
list-style-image: var(--pageAction-image-32px, inherit);
}
.urlbar-page-action {
list-style-image: var(--pageAction-image-32px, inherit);
}
}
+/* Page action context menu */
+#pageActionContextMenu > .pageActionContextMenuItem {
+ visibility: collapse;
+}
+#pageActionContextMenu[state=builtInPinned] > .pageActionContextMenuItem.builtInPinned,
+#pageActionContextMenu[state=builtInUnpinned] > .pageActionContextMenuItem.builtInUnpinned,
+#pageActionContextMenu[state=extensionPinned] > .pageActionContextMenuItem.extensionPinned,
+#pageActionContextMenu[state=extensionUnpinned] > .pageActionContextMenuItem.extensionUnpinned {
+ visibility: visible;
+}
+
/* WebExtension Sidebars */
#sidebar-box[sidebarcommand$="-sidebar-action"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
list-style-image: var(--webextension-menuitem-image, inherit);
-moz-context-properties: fill;
fill: currentColor;
width: 16px;
height: 16px;
}
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -417,17 +417,16 @@
emailLink-title="&emailPageCmd.label;"
sendToDevice-title="&pageAction.sendTabToDevice.label;"
sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;">
<photonpanelmultiview id="pageActionPanelMultiView"
mainViewId="pageActionPanelMainView"
viewCacheId="appMenu-viewCache">
<panelview id="pageActionPanelMainView"
context="pageActionContextMenu"
- oncontextmenu="BrowserPageActions.onContextMenu(event);"
class="PanelUI-subView">
<vbox class="panel-subview-body"/>
</panelview>
</photonpanelmultiview>
</panel>
<panel id="pageActionFeedback"
role="alert"
type="arrow"
@@ -441,21 +440,32 @@
<hbox id="pageActionFeedbackAnimatableBox">
<image id="pageActionFeedbackAnimatableImage"/>
</hbox>
<label id="pageActionFeedbackMessage"/>
</panel>
<menupopup id="pageActionContextMenu"
onpopupshowing="BrowserPageActions.onContextMenuShowing(event, this);">
- <menuitem id="pageActionContextMenu-toggleUrlbar"
- add-label="&pageAction.addToUrlbar.label;"
- remove-label="&pageAction.removeFromUrlbar.label;"
+ <menuitem class="pageActionContextMenuItem builtInUnpinned"
label="&pageAction.addToUrlbar.label;"
- oncommand="BrowserPageActions.toggleShownInUrlbarForContextAction();"/>
+ oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+ <menuitem class="pageActionContextMenuItem builtInPinned"
+ label="&pageAction.removeFromUrlbar.label;"
+ oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+ <menuitem class="pageActionContextMenuItem extensionUnpinned"
+ label="&pageAction.allowInUrlbar.label;"
+ oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+ <menuitem class="pageActionContextMenuItem extensionPinned"
+ label="&pageAction.disallowInUrlbar.label;"
+ oncommand="BrowserPageActions.togglePinningForContextAction();"/>
+ <menuseparator class="pageActionContextMenuItem extensionPinned extensionUnpinned"/>
+ <menuitem class="pageActionContextMenuItem extensionPinned extensionUnpinned"
+ label="&pageAction.manageExtension.label;"
+ oncommand="BrowserPageActions.openAboutAddonsForContextAction();"/>
</menupopup>
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip"/>
<tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
<tooltip id="back-button-tooltip">
@@ -854,19 +864,17 @@
<label id="identity-icon-label" class="plain" flex="1"/>
<label id="identity-icon-country-label" class="plain"/>
</hbox>
</box>
<box id="urlbar-display-box" align="center">
<label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
<label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
</box>
- <hbox id="page-action-buttons"
- context="pageActionContextMenu"
- oncontextmenu="BrowserPageActions.onContextMenu(event);">
+ <hbox id="page-action-buttons" context="pageActionContextMenu">
<hbox id="userContext-icons" hidden="true">
<label id="userContext-label"/>
<image id="userContext-indicator"/>
</hbox>
<image id="reader-mode-button"
class="urlbar-icon urlbar-page-action"
role="button"
hidden="true"
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -152,18 +152,18 @@ add_task(async function copyURLFromPanel
add_task(async function copyURLFromURLBar() {
// Open an actionable page so that the main page action button appears. (It
// does not appear on about:blank for example.)
let url = "http://example.com/";
await BrowserTestUtils.withNewTab(url, async () => {
// Add action to URL bar.
let action = PageActions._builtInActions.find(a => a.id == "copyURL");
- action.shownInUrlbar = true;
- registerCleanupFunction(() => action.shownInUrlbar = false);
+ action.pinnedToUrlbar = true;
+ registerCleanupFunction(() => action.pinnedToUrlbar = false);
let copyURLButton =
document.getElementById("pageAction-urlbar-copyURL");
let feedbackShownPromise = promisePanelShown("pageActionFeedback");
EventUtils.synthesizeMouseAtCenter(copyURLButton, {});
await feedbackShownPromise;
let panel = document.getElementById("pageActionFeedback");
@@ -539,17 +539,17 @@ add_task(async function sendToDevice_inU
let cleanUp = () => {
sandbox.restore();
};
registerCleanupFunction(cleanUp);
// Add Send to Device to the urlbar.
let action = PageActions.actionForID("sendToDevice");
- action.shownInUrlbar = true;
+ action.pinnedToUrlbar = true;
// Click it to open its panel.
let urlbarButton = document.getElementById(
BrowserPageActions.urlbarButtonNodeIDForActionID(action.id)
);
Assert.ok(!urlbarButton.disabled);
let panelPromise =
promisePanelShown(BrowserPageActions._activatedActionPanelID);
@@ -617,17 +617,17 @@ add_task(async function sendToDevice_inU
Assert.equal(
BrowserPageActionFeedback.panelNode.anchorNode.id,
urlbarButton.id
);
info("Waiting for the Sent! notification panel to close");
await promisePanelHidden(BrowserPageActionFeedback.panelNode.id);
// Remove Send to Device from the urlbar.
- action.shownInUrlbar = false;
+ action.pinnedToUrlbar = false;
cleanUp();
});
});
add_task(async function contextMenu() {
// Open an actionable page so that the main page action button appears.
let url = "http://example.com/";
@@ -637,24 +637,24 @@ add_task(async function contextMenu() {
let bookmarkButton = document.getElementById("pageAction-panel-bookmark");
let contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
- // The context menu should show "Remove from Address Bar". Click it.
- let contextMenuNode = document.getElementById("pageActionContextMenu");
- Assert.equal(contextMenuNode.childNodes.length, 1,
+ // The context menu should show the "remove" item. Click it.
+ let menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 1,
"Context menu has one child");
- Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
+ Assert.equal(menuItems[0].label, "Remove from Address Bar",
"Context menu is in the 'remove' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
- EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar. In this case, the bookmark
// star, the node in the urlbar should be hidden.
let starButtonBox = document.getElementById("star-button-box");
await BrowserTestUtils.waitForCondition(() => {
return starButtonBox.hidden;
}, "Waiting for star button to become hidden");
@@ -663,67 +663,71 @@ add_task(async function contextMenu() {
// panel remains open.)
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
- // The context menu should show "Add to Address Bar". Click it.
- Assert.equal(contextMenuNode.childNodes.length, 1,
+ // The context menu should show the "add" item. Click it.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 1,
"Context menu has one child");
- Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
+ Assert.equal(menuItems[0].label, "Add to Address Bar",
"Context menu is in the 'add' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
- EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be added to the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return !starButtonBox.hidden;
}, "Waiting for star button to become unhidden");
// Open the context menu on the bookmark star in the urlbar.
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(starButtonBox, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
- // The context menu should show "Remove from Address Bar". Click it.
- Assert.equal(contextMenuNode.childNodes.length, 1,
+ // The context menu should show the "remove" item. Click it.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 1,
"Context menu has one child");
- Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
+ Assert.equal(menuItems[0].label, "Remove from Address Bar",
"Context menu is in the 'remove' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
- EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return starButtonBox.hidden;
}, "Waiting for star button to become hidden");
// Finally, add the bookmark star back to the urlbar so that other tests
// that rely on it are OK.
await promisePageActionPanelOpen();
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(bookmarkButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
- Assert.equal(contextMenuNode.childNodes.length, 1,
+
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 1,
"Context menu has one child");
- Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
+ Assert.equal(menuItems[0].label, "Add to Address Bar",
"Context menu is in the 'add' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
- EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
await BrowserTestUtils.waitForCondition(() => {
return !starButtonBox.hidden;
}, "Waiting for star button to become unhidden");
});
// urlbar tests that run after this one can break if the mouse is left over
// the area where the urlbar popup appears, which seems to happen due to the
@@ -770,8 +774,15 @@ function checkSendToDeviceItems(expected
if (name == "label") {
attrVal = attrVal.normalize("NFKC"); // There's a bug with …
}
Assert.equal(attrVal, expected.attrs[name]);
}
}
}
}
+
+function collectContextMenuItems() {
+ let contextMenu = document.getElementById("pageActionContextMenu");
+ return Array.filter(contextMenu.childNodes, node => {
+ return window.getComputedStyle(node).visibility == "visible";
+ });
+}
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -61,19 +61,20 @@ this.pageAction = class extends Extensio
this.defaults.icon = await StartupCache.get(
extension, ["pageAction", "default_icon"],
() => IconDetails.normalize({path: options.default_icon}, extension));
if (!this.browserPageAction) {
this.browserPageAction = PageActions.addAction(new PageActions.Action({
id: widgetId,
+ extensionID: extension.id,
title: this.defaults.title,
iconURL: this.getIconData(this.defaults.icon),
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
disabled: true,
onCommand: (event, buttonNode) => {
this.handleClick(event.target.ownerGlobal);
},
onBeforePlacedInWindow: browserWindow => {
if (this.extension.hasPermission("menus") ||
this.extension.hasPermission("contextMenus")) {
browserWindow.document.addEventListener("popupshowing", this);
--- a/browser/components/extensions/test/xpcshell/test_ext_pageAction_shutdown.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_pageAction_shutdown.js
@@ -19,17 +19,17 @@ createAppInfo("xpcshell@tests.mozilla.or
// This is copied and pasted from ExtensionPopups.jsm. It's used as the
// PageActions action ID. See ext-pageAction.js.
function makeWidgetId(id) {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
}
-// Tests that the shownInUrlbar property of the PageActions.Action object
+// Tests that the pinnedToUrlbar property of the PageActions.Action object
// backing the extension's page action persists across app restarts.
add_task(async function testAppShutdown() {
let extensionData = {
useAddonManager: "permanent",
manifest: {
page_action: {
default_title: "test_ext_pageAction_shutdown.js",
browser_style: false,
@@ -39,41 +39,41 @@ add_task(async function testAppShutdown(
// Simulate starting up the app.
PageActions.init();
await promiseStartupManager();
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
- // Get the PageAction.Action object. Its shownInUrlbar should have been
+ // Get the PageAction.Action object. Its pinnedToUrlbar should have been
// initialized to true in ext-pageAction.js, when it's created.
let actionID = makeWidgetId(extension.id);
let action = PageActions.actionForID(actionID);
- Assert.equal(action.shownInUrlbar, true);
+ Assert.equal(action.pinnedToUrlbar, true);
// Simulate restarting the app without first unloading the extension.
await promiseShutdownManager();
PageActions._reset();
await promiseStartupManager();
await extension.awaitStartup();
- // Get the action. Its shownInUrlbar should remain true.
+ // Get the action. Its pinnedToUrlbar should remain true.
action = PageActions.actionForID(actionID);
- Assert.equal(action.shownInUrlbar, true);
+ Assert.equal(action.pinnedToUrlbar, true);
- // Now set its shownInUrlbar to false.
- action.shownInUrlbar = false;
+ // Now set its pinnedToUrlbar to false.
+ action.pinnedToUrlbar = false;
// Simulate restarting the app again without first unloading the extension.
await promiseShutdownManager();
PageActions._reset();
await promiseStartupManager();
await extension.awaitStartup();
- // Get the action. Its shownInUrlbar should remain false.
+ // Get the action. Its pinnedToUrlbar should remain false.
action = PageActions.actionForID(actionID);
- Assert.equal(action.shownInUrlbar, false);
+ Assert.equal(action.pinnedToUrlbar, false);
// Now unload the extension and quit the app.
await extension.unload();
await promiseShutdownManager();
});
--- a/browser/components/uitour/test/browser_UITour_availableTargets.js
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -131,27 +131,27 @@ async function assertTargetNode(targetNa
let target = await UITour.getTarget(window, targetName);
is(target.node.id, expectedNodeId, "UITour should get the right target node");
}
var pageActionsHelper = {
setActionsUrlbarState(inUrlbar) {
this._originalStates = [];
PageActions._actionsByID.forEach(action => {
- this._originalStates.push([ action, action.shownInUrlbar ]);
- action.shownInUrlbar = inUrlbar;
+ this._originalStates.push([ action, action.pinnedToUrlbar ]);
+ action.pinnedToUrlbar = inUrlbar;
});
},
restoreActionsUrlbarState() {
if (!this._originalStates) {
return;
}
for (let [ action, originalState] of this._originalStates) {
- action.shownInUrlbar = originalState;
+ action.pinnedToUrlbar = originalState;
}
this._originalStates = null;
}
};
function ensureScreenshotsEnabled() {
SpecialPowers.pushPrefEnv({ set: [
[ "extensions.screenshots.disabled", false ]
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -89,17 +89,17 @@ var PocketPageAction = {
init() {
let id = "pocket";
this.pageAction = PageActions.actionForID(id);
if (!this.pageAction) {
this.pageAction = PageActions.addAction(new PageActions.Action({
id,
title: gPocketBundle.GetStringFromName("saveToPocketCmd.label"),
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
wantsIframe: true,
urlbarIDOverride: "pocket-button-box",
anchorIDOverride: "pocket-button",
_insertBeforeActionID: PageActions.ACTION_ID_BOOKMARK_SEPARATOR,
_urlbarNodeInMarkup: true,
onBeforePlacedInWindow(window) {
let doc = window.document;
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -956,16 +956,19 @@ you can use these alternative items. Oth
<!ENTITY updateRestart.acceptButton.accesskey "R">
<!ENTITY updateRestart.cancelButton.label "Not Now">
<!ENTITY updateRestart.cancelButton.accesskey "N">
<!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
<!ENTITY pageActionButton.tooltip "Page actions">
<!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
<!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
+<!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
+<!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
+<!ENTITY pageAction.manageExtension.label "Manage Extension…">
<!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
<!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
<!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
<!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
display a tooltip for accessibility indicator in toolbar/tabbar. It is also
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -114,25 +114,30 @@ this.PageActions = {
/**
* The list of non-built-in actions. Not live. (array of Action objects)
*/
get nonBuiltInActions() {
return this._nonBuiltInActions.slice();
},
/**
- * The list of actions in the urlbar, sorted in the order in which they should
- * appear there. Not live. (array of Action objects)
+ * The list of actions currently in the urlbar, sorted in the order in which
+ * they appear. Not live.
+ *
+ * @param browserWindow (DOM window, required)
+ * This window's actions will be returned.
+ * @return (array of PageAction.Action objects) The actions currently in the
+ * given window's urlbar.
*/
- get actionsInUrlbar() {
+ actionsInUrlbar(browserWindow) {
// Remember that IDs in idsInUrlbar may belong to actions that aren't
// currently registered.
return this._persistedActions.idsInUrlbar.reduce((actions, id) => {
let action = this.actionForID(id);
- if (action) {
+ if (action && action.shouldShowInUrlbar(browserWindow)) {
actions.push(action);
}
return actions;
}, []);
},
/**
* Gets an action.
@@ -223,40 +228,39 @@ this.PageActions = {
// Keep this list sorted by title.
let index = BinarySearch.insertionIndexOf((a1, a2) => {
return a1.getTitle().localeCompare(a2.getTitle());
}, this._nonBuiltInActions, action);
this._nonBuiltInActions.splice(index, 0, action);
}
if (this._persistedActions.ids.includes(action.id)) {
- // The action has been seen before. Override its shownInUrlbar value
+ // The action has been seen before. Override its pinnedToUrlbar value
// with the persisted value. Set the private version of that property
- // so that onActionToggledShownInUrlbar isn't called, which happens when
+ // so that onActionToggledPinnedToUrlbar isn't called, which happens when
// the public version is set.
- action._shownInUrlbar =
+ action._pinnedToUrlbar =
this._persistedActions.idsInUrlbar.includes(action.id);
} else {
// The action is new. Store it in the persisted actions.
this._persistedActions.ids.push(action.id);
- this._updateIDsInUrlbarForAction(action);
+ this._updateIDsPinnedToUrlbarForAction(action);
}
},
- _updateIDsInUrlbarForAction(action) {
+ _updateIDsPinnedToUrlbarForAction(action) {
let index = this._persistedActions.idsInUrlbar.indexOf(action.id);
- if (action.shownInUrlbar) {
+ if (action.pinnedToUrlbar) {
if (index < 0) {
- let nextID = this.nextActionIDInUrlbar(action.id);
- let nextIndex =
- nextID ? this._persistedActions.idsInUrlbar.indexOf(nextID) : -1;
- if (nextIndex < 0) {
- nextIndex = this._persistedActions.idsInUrlbar.length;
+ index = action.id == ACTION_ID_BOOKMARK ? -1 :
+ this._persistedActions.idsInUrlbar.indexOf(ACTION_ID_BOOKMARK);
+ if (index < 0) {
+ index = this._persistedActions.idsInUrlbar.length;
}
- this._persistedActions.idsInUrlbar.splice(nextIndex, 0, action.id);
+ this._persistedActions.idsInUrlbar.splice(index, 0, action.id);
}
} else if (index >= 0) {
this._persistedActions.idsInUrlbar.splice(index, 1);
}
this._storePersistedActions();
},
// These keep track of currently registered actions.
@@ -268,23 +272,23 @@ this.PageActions = {
* Returns the ID of the action before which the given action should be
* inserted in the urlbar.
*
* @param action (Action object, required)
* The action you're inserting.
* @return The ID of the reference action, or null if your action should be
* appended.
*/
- nextActionIDInUrlbar(action) {
+ nextActionIDInUrlbar(browserWindow, action) {
// Actions in the urlbar are always inserted before the bookmark action,
// which always comes last if it's present.
if (action.id == ACTION_ID_BOOKMARK) {
return null;
}
- let id = this._nextActionID(action, this.actionsInUrlbar);
+ let id = this._nextActionID(action, this.actionsInUrlbar(browserWindow));
return id || ACTION_ID_BOOKMARK;
},
/**
* Returns the ID of the action before which the given action should be
* inserted in the panel.
*
* @param action (Action object, required)
@@ -349,44 +353,42 @@ this.PageActions = {
}
for (let bpa of allBrowserPageActions()) {
bpa.removeAction(action);
}
},
/**
- * Call this when an action's shownInUrlbar property changes.
+ * Call this when an action's pinnedToUrlbar property changes.
*
* @param action (Action object, required)
- * The action whose shownInUrlbar property changed.
+ * The action whose pinnedToUrlbar property changed.
*/
- onActionToggledShownInUrlbar(action) {
+ onActionToggledPinnedToUrlbar(action) {
if (!this.actionForID(action.id)) {
// This may be called before the action has been added.
return;
}
- this._updateIDsInUrlbarForAction(action);
+ this._updateIDsPinnedToUrlbarForAction(action);
for (let bpa of allBrowserPageActions()) {
bpa.placeActionInUrlbar(action);
}
},
logTelemetry(type, action, node = null) {
- const kAllowedLabels = ["pocket", "screenshots", "webcompat"].concat(
- gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id)
- );
-
if (type == "used") {
- type = (node && node.closest("#urlbar-container")) ? "urlbar_used" : "panel_used";
+ type =
+ node && node.closest("#urlbar-container") ? "urlbar_used" :
+ "panel_used";
}
let histogramID = "FX_PAGE_ACTION_" + type.toUpperCase();
try {
let histogram = Services.telemetry.getHistogramById(histogramID);
- if (kAllowedLabels.includes(action.labelForHistogram)) {
+ if (action._isBuiltIn) {
histogram.add(action.labelForHistogram);
} else {
histogram.add("other");
}
} catch (ex) {
Cu.reportError(ex);
}
},
@@ -487,16 +489,18 @@ this.PageActions = {
* @param title (string, required)
* The action's title.
* @param anchorIDOverride (string, optional)
* Pass a string to override the node to which the action's activated-
* action panel is anchored.
* @param disabled (bool, optional)
* Pass true to cause the action to be disabled initially in all browser
* windows. False by default.
+ * @param extensionID (string, optional)
+ * If the action lives in an extension, pass its ID.
* @param iconURL (string or object, optional)
* The URL string of the action's icon. Usually you want to specify an
* icon in CSS, but this option is useful if that would be a pain for
* some reason. You can also pass an object that maps pixel sizes to
* URLs, like { 16: url16, 32: url32 }. The best size for the user's
* screen will be used.
* @param nodeAttributes (object, optional)
* An object of name-value pairs. Each pair will be added as an
@@ -544,19 +548,19 @@ this.PageActions = {
* @param onRemovedFromWindow (function, optional)
* Called after the action is removed from a browser window:
* onRemovedFromWindow(browserWindow)
* * browserWindow: The browser window that the action was removed from.
* @param onShowingInPanel (function, optional)
* Called when a browser window's page action panel is showing:
* onShowingInPanel(buttonNode)
* * buttonNode: The action's node in the page action panel.
- * @param shownInUrlbar (bool, optional)
- * Pass true to show the action in the urlbar, false otherwise. False by
- * default.
+ * @param pinnedToUrlbar (bool, optional)
+ * Pass true to pin the action to the urlbar. An action is shown in the
+ * urlbar if it's pinned and not disabled. False by default.
* @param subview (object, optional)
* An options object suitable for passing to the Subview constructor, if
* you'd like the action to have a subview. See the subview constructor
* for info on this object's properties.
* @param tooltip (string, optional)
* The action's button tooltip text.
* @param urlbarIDOverride (string, optional)
* Usually the ID of the action's button in the urlbar will be generated
@@ -567,30 +571,31 @@ this.PageActions = {
* clicked.
*/
function Action(options) {
setProperties(this, options, {
id: true,
title: !options._isSeparator,
anchorIDOverride: false,
disabled: false,
+ extensionID: false,
iconURL: false,
labelForHistogram: false,
nodeAttributes: false,
onBeforePlacedInWindow: false,
onCommand: false,
onIframeHiding: false,
onIframeHidden: false,
onIframeShown: false,
onLocationChange: false,
onPlacedInPanel: false,
onPlacedInUrlbar: false,
onRemovedFromWindow: false,
onShowingInPanel: false,
- shownInUrlbar: false,
+ pinnedToUrlbar: false,
subview: false,
tooltip: false,
urlbarIDOverride: false,
wantsIframe: false,
// private
// (string, optional)
@@ -613,42 +618,50 @@ function Action(options) {
});
if (this._subview) {
this._subview = new Subview(options.subview);
}
}
Action.prototype = {
/**
+ * The ID of the action's parent extension (string, nullable)
+ */
+ get extensionID() {
+ return this._extensionID;
+ },
+
+ /**
* The action's ID (string, nonnull)
*/
get id() {
return this._id;
},
/**
* Attribute name => value mapping to set on nodes created for this action
* (object, nullable)
*/
get nodeAttributes() {
return this._nodeAttributes;
},
/**
- * True if the action is shown in the urlbar (bool, nonnull)
+ * True if the action is pinned to the urlbar. The action is shown in the
+ * urlbar if it's pinned and not disabled. (bool, nonnull)
*/
- get shownInUrlbar() {
- return this._shownInUrlbar || false;
+ get pinnedToUrlbar() {
+ return this._pinnedToUrlbar || false;
},
- set shownInUrlbar(shown) {
- if (this.shownInUrlbar != shown) {
- this._shownInUrlbar = shown;
- PageActions.onActionToggledShownInUrlbar(this);
+ set pinnedToUrlbar(shown) {
+ if (this.pinnedToUrlbar != shown) {
+ this._pinnedToUrlbar = shown;
+ PageActions.onActionToggledPinnedToUrlbar(this);
}
- return this.shownInUrlbar;
+ return this.pinnedToUrlbar;
},
/**
* The action's disabled state (bool, nonnull)
*/
getDisabled(browserWindow = null) {
return !!this._getProperty("disabled", browserWindow);
},
@@ -967,17 +980,38 @@ Action.prototype = {
*
* PageActions will remember the action's urlbar placement, if any, after this
* method is called until app shutdown. If the action is not added again
* before shutdown, then PageActions will discard the placement, and the next
* time the action is added, its placement will be reset.
*/
remove() {
PageActions.onActionRemoved(this);
- }
+ },
+
+ /**
+ * Returns whether the action should be shown in a given window's urlbar.
+ *
+ * @param browserWindow (DOM window, required)
+ * The window.
+ * @return True if the action should be shown and false otherwise. The action
+ * should be shown if it's both pinned and not disabled.
+ */
+ shouldShowInUrlbar(browserWindow) {
+ return this.pinnedToUrlbar && !this.getDisabled(browserWindow);
+ },
+
+ get _isBuiltIn() {
+ let builtInIDs = [
+ "pocket",
+ "screenshots",
+ "webcompat-reporter-button",
+ ].concat(gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id));
+ return builtInIDs.includes(this.id);
+ },
};
this.PageActions.Action = Action;
/**
* A Subview represents a PanelUI panelview that your actions can show.
* `options` is a required object with the following properties.
@@ -1141,17 +1175,17 @@ var gBuiltInActions = [
// bookmark
{
id: ACTION_ID_BOOKMARK,
urlbarIDOverride: "star-button-box",
_urlbarNodeInMarkup: true,
// The title is set in browser-pageActions.js by calling
// BookmarkingUI.updateBookmarkPageMenuItem().
title: "",
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
nodeAttributes: {
observes: "bookmarkThisPageBroadcaster",
},
onShowingInPanel(buttonNode) {
browserPageActions(buttonNode).bookmark.onShowingInPanel(buttonNode);
},
onCommand(event, buttonNode) {
browserPageActions(buttonNode).bookmark.onCommand(event, buttonNode);
--- a/browser/modules/test/browser/browser_PageActions.js
+++ b/browser/modules/test/browser/browser_PageActions.js
@@ -1,9 +1,8 @@
-/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
// This is a test for PageActions.jsm, specifically the generalized parts that
// add and remove page actions and toggle them in the urlbar. This does not
// test the built-in page actions; browser_page_action_menu.js does that.
// Initialization. Must run first.
add_task(async function init() {
@@ -72,20 +71,24 @@ add_task(async function simple() {
Assert.ok(buttonNode, "buttonNode should be non-null: " + buttonNode);
Assert.equal(buttonNode.id, panelButtonID, "buttonNode.id");
},
}));
Assert.equal(action.getIconURL(), iconURL, "iconURL");
Assert.equal(action.id, id, "id");
Assert.deepEqual(action.nodeAttributes, nodeAttributes, "nodeAttributes");
- Assert.equal(action.shownInUrlbar, false, "shownInUrlbar");
+ Assert.equal(action.pinnedToUrlbar, false, "pinnedToUrlbar");
Assert.equal(action.subview, null, "subview");
+ Assert.equal(action.getDisabled(), false, "disabled");
+ Assert.equal(action.getDisabled(window), false, "disabled in window");
Assert.equal(action.getTitle(), title, "title");
+ Assert.equal(action.getTitle(window), title, "title in window");
Assert.equal(action.getTooltip(), tooltip, "tooltip");
+ Assert.equal(action.getTooltip(window), tooltip, "tooltip in window");
Assert.equal(action.urlbarIDOverride, null, "urlbarIDOverride");
Assert.equal(action.wantsIframe, false, "wantsIframe");
Assert.ok(!("__insertBeforeActionID" in action), "__insertBeforeActionID");
Assert.ok(!("__isSeparator" in action), "__isSeparator");
Assert.ok(!("__urlbarNodeInMarkup" in action), "__urlbarNodeInMarkup");
Assert.equal(onPlacedInPanelCallCount, 1,
@@ -146,17 +149,17 @@ add_task(async function simple() {
Assert.equal(onShowingInPanelCallCount, 1,
"onShowingInPanelCallCount should be inc'ed");
onCommandExpectedButtonID = panelButtonID;
EventUtils.synthesizeMouseAtCenter(panelButtonNode, {});
await promisePageActionPanelHidden();
Assert.equal(onCommandCallCount, 1, "onCommandCallCount should be inc'ed");
// Show the action's button in the urlbar.
- action.shownInUrlbar = true;
+ action.pinnedToUrlbar = true;
Assert.equal(onPlacedInUrlbarCallCount, 1,
"onPlacedInUrlbarCallCount should be inc'ed");
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.notEqual(urlbarButtonNode, null, "urlbarButtonNode");
for (let name in action.nodeAttributes) {
Assert.ok(urlbarButtonNode.hasAttribute(name), name,
"Has attribute: " + name);
Assert.equal(urlbarButtonNode.getAttribute(name),
@@ -167,29 +170,45 @@ add_task(async function simple() {
// The button should have been inserted before the bookmark star.
Assert.notEqual(urlbarButtonNode.nextSibling, null, "Should be a next node");
Assert.equal(
urlbarButtonNode.nextSibling.id,
PageActions.actionForID(PageActions.ACTION_ID_BOOKMARK).urlbarIDOverride,
"Next node should be the bookmark star"
);
+ // Disable the action. The button in the urlbar should be removed, and the
+ // button in the panel should be disabled.
+ action.setDisabled(true);
+ urlbarButtonNode = document.getElementById(urlbarButtonID);
+ Assert.equal(urlbarButtonNode, null, "urlbar button should be removed");
+ Assert.equal(panelButtonNode.disabled, true,
+ "panel button should be disabled");
+
+ // Enable the action. The button in the urlbar should be added back, and the
+ // button in the panel should be enabled.
+ action.setDisabled(false);
+ urlbarButtonNode = document.getElementById(urlbarButtonID);
+ Assert.notEqual(urlbarButtonNode, null, "urlbar button should be added back");
+ Assert.equal(panelButtonNode.disabled, false,
+ "panel button should not be disabled");
+
// Click the urlbar button.
onCommandExpectedButtonID = urlbarButtonID;
EventUtils.synthesizeMouseAtCenter(urlbarButtonNode, {});
Assert.equal(onCommandCallCount, 2, "onCommandCallCount should be inc'ed");
// Set a new title.
let newTitle = title + " new title";
action.setTitle(newTitle);
Assert.equal(action.getTitle(), newTitle, "New title");
Assert.equal(panelButtonNode.getAttribute("label"), action.getTitle(),
"New label");
- // Now that shownInUrlbar has been toggled, make sure that it sticks across
+ // Now that pinnedToUrlbar has been toggled, make sure that it sticks across
// app restarts. Simulate that by "unregistering" the action (not by removing
// it, which is more permanent) and then registering it again.
// unregister
PageActions._actionsByID.delete(action.id);
let index = PageActions._nonBuiltInActions.findIndex(a => a.id == action.id);
Assert.ok(index >= 0, "Action should be in _nonBuiltInActions to begin with");
PageActions._nonBuiltInActions.splice(index, 1);
@@ -197,20 +216,20 @@ add_task(async function simple() {
// register again
PageActions._registerAction(action);
// check relevant properties
Assert.ok(PageActions._persistedActions.ids.includes(action.id),
"PageActions should have 'seen' the action");
Assert.ok(PageActions._persistedActions.idsInUrlbar.includes(action.id),
"idsInUrlbar should still include the action");
- Assert.ok(action.shownInUrlbar,
- "shownInUrlbar should still be true");
- Assert.ok(action._shownInUrlbar,
- "_shownInUrlbar should still be true, for good measure");
+ Assert.ok(action.pinnedToUrlbar,
+ "pinnedToUrlbar should still be true");
+ Assert.ok(action._pinnedToUrlbar,
+ "_pinnedToUrlbar should still be true, for good measure");
// Remove the action.
action.remove();
panelButtonNode = document.getElementById(panelButtonID);
Assert.equal(panelButtonNode, null, "panelButtonNode");
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.equal(urlbarButtonNode, null, "urlbarButtonNode");
@@ -296,17 +315,17 @@ add_task(async function withSubview() {
break;
}
}
};
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id,
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
subview,
title: "Test subview",
onCommand(event, buttonNode) {
onActionCommandCallCount++;
},
onPlacedInPanel(buttonNode) {
onActionPlacedInPanelCallCount++;
Assert.ok(buttonNode, "buttonNode should be non-null: " + buttonNode);
@@ -439,17 +458,17 @@ add_task(async function withIframe() {
let onIframeShownCount = 0;
let panelButtonID = BrowserPageActions.panelButtonNodeIDForActionID(id);
let urlbarButtonID = BrowserPageActions.urlbarButtonNodeIDForActionID(id);
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id,
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
title: "Test iframe",
wantsIframe: true,
onCommand(event, buttonNode) {
onCommandCallCount++;
},
onIframeShown(iframeNode, panelNode) {
onIframeShownCount++;
Assert.ok(iframeNode, "iframeNode should be non-null: " + iframeNode);
@@ -525,17 +544,17 @@ add_task(async function withIframe() {
// action's urlbar button.
aaPanel = document.getElementById(BrowserPageActions._activatedActionPanelID);
Assert.notEqual(aaPanel, null, "aaPanel");
Assert.equal(aaPanel.anchorNode.id, urlbarButtonID, "aaPanel.anchorNode.id");
EventUtils.synthesizeMouseAtCenter(urlbarButtonNode, {});
await promisePanelHidden(BrowserPageActions._activatedActionPanelID);
// Hide the action's button in the urlbar.
- action.shownInUrlbar = false;
+ action.pinnedToUrlbar = false;
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.equal(urlbarButtonNode, null, "urlbarButtonNode");
// Open the panel, click the action's button.
await promisePageActionPanelOpen();
EventUtils.synthesizeMouseAtCenter(panelButtonNode, {});
await promisePanelShown(BrowserPageActions._activatedActionPanelID);
Assert.equal(onCommandCallCount, 0, "onCommandCallCount should remain 0");
@@ -825,32 +844,32 @@ add_task(async function nonBuiltFirst()
// Makes sure that urlbar nodes appear in the correct order in a new window.
add_task(async function urlbarOrderNewWindow() {
// Make some new actions.
let actions = [0, 1, 2].map(i => {
return PageActions.addAction(new PageActions.Action({
id: `test-urlbarOrderNewWindow-${i}`,
title: `Test urlbarOrderNewWindow ${i}`,
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
}));
});
// Make sure PageActions knows they're inserted before the bookmark action in
// the urlbar.
Assert.deepEqual(
PageActions._persistedActions.idsInUrlbar.slice(
PageActions._persistedActions.idsInUrlbar.length - (actions.length + 1)
),
actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
"PageActions._persistedActions.idsInUrlbar has new actions inserted"
);
Assert.deepEqual(
- PageActions.actionsInUrlbar.slice(
- PageActions.actionsInUrlbar.length - (actions.length + 1)
+ PageActions.actionsInUrlbar(window).slice(
+ PageActions.actionsInUrlbar(window).length - (actions.length + 1)
).map(a => a.id),
actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
"PageActions.actionsInUrlbar has new actions inserted"
);
// Reach into _persistedActions to move the new actions to the front of the
// urlbar, same as if the user moved them. That way we can test that insert-
// before IDs are correctly non-null when the urlbar nodes are inserted in the
@@ -929,35 +948,35 @@ add_task(async function migrate1() {
JSON.stringify(persisted)
);
// Migrate.
PageActions._loadPersistedActions();
Assert.equal(PageActions._persistedActions.version, 1, "Correct version");
- // Need to set copyURL's _shownInUrlbar. It won't be set since it's false by
+ // Need to set copyURL's _pinnedToUrlbar. It won't be set since it's false by
// default and we reached directly into persisted storage above.
- PageActions.actionForID("copyURL")._shownInUrlbar = true;
+ PageActions.actionForID("copyURL")._pinnedToUrlbar = true;
// expected order
let orderedIDs = [
"pocket",
"copyURL",
PageActions.ACTION_ID_BOOKMARK,
];
// Check the ordering.
Assert.deepEqual(
PageActions._persistedActions.idsInUrlbar,
orderedIDs,
"PageActions._persistedActions.idsInUrlbar has right order"
);
Assert.deepEqual(
- PageActions.actionsInUrlbar.map(a => a.id),
+ PageActions.actionsInUrlbar(window).map(a => a.id),
orderedIDs,
"PageActions.actionsInUrlbar has right order"
);
// Open a new window.
let win = await BrowserTestUtils.openNewBrowserWindow();
await BrowserTestUtils.openNewForegroundTab({
gBrowser: win.gBrowser,
@@ -977,31 +996,33 @@ add_task(async function migrate1() {
actualUrlbarNodeIDs,
orderedIDs.map(id => win.BrowserPageActions.urlbarButtonNodeIDForActionID(id)),
"Expected actions in new window's urlbar"
);
// Done, clean up.
await BrowserTestUtils.closeWindow(win);
Services.prefs.clearUserPref(PageActions.PREF_PERSISTED_ACTIONS);
- PageActions.actionForID("copyURL").shownInUrlbar = false;
+ PageActions.actionForID("copyURL").pinnedToUrlbar = false;
});
// Opens a new browser window and makes sure per-window state works right.
add_task(async function perWindowState() {
// Add a test action.
let title = "Test perWindowState";
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id: "test-perWindowState",
- shownInUrlbar: true,
+ pinnedToUrlbar: true,
title,
}));
+ let actionsInUrlbar = PageActions.actionsInUrlbar(window);
+
// Open a new browser window and load an actionable page so that the action
// shows up in it.
let newWindow = await BrowserTestUtils.openNewBrowserWindow();
await BrowserTestUtils.openNewForegroundTab({
gBrowser: newWindow.gBrowser,
url: "http://example.com/",
});
@@ -1038,53 +1059,100 @@ add_task(async function perWindowState()
// same in the old window.
let panelButtonNode1 = document.getElementById(panelButtonID);
Assert.equal(panelButtonNode1.getAttribute("label"), newGlobalTitle,
"Panel button label in old window");
let panelButtonNode2 = newWindow.document.getElementById(panelButtonID);
Assert.equal(panelButtonNode2.getAttribute("label"), newPerWinTitle,
"Panel button label in new window");
+ // Disable the action in the new window.
+ action.setDisabled(true, newWindow);
+ Assert.equal(action.getDisabled(), false,
+ "Disabled: global should remain false");
+ Assert.equal(action.getDisabled(window), false,
+ "Disabled: old window should remain false");
+ Assert.equal(action.getDisabled(newWindow), true,
+ "Disabled: new window should be true");
+
+ // Check PageActions.actionsInUrlbar for each window.
+ Assert.deepEqual(
+ PageActions.actionsInUrlbar(window).map(a => a.id),
+ actionsInUrlbar.map(a => a.id),
+ "PageActions.actionsInUrlbar: old window should have all actions in urlbar"
+ );
+ Assert.deepEqual(
+ PageActions.actionsInUrlbar(newWindow).map(a => a.id),
+ actionsInUrlbar.map(a => a.id).filter(id => id != action.id),
+ "PageActions.actionsInUrlbar: new window should have all actions in urlbar except the test action"
+ );
+
+ // Check the urlbar nodes for the old window.
+ let actualUrlbarNodeIDs = [];
+ for (let node = BrowserPageActions.mainButtonNode.nextSibling;
+ node;
+ node = node.nextSibling) {
+ actualUrlbarNodeIDs.push(node.id);
+ }
+ Assert.deepEqual(
+ actualUrlbarNodeIDs,
+ actionsInUrlbar.map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
+ "Old window should have all nodes in urlbar"
+ );
+
+ // Check the urlbar nodes for the new window.
+ actualUrlbarNodeIDs = [];
+ for (let node = newWindow.BrowserPageActions.mainButtonNode.nextSibling;
+ node;
+ node = node.nextSibling) {
+ actualUrlbarNodeIDs.push(node.id);
+ }
+ Assert.deepEqual(
+ actualUrlbarNodeIDs,
+ actionsInUrlbar.filter(a => a.id != action.id).map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
+ "New window should have all nodes in urlbar except for the test action's"
+ );
+
// Done, clean up.
await BrowserTestUtils.closeWindow(newWindow);
action.remove();
});
// Adds an action, changes its placement in the urlbar to something non-default,
// removes the action, and then adds it back. Since the action was removed and
// re-added without restarting the app (or more accurately without calling
// PageActions._purgeUnregisteredPersistedActions), the action should remain in
// persisted state and retain its last placement in the urlbar.
add_task(async function removeRetainState() {
// Get the list of actions initially in the urlbar.
- let initialActionsInUrlbar = PageActions.actionsInUrlbar;
+ let initialActionsInUrlbar = PageActions.actionsInUrlbar(window);
Assert.ok(initialActionsInUrlbar.length > 0,
"This test expects there to be at least one action in the urlbar initially (like the bookmark star)");
// Add a test action.
let id = "test-removeRetainState";
let testAction = PageActions.addAction(new PageActions.Action({
id,
title: "Test removeRetainState",
}));
// Show its button in the urlbar.
- testAction.shownInUrlbar = true;
+ testAction.pinnedToUrlbar = true;
- // "Move" the test action to the front of the urlbar by toggling shownInUrlbar
- // for all the other actions in the urlbar.
+ // "Move" the test action to the front of the urlbar by toggling
+ // pinnedToUrlbar for all the other actions in the urlbar.
for (let action of initialActionsInUrlbar) {
- action.shownInUrlbar = false;
- action.shownInUrlbar = true;
+ action.pinnedToUrlbar = false;
+ action.pinnedToUrlbar = true;
}
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
- PageActions.actionsInUrlbar.map(a => a.id),
+ PageActions.actionsInUrlbar(window).map(a => a.id),
[testAction].concat(initialActionsInUrlbar).map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order: testAction followed by all initial actions"
);
// Check the nodes in the urlbar.
let actualUrlbarNodeIDs = [];
for (let node = BrowserPageActions.mainButtonNode.nextSibling;
node;
@@ -1097,17 +1165,17 @@ add_task(async function removeRetainStat
"urlbar nodes should be in expected order: testAction followed by all initial actions"
);
// Remove the test action.
testAction.remove();
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
- PageActions.actionsInUrlbar.map(a => a.id),
+ PageActions.actionsInUrlbar(window).map(a => a.id),
initialActionsInUrlbar.map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order after removing test action: all initial actions"
);
// Check the nodes in the urlbar.
actualUrlbarNodeIDs = [];
for (let node = BrowserPageActions.mainButtonNode.nextSibling;
node;
@@ -1122,21 +1190,21 @@ add_task(async function removeRetainStat
// Add the test action again.
testAction = PageActions.addAction(new PageActions.Action({
id,
title: "Test removeRetainState",
}));
// Show its button in the urlbar again.
- testAction.shownInUrlbar = true;
+ testAction.pinnedToUrlbar = true;
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
- PageActions.actionsInUrlbar.map(a => a.id),
+ PageActions.actionsInUrlbar(window).map(a => a.id),
[testAction].concat(initialActionsInUrlbar).map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order after re-adding test action: testAction followed by all initial actions"
);
// Check the nodes in the urlbar.
actualUrlbarNodeIDs = [];
for (let node = BrowserPageActions.mainButtonNode.nextSibling;
node;
@@ -1149,16 +1217,210 @@ add_task(async function removeRetainStat
"urlbar nodes should be in expected order after re-adding test action: testAction followed by all initial actions"
);
// Done, clean up.
testAction.remove();
});
+// Opens the context menu on a non-built-in action. (The context menu for
+// built-in actions is tested in browser_page_action_menu.js.)
+add_task(async function contextMenu() {
+ // Add a test action.
+ let action = PageActions.addAction(new PageActions.Action({
+ id: "test-contextMenu",
+ title: "Test contextMenu",
+ pinnedToUrlbar: true,
+ }));
+
+ // Open the panel and then open the context menu on the action's item.
+ await promisePageActionPanelOpen();
+ let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
+ let contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(panelButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "don't show" item and the "manage" item.
+ // Click the "don't show" item.
+ let menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+ "Context menu is in the 'don't show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+ await contextMenuPromise;
+
+ // The action should be removed from the urlbar.
+ await BrowserTestUtils.waitForCondition(() => {
+ return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ }, "Waiting for urlbar button to be removed");
+
+ // Open the context menu again on the action's button in the panel. (The
+ // panel should still be open.)
+ contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(panelButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "show" item and the "manage" item. Click
+ // the "show" item.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Show in Address Bar",
+ "Context menu is in the 'show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+ await contextMenuPromise;
+
+ // The action should be added back to the urlbar.
+ await BrowserTestUtils.waitForCondition(() => {
+ return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ }, "Waiting for urlbar button to be added back");
+
+ // Open the context menu again on the action's button in the panel. (The
+ // panel should still be open.)
+ contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(panelButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "don't show" item and the "manage" item.
+ // Click the "manage" item. about:addons should open.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+ "Context menu is in the 'don't show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ let aboutAddonsPromise =
+ BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+ EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
+ let values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
+ let aboutAddonsTab = values[0];
+ await BrowserTestUtils.removeTab(aboutAddonsTab);
+
+ // Open the context menu on the action's urlbar button.
+ let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(urlbarButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "don't show" item and the "manage" item.
+ // Click the "don't show" item.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+ "Context menu is in the 'don't show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+ await contextMenuPromise;
+
+ // The action should be removed from the urlbar.
+ await BrowserTestUtils.waitForCondition(() => {
+ return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ }, "Waiting for urlbar button to be removed");
+
+ // Open the panel and then open the context menu on the action's item.
+ await promisePageActionPanelOpen();
+ contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(panelButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "show" item and the "manage" item. Click
+ // the "show" item.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Show in Address Bar",
+ "Context menu is in the 'show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
+ await contextMenuPromise;
+
+ // The action should be added back to the urlbar.
+ await BrowserTestUtils.waitForCondition(() => {
+ return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ }, "Waiting for urlbar button to be added back");
+
+ // Open the context menu on the action's urlbar button.
+ urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+ contextMenuPromise = promisePanelShown("pageActionContextMenu");
+ EventUtils.synthesizeMouseAtCenter(urlbarButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await contextMenuPromise;
+
+ // The context menu should show the "don't show" item and the "manage" item.
+ // Click the "manage" item. about:addons should open.
+ menuItems = collectContextMenuItems();
+ Assert.equal(menuItems.length, 3,
+ "Context menu has 3 children");
+ Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
+ "Context menu is in the 'don't show' state");
+ Assert.equal(menuItems[1].localName, "menuseparator",
+ "menuseparator is present");
+ Assert.equal(menuItems[2].label, "Manage Extension\u2026",
+ "'Manage' item is present");
+ contextMenuPromise = promisePanelHidden("pageActionContextMenu");
+ aboutAddonsPromise =
+ BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+ EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
+ values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
+ aboutAddonsTab = values[0];
+ await BrowserTestUtils.removeTab(aboutAddonsTab);
+
+ // Done, clean up.
+ action.remove();
+
+ // urlbar tests that run after this one can break if the mouse is left over
+ // the area where the urlbar popup appears, which seems to happen due to the
+ // above synthesized mouse events. Move it over the urlbar.
+ EventUtils.synthesizeMouseAtCenter(gURLBar, { type: "mousemove" });
+ gURLBar.focus();
+});
+
+
function promisePageActionPanelOpen() {
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return BrowserTestUtils.waitForCondition(() => {
// Wait for the main page action button to become visible. It's hidden for
// some URIs, so depending on when this is called, it may not yet be quite
// visible. It's up to the caller to make sure it will be visible.
info("Waiting for main page action button to have non-0 size");
@@ -1230,8 +1492,15 @@ function promisePageActionViewChildrenVi
let bounds = dwu.getBoundsWithoutFlushing(childNode);
if (bounds.width > 0 && bounds.height > 0) {
return true;
}
}
return false;
});
}
+
+function collectContextMenuItems() {
+ let contextMenu = document.getElementById("pageActionContextMenu");
+ return Array.filter(contextMenu.childNodes, node => {
+ return window.getComputedStyle(node).visibility == "visible";
+ });
+}
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6596,16 +6596,26 @@
"alert_emails": ["gijs@mozilla.com"],
"bug_numbers": [1393843],
"expires_in_version": "60",
"kind": "categorical",
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
"sendToDevice", "other"],
"description": "Count how many times people remove items from the url bar"
},
+ "FX_PAGE_ACTION_MANAGED": {
+ "record_in_processes": ["main"],
+ "alert_emails": ["gijs@mozilla.com"],
+ "bug_numbers": [1393843],
+ "expires_in_version": "60",
+ "kind": "categorical",
+ "labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
+ "sendToDevice", "other"],
+ "description": "Count how many times people manage extensions via their actions in the url bar"
+ },
"FX_PAGE_ACTION_URLBAR_USED": {
"record_in_processes": ["main"],
"alert_emails": ["gijs@mozilla.com"],
"bug_numbers": [1393843],
"expires_in_version": "60",
"kind": "categorical",
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
"sendToDevice", "other"],