1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 ChromeUtils.defineESModuleGetters(this, {
6 SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
9 var BrowserPageActions = {
12 * The main page action button in the urlbar (DOM node)
14 get mainButtonNode() {
15 delete this.mainButtonNode;
16 return (this.mainButtonNode = document.getElementById("pageActionButton"));
20 * The main page action panel DOM node (DOM node)
23 // Lazy load the page action panel the first time we need to display it
24 if (!this._panelNode) {
25 this.initializePanel();
27 delete this.panelNode;
28 return (this.panelNode = this._panelNode);
32 * The panelmultiview node in the main page action panel (DOM node)
35 delete this.multiViewNode;
36 return (this.multiViewNode = document.getElementById(
37 "pageActionPanelMultiView"
42 * The main panelview node in the main page action panel (DOM node)
45 delete this.mainViewNode;
46 return (this.mainViewNode = document.getElementById(
47 "pageActionPanelMainView"
52 * The vbox body node in the main panelview node (DOM node)
54 get mainViewBodyNode() {
55 delete this.mainViewBodyNode;
56 return (this.mainViewBodyNode = this.mainViewNode.querySelector(
62 * Inits. Call to init.
65 this.placeAllActionsInUrlbar();
66 this._onPanelShowing = this._onPanelShowing.bind(this);
70 this.initializePanel();
71 for (let action of PageActions.actionsInPanel(window)) {
72 let buttonNode = this.panelButtonNodeForActionID(action.id);
73 action.onShowingInPanel(buttonNode);
77 placeLazyActionsInPanel() {
78 let actions = this._actionsToLazilyPlaceInPanel;
79 this._actionsToLazilyPlaceInPanel = [];
80 for (let action of actions) {
81 this._placeActionInPanelNow(action);
85 // Actions placed in the panel aren't actually placed until the panel is
86 // subsequently opened.
87 _actionsToLazilyPlaceInPanel: [],
90 * Places all registered actions in the urlbar.
92 placeAllActionsInUrlbar() {
93 let urlbarActions = PageActions.actionsInUrlbar(window);
94 for (let action of urlbarActions) {
95 this.placeActionInUrlbar(action);
97 this._updateMainButtonAttributes();
101 * Initializes the panel if necessary.
104 // Lazy load the page action panel the first time we need to display it
105 if (!this._panelNode) {
106 let template = document.getElementById("pageActionPanelTemplate");
107 template.replaceWith(template.content);
108 this._panelNode = document.getElementById("pageActionPanel");
109 this._panelNode.addEventListener("popupshowing", this._onPanelShowing);
112 for (let action of PageActions.actionsInPanel(window)) {
113 this.placeActionInPanel(action);
115 this.placeLazyActionsInPanel();
119 * Adds or removes as necessary DOM nodes for the given action.
121 * @param action (PageActions.Action, required)
122 * The action to place.
124 placeAction(action) {
125 this.placeActionInPanel(action);
126 this.placeActionInUrlbar(action);
127 this._updateMainButtonAttributes();
131 * Adds or removes as necessary DOM nodes for the action in the panel.
133 * @param action (PageActions.Action, required)
134 * The action to place.
136 placeActionInPanel(action) {
137 if (this._panelNode && this.panelNode.state != "closed") {
138 this._placeActionInPanelNow(action);
140 // This method may be called for the same action more than once
141 // (e.g. when an extension does call pageAction.show/hidden to
142 // enable or disable its own pageAction and we will have to
143 // update the urlbar overflow panel accordingly).
145 // Ensure we don't add the same actions more than once (otherwise we will
146 // not remove all the entries in _removeActionFromPanel).
148 this._actionsToLazilyPlaceInPanel.findIndex(a => a.id == action.id) >= 0
152 // Lazily place the action in the panel the next time it opens.
153 this._actionsToLazilyPlaceInPanel.push(action);
157 _placeActionInPanelNow(action) {
158 if (action.shouldShowInPanel(window)) {
159 this._addActionToPanel(action);
161 this._removeActionFromPanel(action);
165 _addActionToPanel(action) {
166 let id = this.panelButtonNodeIDForActionID(action.id);
167 let node = document.getElementById(id);
171 this._maybeNotifyBeforePlacedInWindow(action);
172 node = this._makePanelButtonNodeForAction(action);
174 let insertBeforeNode = this._getNextNode(action, false);
175 this.mainViewBodyNode.insertBefore(node, insertBeforeNode);
176 this.updateAction(action, null, {
179 this._updateActionDisabledInPanel(action, node);
180 action.onPlacedInPanel(node);
181 this._addOrRemoveSeparatorsInPanel();
184 _removeActionFromPanel(action) {
185 let lazyIndex = this._actionsToLazilyPlaceInPanel.findIndex(
186 a => a.id == action.id
188 if (lazyIndex >= 0) {
189 this._actionsToLazilyPlaceInPanel.splice(lazyIndex, 1);
191 let node = this.panelButtonNodeForActionID(action.id);
196 if (action.getWantsSubview(window)) {
197 let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
198 let panelViewNode = document.getElementById(panelViewNodeID);
200 panelViewNode.remove();
203 this._addOrRemoveSeparatorsInPanel();
206 _addOrRemoveSeparatorsInPanel() {
207 let actions = PageActions.actionsInPanel(window);
209 PageActions.ACTION_ID_BUILT_IN_SEPARATOR,
210 PageActions.ACTION_ID_TRANSIENT_SEPARATOR,
212 for (let id of ids) {
213 let sep = actions.find(a => a.id == id);
215 this._addActionToPanel(sep);
217 let node = this.panelButtonNodeForActionID(id);
225 _updateMainButtonAttributes() {
226 this.mainButtonNode.toggleAttribute(
228 PageActions.actions.length > 1
233 * Returns the node before which an action's node should be inserted.
235 * @param action (PageActions.Action, required)
236 * The action that will be inserted.
237 * @param forUrlbar (bool, required)
238 * True if you're inserting into the urlbar, false if you're inserting
240 * @return (DOM node, maybe null) The DOM node before which to insert the
241 * given action. Null if the action should be inserted at the end.
243 _getNextNode(action, forUrlbar) {
244 let actions = forUrlbar
245 ? PageActions.actionsInUrlbar(window)
246 : PageActions.actionsInPanel(window);
247 let index = actions.findIndex(a => a.id == action.id);
251 for (let i = index + 1; i < actions.length; i++) {
253 ? this.urlbarButtonNodeForActionID(actions[i].id)
254 : this.panelButtonNodeForActionID(actions[i].id);
262 _maybeNotifyBeforePlacedInWindow(action) {
263 if (!this._isActionPlacedInWindow(action)) {
264 action.onBeforePlacedInWindow(window);
268 _isActionPlacedInWindow(action) {
269 if (this.panelButtonNodeForActionID(action.id)) {
272 let urlbarNode = this.urlbarButtonNodeForActionID(action.id);
273 return urlbarNode && !urlbarNode.hidden;
276 _makePanelButtonNodeForAction(action) {
277 if (action.__isSeparator) {
278 let node = document.createXULElement("toolbarseparator");
281 let buttonNode = document.createXULElement("toolbarbutton");
282 buttonNode.classList.add(
284 "subviewbutton-iconic",
285 "pageAction-panel-button"
287 if (action.isBadged) {
288 buttonNode.setAttribute("badged", "true");
290 buttonNode.setAttribute("actionid", action.id);
291 buttonNode.addEventListener("command", event => {
292 this.doCommandForAction(action, event, buttonNode);
297 _makePanelViewNodeForAction(action, forUrlbar) {
298 let panelViewNode = document.createXULElement("panelview");
299 panelViewNode.id = this._panelViewNodeIDForActionID(action.id, forUrlbar);
300 panelViewNode.classList.add("PanelUI-subView");
301 let bodyNode = document.createXULElement("vbox");
302 bodyNode.id = panelViewNode.id + "-body";
303 bodyNode.classList.add("panel-subview-body");
304 panelViewNode.appendChild(bodyNode);
305 return panelViewNode;
309 * Shows or hides a panel for an action. You can supply your own panel;
310 * otherwise one is created.
312 * @param action (PageActions.Action, required)
313 * The action for which to toggle the panel. If the action is in the
314 * urlbar, then the panel will be anchored to it. Otherwise, a
315 * suitable anchor will be used.
316 * @param panelNode (DOM node, optional)
317 * The panel to use. This method takes a hands-off approach with
318 * regard to your panel in terms of attributes, styling, etc.
319 * @param event (DOM event, optional)
320 * The event which triggered this panel.
322 togglePanelForAction(action, panelNode = null, event = null) {
323 let aaPanelNode = this.activatedActionPanelNode;
325 // Note that this particular code path will not prevent the panel from
326 // opening later if PanelMultiView.showPopup was called but the panel has
327 // not been opened yet.
328 if (panelNode.state != "closed") {
329 PanelMultiView.hidePopup(panelNode);
333 PanelMultiView.hidePopup(aaPanelNode);
335 } else if (aaPanelNode) {
336 PanelMultiView.hidePopup(aaPanelNode);
339 panelNode = this._makeActivatedActionPanelForAction(action);
342 // Hide the main panel before showing the action's panel.
343 PanelMultiView.hidePopup(this.panelNode);
345 let anchorNode = this.panelAnchorNodeForAction(action);
346 PanelMultiView.openPopup(panelNode, anchorNode, {
347 position: "bottomright topright",
349 }).catch(console.error);
352 _makeActivatedActionPanelForAction(action) {
353 let panelNode = document.createXULElement("panel");
354 panelNode.id = this._activatedActionPanelID;
355 panelNode.classList.add("cui-widget-panel", "panel-no-padding");
356 panelNode.setAttribute("actionID", action.id);
357 panelNode.setAttribute("role", "group");
358 panelNode.setAttribute("type", "arrow");
359 panelNode.setAttribute("flip", "slide");
360 panelNode.setAttribute("noautofocus", "true");
361 panelNode.setAttribute("tabspecific", "true");
363 let panelViewNode = null;
364 let iframeNode = null;
366 if (action.getWantsSubview(window)) {
367 let multiViewNode = document.createXULElement("panelmultiview");
368 panelViewNode = this._makePanelViewNodeForAction(action, true);
369 multiViewNode.setAttribute("mainViewId", panelViewNode.id);
370 multiViewNode.appendChild(panelViewNode);
371 panelNode.appendChild(multiViewNode);
372 } else if (action.wantsIframe) {
373 iframeNode = document.createXULElement("iframe");
374 iframeNode.setAttribute("type", "content");
375 panelNode.appendChild(iframeNode);
378 let popupSet = document.getElementById("mainPopupSet");
379 popupSet.appendChild(panelNode);
380 panelNode.addEventListener(
383 PanelMultiView.removePopup(panelNode);
389 panelNode.addEventListener(
392 action.onIframeShowing(iframeNode, panelNode);
396 panelNode.addEventListener(
403 panelNode.addEventListener(
406 action.onIframeHiding(iframeNode, panelNode);
410 panelNode.addEventListener(
413 action.onIframeHidden(iframeNode, panelNode);
420 action.onSubviewPlaced(panelViewNode);
421 panelNode.addEventListener(
424 action.onSubviewShowing(panelViewNode);
434 * Returns the node in the urlbar to which popups for the given action should
435 * be anchored. If the action is null, a sensible anchor is returned.
437 * @param action (PageActions.Action, optional)
438 * The action you want to anchor.
439 * @param event (DOM event, optional)
440 * This is used to display the feedback panel on the right node when
441 * the command can be invoked from both the main panel and another
442 * location, such as an activated action panel or a button.
443 * @return (DOM node) The node to which the action should be anchored.
445 panelAnchorNodeForAction(action, event) {
446 if (event && event.target.closest("panel") == this.panelNode) {
447 return this.mainButtonNode;
450 // Try each of the following nodes in order, using the first that's visible.
451 let potentialAnchorNodeIDs = [
452 action && action.anchorIDOverride,
453 action && this.urlbarButtonNodeIDForActionID(action.id),
454 this.mainButtonNode.id,
456 "urlbar-search-button",
458 for (let id of potentialAnchorNodeIDs) {
460 let node = document.getElementById(id);
461 if (node && !node.hidden) {
462 let bounds = window.windowUtils.getBoundsWithoutFlushing(node);
463 if (bounds.height > 0 && bounds.width > 0) {
469 let id = action ? action.id : "<no action>";
470 throw new Error(`PageActions: No anchor node for ${id}`);
473 get activatedActionPanelNode() {
474 return document.getElementById(this._activatedActionPanelID);
477 get _activatedActionPanelID() {
478 return "pageActionActivatedActionPanel";
482 * Adds or removes as necessary a DOM node for the given action in the urlbar.
484 * @param action (PageActions.Action, required)
485 * The action to place.
487 placeActionInUrlbar(action) {
488 let id = this.urlbarButtonNodeIDForActionID(action.id);
489 let node = document.getElementById(id);
491 if (!action.shouldShowInUrlbar(window)) {
493 if (action.__urlbarNodeInMarkup) {
502 let newlyPlaced = false;
503 if (action.__urlbarNodeInMarkup) {
504 this._maybeNotifyBeforePlacedInWindow(action);
505 // Allow the consumer to add the node in response to the
506 // onBeforePlacedInWindow notification.
507 node = document.getElementById(id);
511 newlyPlaced = node.hidden;
515 this._maybeNotifyBeforePlacedInWindow(action);
516 node = this._makeUrlbarButtonNode(action);
524 let insertBeforeNode = this._getNextNode(action, true);
525 this.mainButtonNode.parentNode.insertBefore(node, insertBeforeNode);
526 this.updateAction(action, null, {
529 action.onPlacedInUrlbar(node);
532 _makeUrlbarButtonNode(action) {
533 let buttonNode = document.createXULElement("hbox");
534 buttonNode.classList.add("urlbar-page-action");
535 if (action.extensionID) {
536 buttonNode.classList.add("urlbar-addon-page-action");
538 buttonNode.setAttribute("actionid", action.id);
539 buttonNode.setAttribute("role", "button");
540 let commandHandler = event => {
541 this.doCommandForAction(action, event, buttonNode);
543 buttonNode.addEventListener("click", commandHandler);
544 buttonNode.addEventListener("keypress", commandHandler);
546 let imageNode = document.createXULElement("image");
547 imageNode.classList.add("urlbar-icon");
548 buttonNode.appendChild(imageNode);
553 * Removes all the DOM nodes of the given action.
555 * @param action (PageActions.Action, required)
556 * The action to remove.
558 removeAction(action) {
559 this._removeActionFromPanel(action);
560 this._removeActionFromUrlbar(action);
561 action.onRemovedFromWindow(window);
562 this._updateMainButtonAttributes();
565 _removeActionFromUrlbar(action) {
566 let node = this.urlbarButtonNodeForActionID(action.id);
573 * Updates the DOM nodes of an action to reflect either a changed property or
576 * @param action (PageActions.Action, required)
577 * The action to update.
578 * @param propertyName (string, optional)
579 * The name of the property to update. If not given, then DOM nodes
580 * will be updated to reflect the current values of all properties.
581 * @param opts (object, optional)
582 * - panelNode: The action's node in the panel to update.
583 * - urlbarNode: The action's node in the urlbar to update.
584 * - value: If a property name is passed, this argument may contain
585 * its current value, in order to prevent a further look-up.
587 updateAction(action, propertyName = null, opts = {}) {
588 let anyNodeGiven = "panelNode" in opts || "urlbarNode" in opts;
589 let panelNode = anyNodeGiven
590 ? opts.panelNode || null
591 : this.panelButtonNodeForActionID(action.id);
592 let urlbarNode = anyNodeGiven
593 ? opts.urlbarNode || null
594 : this.urlbarButtonNodeForActionID(action.id);
595 let value = opts.value || undefined;
597 this[this._updateMethods[propertyName]](
604 for (let name of ["iconURL", "title", "tooltip", "wantsSubview"]) {
605 this[this._updateMethods[name]](action, panelNode, urlbarNode, value);
611 disabled: "_updateActionDisabled",
612 iconURL: "_updateActionIconURL",
613 title: "_updateActionLabeling",
614 tooltip: "_updateActionTooltip",
615 wantsSubview: "_updateActionWantsSubview",
618 _updateActionDisabled(
622 disabled = action.getDisabled(window)
624 // Extension page actions should behave like a transient action,
625 // and be hidden from the urlbar overflow menu if they
626 // are disabled (as in the urlbar when the overflow menu isn't available)
628 // TODO(Bug 1704139): as a follow up we may look into just set on all
629 // extension pageActions `_transient: true`, at least once we sunset
630 // the proton preference and we don't need the pre-Proton behavior anymore,
631 // and remove this special case.
632 const isProtonExtensionAction = action.extensionID;
634 if (action.__transient || isProtonExtensionAction) {
635 this.placeActionInPanel(action);
637 this._updateActionDisabledInPanel(action, panelNode, disabled);
639 this.placeActionInUrlbar(action);
642 _updateActionDisabledInPanel(
645 disabled = action.getDisabled(window)
649 panelNode.setAttribute("disabled", "true");
651 panelNode.removeAttribute("disabled");
656 _updateActionIconURL(
660 properties = action.getIconProperties(window)
662 for (let [prop, value] of Object.entries(properties)) {
664 panelNode.style.setProperty(prop, value);
667 urlbarNode.style.setProperty(prop, value);
672 _updateActionLabeling(
676 title = action.getTitle(window)
679 panelNode.setAttribute("label", title);
682 urlbarNode.setAttribute("aria-label", title);
683 // tooltiptext falls back to the title, so update it too if necessary.
684 let tooltip = action.getTooltip(window);
686 urlbarNode.setAttribute("tooltiptext", title);
691 _updateActionTooltip(
695 tooltip = action.getTooltip(window)
699 tooltip = action.getTitle(window);
702 urlbarNode.setAttribute("tooltiptext", tooltip);
707 _updateActionWantsSubview(
711 wantsSubview = action.getWantsSubview(window)
716 let panelViewID = this._panelViewNodeIDForActionID(action.id, false);
717 let panelViewNode = document.getElementById(panelViewID);
718 panelNode.classList.toggle("subviewbutton-nav", wantsSubview);
721 panelViewNode.remove();
725 if (!panelViewNode) {
726 panelViewNode = this._makePanelViewNodeForAction(action, false);
727 this.multiViewNode.appendChild(panelViewNode);
728 action.onSubviewPlaced(panelViewNode);
732 doCommandForAction(action, event, buttonNode) {
733 if (event && event.type == "click" && event.button != 0) {
736 if (event && event.type == "keypress") {
737 if (event.key != " " && event.key != "Enter") {
740 event.stopPropagation();
742 // If we're in the panel, open a subview inside the panel:
743 // Note that we can't use this.panelNode.contains(buttonNode) here
744 // because of XBL boundaries breaking Element.contains.
746 action.getWantsSubview(window) &&
748 buttonNode.closest("panel") == this.panelNode
750 let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
751 let panelViewNode = document.getElementById(panelViewNodeID);
752 action.onSubviewShowing(panelViewNode);
753 this.multiViewNode.showSubView(panelViewNode, buttonNode);
756 // Otherwise, hide the main popup in case it was open:
757 PanelMultiView.hidePopup(this.panelNode);
759 let aaPanelNode = this.activatedActionPanelNode;
760 if (!aaPanelNode || aaPanelNode.getAttribute("actionID") != action.id) {
761 action.onCommand(event, buttonNode);
763 if (action.getWantsSubview(window) || action.wantsIframe) {
764 this.togglePanelForAction(action, null, event);
769 * Returns the action for a node.
771 * @param node (DOM node, required)
772 * A button DOM node, either one that's shown in the page action panel
774 * @return (PageAction.Action) If the node has a related action and the action
775 * is not a separator, then the action is returned. Otherwise null is
778 actionForNode(node) {
782 let actionID = this._actionIDForNodeID(node.id);
783 let action = PageActions.actionForID(actionID);
785 // When a page action is clicked, `node` will be an ancestor of
786 // a node corresponding to an action. `node` will be the page action node
787 // itself when a page action is selected with the keyboard. That's because
788 // the semantic meaning of page action is on an hbox that contains an
790 for (let n = node.parentNode; n && !action; n = n.parentNode) {
791 if (n.id == "page-action-buttons" || n.localName == "panelview") {
792 // We reached the page-action-buttons or panelview container.
793 // Stop looking; no action was found.
796 actionID = this._actionIDForNodeID(n.id);
797 action = PageActions.actionForID(actionID);
800 return action && !action.__isSeparator ? action : null;
804 * The given action's top-level button in the main panel.
806 * @param actionID (string, required)
808 * @return (DOM node) The action's button in the main panel.
810 panelButtonNodeForActionID(actionID) {
811 return document.getElementById(this.panelButtonNodeIDForActionID(actionID));
815 * The ID of the given action's top-level button in the main panel.
817 * @param actionID (string, required)
819 * @return (string) The ID of the action's button in the main panel.
821 panelButtonNodeIDForActionID(actionID) {
822 return `pageAction-panel-${actionID}`;
826 * The given action's button in the urlbar.
828 * @param actionID (string, required)
830 * @return (DOM node) The action's urlbar button node.
832 urlbarButtonNodeForActionID(actionID) {
833 return document.getElementById(
834 this.urlbarButtonNodeIDForActionID(actionID)
839 * The ID of the given action's button in the urlbar.
841 * @param actionID (string, required)
843 * @return (string) The ID of the action's urlbar button node.
845 urlbarButtonNodeIDForActionID(actionID) {
846 let action = PageActions.actionForID(actionID);
847 if (action && action.urlbarIDOverride) {
848 return action.urlbarIDOverride;
850 return `pageAction-urlbar-${actionID}`;
853 // The ID of the given action's panelview.
854 _panelViewNodeIDForActionID(actionID, forUrlbar) {
855 let placementID = forUrlbar ? "urlbar" : "panel";
856 return `pageAction-${placementID}-${actionID}-subview`;
859 // The ID of the action corresponding to the given top-level button in the
860 // panel or button in the urlbar.
861 _actionIDForNodeID(nodeID) {
865 let match = nodeID.match(/^pageAction-(?:panel|urlbar)-(.+)$/);
869 // Check all the urlbar ID overrides.
870 for (let action of PageActions.actions) {
871 if (action.urlbarIDOverride && action.urlbarIDOverride == nodeID) {
879 * Call this when the main page action button in the urlbar is activated.
881 * @param event (DOM event, required)
882 * The click or whatever event.
884 mainButtonClicked(event) {
885 event.stopPropagation();
887 // On mac, ctrl-click will send a context menu event from the widget, so
888 // we don't want to bring up the panel when ctrl key is pressed.
889 (event.type == "mousedown" &&
890 (event.button != 0 ||
891 (AppConstants.platform == "macosx" && event.ctrlKey))) ||
892 (event.type == "keypress" &&
893 event.charCode != KeyEvent.DOM_VK_SPACE &&
894 event.keyCode != KeyEvent.DOM_VK_RETURN)
899 // If the activated-action panel is open and anchored to the main button,
901 let panelNode = this.activatedActionPanelNode;
902 if (panelNode && panelNode.anchorNode.id == this.mainButtonNode.id) {
903 PanelMultiView.hidePopup(panelNode);
907 if (this.panelNode.state == "open") {
908 PanelMultiView.hidePopup(this.panelNode);
909 } else if (this.panelNode.state == "closed") {
910 this.showPanel(event);
915 * Show the page action panel
917 * @param event (DOM event, optional)
918 * The event that triggers showing the panel. (such as a mouse click,
919 * if the user clicked something to open the panel)
921 showPanel(event = null) {
922 this.panelNode.hidden = false;
923 PanelMultiView.openPopup(this.panelNode, this.mainButtonNode, {
924 position: "bottomright topright",
926 }).catch(console.error);
930 * Call this on the context menu's popupshowing event.
932 * @param event (DOM event, required)
933 * The popupshowing event.
934 * @param popup (DOM node, required)
935 * The context menu popup DOM node.
937 async onContextMenuShowing(event, popup) {
938 if (event.target != popup) {
942 let action = this.actionForNode(popup.triggerNode);
943 // Only extension actions provide a context menu.
944 if (!action?.extensionID) {
945 this._contextAction = null;
946 event.preventDefault();
949 this._contextAction = action;
951 let removeExtension = popup.querySelector(".removeExtensionItem");
952 let { extensionID } = this._contextAction;
953 let addon = extensionID && (await AddonManager.getAddonByID(extensionID));
954 removeExtension.hidden = !addon;
956 removeExtension.disabled = !(
957 addon.permissions & AddonManager.PERM_CAN_UNINSTALL
963 * Call this from the menu item in the context menu that opens about:addons.
965 openAboutAddonsForContextAction() {
966 if (!this._contextAction) {
969 let action = this._contextAction;
970 this._contextAction = null;
972 let viewID = "addons://detail/" + encodeURIComponent(action.extensionID);
973 window.BrowserOpenAddonsMgr(viewID);
977 * Call this from the menu item in the context menu that removes an add-on.
979 removeExtensionForContextAction() {
980 if (!this._contextAction) {
983 let action = this._contextAction;
984 this._contextAction = null;
986 BrowserAddonUI.removeAddon(action.extensionID, "pageAction");
989 _contextAction: null,
992 * Call this on tab switch or when the current <browser>'s location changes.
995 for (let action of PageActions.actions) {
996 action.onLocationChange(window);
1001 // built-in actions below //////////////////////////////////////////////////////
1004 BrowserPageActions.bookmark = {
1005 onShowingInPanel(buttonNode) {
1006 if (buttonNode.label == "null") {
1007 BookmarkingUI.updateBookmarkPageMenuItem();
1012 PanelMultiView.hidePopup(BrowserPageActions.panelNode);
1013 BookmarkingUI.onStarCommand(event);