如何在我的Firefox扩展程序图标上添加右键单击?

时间:2014-10-15 18:52:23

标签: firefox-addon contextmenu firefox-addon-sdk right-click

美好的一天!

我再次搜索和搜索,我没有找到任何帮助来解决这个问题......

背景: 我开发了一个非常简单的Google Chrome扩展程序:只需点击一下即可向某人发送电子邮件。要配置此选项,此扩展程序上有一个选项页面,用于设置要发送的电子邮件地址。 My Google Chrome extension is available hereEnglish translation,只是文字,而不是安装)。

用户已要求我为Firefox制作此扩展程序,以便我正在处理它!

我已经阅读了有关cfx的教程,没关系。但我需要让我的扩展程序响应右键单击工具栏中的扩展程序图标。不在页面上,在我的图标上。

我使用ActionButton(或ToggleButton)将我的图标添加到工具栏中,但我找不到在右键单击上添加菜单的方法(那里已经是默认的Firefox上下文菜单,但我想添加"选项"。)

如果有人有解决方案,那就太好了!

(我不熟悉XUL。所以,如果可能的话,只有一个JavaScript解决方案,请^^)

PS:我是法国人,请原谅我的英语不好

编辑:我已经找到了如何在我的" package.json"中设置首选项。文件,但我想要自己的窗口。 如果我们可以"绑定"按钮"选项"在附加管理器到我自己的窗口,这将是完美的!

编辑2:因为对每个人都不清楚,我会在这里详细说明我对扩展的要求: - 在图标上单击(左键单击)获取当前URL并将其发送到邮件地址(确定) - 只需点击即可。这个扩展旨在非常简单! - 右键单击​​图标显示Firefox的上下文菜单,我想添加"选项"这里显示我的选项页面 - 插件管理器可以有一个"选项"按钮靠近"停用"和"调试"我想要这个选项按钮来显示我的选项页面。 => 2种查看我的选项页面的方法:通过右键单击或插件管理器,这就是我需要你帮助的原因!

4 个答案:

答案 0 :(得分:3)

一般用户界面评论

使用右键单击直接激活您的功能与系统范围内使用的常规UI相反。右键单击是系统范围的(在某些系统上),用于打开上下文菜单。在工具栏的Firefox中,这用于显示工具栏区域的上下文菜单。这是用户在使用右键单击时通常会发生的情况。你可能最好使用shift-left-click之类的东西,或者让用户定义用于激活你的功能的组合。如果您正在尝试向上下文菜单添加选项,则通常可以通过右键单击来访问该选项。

其他附加组件中使用的替代方案:

  • 带按向下箭头的按钮的第二部分。单击向下箭头可打开展开的操作或选项菜单。
  • 当鼠标悬停在按钮上时,使用工具提示显示操作或选项菜单。这是通过创建自定义工具提示来完成的,方法是将弹出窗口封装在<tooltip id="myTooltip"></tooltip>元素中,并使用<button tooltip="myTooltip"/>tooltip propertytooltip attribute)在按钮中引用它。

使用右键单击

问题似乎是附加SDK ActionButton已经抽象出你拥有任意事件监听器的能力。它也不允许您访问通常传递给事件处理程序(侦听器)的实际event object。此外,其click事件实际上似乎是command event,而不是click eventclickcommand事件之间的显着差异之一是command事件通常不会在右键单击时触发。

您将需要访问ActionButton界面之外的按钮并添加click事件的监听器,然后在您的点击事件处理程序中,您可以选择基于状态event.buttonevent.shiftKey

根据我发布的an answer for a different question调整一些代码,你会想要类似的东西(未经过修改测试):

function loadUi(buttonId) {
    if (window === null || typeof window !== "object") {
       //If you do not already have a window reference, you need to obtain one:
       //  Add a "/" to un-comment the code appropriate for your add-on type.
       /* Add-on SDK:
       var window = require('sdk/window/utils').getMostRecentBrowserWindow();
       //*/
       /* Overlay and bootstrap (from almost any context/scope):
       var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
                            .getService(Components.interfaces.nsIWindowMediator)
                            .getMostRecentWindow("navigator:browser");
       //*/
    }

    forEachCustomizableUiById(buttonId, loadIntoButton, window);
}

function forEachCustomizableUiById(buttonId ,func, myWindow) {
    let groupWidgetWrap = myWindow.CustomizableUI.getWidget(buttonId);
    groupWidgetWrap.instances.forEach(function(windowUiWidget) {
        //For each button do the load task.
        func(windowUiWidget.node);
    });
}

function loadIntoButton(buttonElement) {
    //Make whatever changes to the button you want to here.
    //You may need to save some information about the original state
    //  of the button.
    buttonElement.addEventListener("click",handleClickEvent,true);
}

function unloadUi(buttonId) {
    if (window === null || typeof window !== "object") {
       //If you do not already have a window reference, you need to obtain one:
       //  Add a "/" to un-comment the code appropriate for your add-on type.
       /* Add-on SDK:
       var window = require('sdk/window/utils').getMostRecentBrowserWindow();
       //*/
       /* Overlay and bootstrap (from almost any context/scope):
       var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
                            .getService(Components.interfaces.nsIWindowMediator)
                            .getMostRecentWindow("navigator:browser");
       //*/
    }

    forEachCustomizableUiById(buttonId, unloadFromButton, window);
}

function unloadFromButton(buttonElement) {
    //Return the button to its original state
    buttonElement.removeEventListener("click",handleClickEvent,true);
}

function handleClickEvent(event) {
    If( (event.button & 2) == 2 && event.shiftKey){
        event.preventDefault();
        event.stopPropagation();
        doMyThing();
    }
}

function doMyThing() {
    //Whatever it is that you are going to do.
}

正如上面的代码所暗示的那样,在卸载/禁用加载项时,您需要确保删除监听器。您还需要确保在新窗口打开时调用loadUi(),以便将处理程序添加到新按钮。

添加到上下文菜单

没有直接的方法来更改图标的上下文菜单。上下文菜单的ID为toolbar-context-menu。你可以做的是在上下文菜单中添加项目,通常是hidden="true"。如果您在图标上发生了右键单击的事件,则可以更改您添加的项目的hidden状态。然后在上下文菜单(<menupopup id="toolbar-context-menu">)的popuphidden事件中调用的事件处理程序中,您可以为<menuitem>元素设置hidden="true"的状态您已添加到每个浏览器窗口中的<menupopup id="toolbar-context-menu">

有些事情:

function loadIntoContextMenu(win){
    let doc = win.ownerDocument;
    let contextPopupEl = doc.getElementById("toolbar-context-menu");
    contextPopupEl.insertAdjacentHTML("beforeend", 
          '<menuitem label="My Item A" id="myExtensionPrefix-context-itemA"' 
        +      ' oncommand="doMyThingA();" hidden="true" />'
        + '<menuitem label="My Item B" id="myExtensionPrefix-context-itemB"'
        +      ' oncommand="doMyThingB();" hidden="true" />');
    contextPopupEl.addEventListener("popuphidden",hideMyContextMenuItems,true);
}
function unloadFromContextMenu(win){
    let doc = win.ownerDocument;
    let contextPopupEl = doc.getElementById("toolbar-context-menu");
    let itemA = doc.getElementById("myExtensionPrefix-context-itemA");
    let itemB = doc.getElementById("myExtensionPrefix-context-itemB");
    contextPopupEl.removeChild(itemA);
    contextPopupEl.removeChild(itemB);
    contextPopupEl.removeEventListener("popuphidden",hideContextMenuItems,true);
}
function setHiddenMyContextMenuItems(element,text){
    //The element is the context menu.
    //text is what you want the "hidden" attribute to be set to.
    let child = element.firstChild;
    while(child !== null){
        if(/myExtensionPrefix-context-item[AB]/.test(child.id)){
            child.setAttribute("hidden",text);
        }
        child = child.nextSibling;
    }
}
function showContextMenuItems(event){
    //The target of this event is the button for which you want to change the
    //  context menu.  We need to find the context menu element.
    let contextmenuEl = event.target.ownerDocument
                                    .getElementById("toolbar-context-menu");
    setHiddenMyContextMenuItems(contextmenuEl,"false");
}
function hideContextMenuItems(event){
    //This is called for the popuphidden event of the context menu.
    setHiddenMyContextMenuItems(event.target,"true");
}

//Change the handleClickEvent function in the code within the earlier section: 
function handleClickEvent(event) {
    If( (event.button & 2) == 2){
        //don't prevent propagation, nor the default as the context menu
        //  showing is desired.
        showContextMenuItems(event);
    }
}

同样,我还没有测试过这个。它应该展示一种方法来实现你想要的东西。

但是,鉴于我们正在讨论上下文菜单,最好使用contextmenu事件而不是click事件并测试右键单击。在这种情况下,我们将上面的一些函数更改为:

function loadIntoButton(buttonElement) {
    //Make whatever changes to the button you want to here.
    buttonElement.addEventListener("contextmenu",handleContextmenuEvent,true);
}

function handleContextmenuEvent(event) {
    showContextMenuItems(event);
}

您可以使用nsIWindowMediator获取每个打开的主浏览器窗口。以下函数from MDN将为每个打开的窗口运行您传递给它的函数:

Components.utils.import("resource://gre/modules/Services.jsm");
function forEachOpenWindow(todo)  // Apply a function to all open browser windows
{
    var windows = Services.wm.getEnumerator("navigator:browser");
    while (windows.hasMoreElements()) {
      todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
    }
}

在附加SDK中:

function forEachOpenWindow(todo)  // Apply a function to all open browser windows
    var windows = require("sdk/windows");
    for (let window of windows.browserWindows) {
      todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
    }
}

您可以使用以下代码(也是from MDN)为新窗口添加一个调用loadIntoContextMenu的侦听器:

Components.utils.import("resource://gre/modules/Services.jsm");
Services.wm.addListener(WindowListener);
var WindowListener =
{
    onOpenWindow: function(xulWindow)
    {
        var window = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindow);
        function onWindowLoad()
        {
            window.removeEventListener("load",onWindowLoad);
            if (window.document.documentElement.getAttribute("windowtype") == "navigator:browser"){
                loadIntoContextMenu(window);
                //It would be better to only do this for the current window, but
                //  it does not hurt to do it to all of them again.
                loadUi(buttonId);
            }
        }
        window.addEventListener("load",onWindowLoad);
    },
    onCloseWindow: function(xulWindow) { },
    onWindowTitleChange: function(xulWindow, newTitle) { }
};

答案 1 :(得分:2)

我已经实施了一个具有主要和辅助操作的menu-button。虽然它不是右/左点击,但按钮有两面:

Firefox menu-button

这允许您将两个不同的操作与按钮相关联,而不会改变Firefox的常用上下文菜单流。下载文件on GitHub并将其存储在lib folder

用法类似于其他按钮类型。在main.js(或lib目录中的任何js文件)中包含以下代码

const { MenuButton } = require('./menu-button');
var btn = MenuButton({
  id: 'my-menu-button',
  label: 'My menu-button',
  icon: {
    "16": "./firefox-16.png",
    "32": "./firefox-32.png"
  },
  onClick: click
});

click函数将与statetoggle按钮传递相同的action对象,并将传递一个额外的布尔参数:isMenu 。它应该像这样使用

function click(state, isMenu) {
  if (isMenu) {
    //menu-button clicked
  } else {
    //icon clicked
  }
}

答案 2 :(得分:1)

在回答完这个问题之后,我在Chrome上尝试了你的扩展,看到我的答案可能不是你想要的,所以我会提出不同的建议(留下另一个答案,因为我觉得它有用人们在一个按钮上寻找多个动作。)

我要说的一件事是(某些)Chrome用户知道Options菜单项指的是扩展名而不是浏览器选项。这些用户知道菜单项在那里,并使用它来更改其扩展设置。 Firefox用户不希望出现这种情况,因为上下文菜单操作都会影响浏览器,而不会影响扩展。以同样的方式,(某些)Firefox用户知道要更改其扩展程序设置,他们必须导航到about:addons(或工具/插件),然后点击扩展程序旁边的Preferences按钮。这是改变您的偏好的预期途径。所以我认为添加上下文菜单选项非常复杂,并不是一个好的解决方案。

相反,如果您的用户尚未设置自己的偏好设置,我认为您应该在Chrome中执行以下操作:创建Panel,将其与您的按钮相关联(使用position: button面板构造函数),并告诉用户他们需要通过导航到工具/插件来设置他们的首选项。如果您使用simple prefs模块,则扩展程序旁边会显示Preferences按钮,您在package.json中设置的选项也可以在此处进行修改。

不幸的是,这是一个非常基本的页面,看起来不像你做的漂亮的HTML选项页面。

Bonne chance。

答案 3 :(得分:1)

除了其他答案中的所有保留以及不重用现有工具栏上下文菜单之外,还有以下方法:

const utils     = require('sdk/window/utils');
const window    = utils.getMostRecentBrowserWindow();
const doc       = window.document;
const XUL_NS    = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const { getNodeView } = require("sdk/view/core");

let ButContext = doc.createElementNS(XUL_NS,'menupopup');
ButContext.setAttribute("id","customIDString");

menuitem = doc.createElementNS(XUL_NS,"menuitem");
menuitem.setAttribute("label", "customLabelString");
ButContext.appendChild(item);

var myBut = require("sdk/ui/button/action").ActionButton({
    id: "myButton",
    label: "Right click me!"
    // And other options
});

//Here is the context magic
getNodeView(myBut).setAttribute('context', "customIDString");

//either ; gets removed together with the button   
getNodeView(myBut).appendChild(ButContext);
//or ; the correct place but needs to be removed manually
doc.getElementById("mainPopupSet").appendChild(ButContext);