【问题标题】:How to add a right click on my Firefox extension's icon?如何在我的 Firefox 扩展程序图标上添加右键?
【发布时间】:2014-10-15 18:52:23
【问题描述】:

美好的一天!

我找了又找,没有找到任何解决这个问题的方法……

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

用户要求我为 Firefox 制作这个扩展,所以我正在努力!

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

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

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

(我对 XUL 不熟悉。所以,如果可能,请提供仅 JavaScript 的解决方案 ^^)

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

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

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

【问题讨论】:

  • 多么漂亮的扩展,干得好
  • 重新阅读您的问题,我不清楚您想要完成什么确切。 1.您是否希望用户右键单击您的扩展程序图标时发生的唯一事情是它会打开您的选项页面? 2. 您是否想在用户右键单击您的图标时出现的上下文菜单中添加选择选项的可能性? 3. 您是否只是想让用户以某种方式从您的图标访问您的选项页面?在这种情况下,一种与 Firefox 用户界面一致的方式(例如,下拉菜单)。
  • @MarcoBonelli:感谢您的称赞
  • @Makyen :首先,感谢您对我的问题的更正。然后,回答您的问题: 1. 不,我不想覆盖 Firefox 的右键单击 2. 是的,实际上我在图标的上下文菜单上有 5 个选项。我只想在此菜单中添加“选项”。 3.我希望拥有与 Chrome 相同的 UI,如果不可能,我会探索其他解决方案。
  • 我有一个类似的问题,但是要将项目添加到在我的插件按钮上右键单击下拉菜单中的项目后生成的上下文菜单。令人惊讶的是,上下文菜单是 sdk 似乎忘记提供访问权限的东西。我也找了又找,还没有找到。

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


【解决方案1】:

一般的 UI cmets

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

其他插件中使用的替代品:

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

使用右键单击

问题似乎在于 Add-on 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 事件上调用的事件处理程序中,您可以为已添加到 @987654358 的 <menuitem> 元素设置 hidden="true" 的状态@ 在每个浏览器窗口中。

类似的东西:

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) { }
};

【讨论】:

  • 关于您的一般 UI 评论,我在问题中添加了 EDIT 2 来描述我想要做什么。关于你的例子,我很抱歉,但我无法实现它,我不知道在哪里调用“loadUI”方法......(我在 Firefox 的扩展和一般的 Javascript 中都是新手)。但是我找到了一种右键单击的方法:使用小部件而不是操作/切换按钮。 (developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Annotator/…) 它已被弃用,但它可以完成工作。如果您想更详细地解释我,我会更喜欢使用您的解决方案。谢谢!
  • 我添加了一些代码,这些代码应该演示一种方法,您可以使用该方法仅更改按钮的上下文菜单。 loadUi 应该在您的扩展启动代码中调用,并且任何时候打开一个新的浏览器窗口。如果您的扩展程序被禁用或卸载,则应调用 unloadUi。正常关机时不需要调用。
  • 我忘记了更新答案时不会通知提出问题的人。我在调用 loadUI 时添加了附加代码寻址,并展开了一些讨论。
  • 非常感谢 Makyen 提供这么长的代码示例。当我有足够的时间理解它时,我会尝试这个!
  • 关于声明:Using right-click to directly activate your function is contrary to the general UI that is used system wide. 我不敢苟同。大多数 Fx 附加组件以及我在许多操作系统上使用过的大多数程序几乎总是有按钮,您可以右键单击以获得context menu。菜单会根据您右键单击的上下文进行更改。这被暗示为基于名称的一般概念。只是在最近几年,SDK 才无缘无故地试图侵蚀这种常见的做法,并实现被多次点击混淆的 UI。
【解决方案2】:

我已经实现了一个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 函数将传递与toggleaction 按钮相同的state 对象,并将传递一个额外的布尔参数:isMenu。应该这样使用

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

【讨论】:

  • 哦,太好了!我想用这个,看起来很简单,这就是我需要的^^
【解决方案3】:

我在回答这个问题后在 Chrome 上尝试了你的扩展程序,发现我的答案可能不是你想要的,所以我会提出一个不同的建议(留下另一个答案,因为我认为它对人们在单个按钮上寻找多个操作)。

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

相反,如果你的用户还没有设置他们的偏好,我认为你应该做你已经在 Chrome 中做的事情:创建一个Panel,将它与你的按钮相关联(通过在面板构造函数中使用position: button) ,并告诉您的用户他们需要通过导航到工具/插件来设置他们的偏好。如果您使用simple prefs 模块,则您的扩展程序旁边会出现一个Preferences 按钮,并且您在package.json 中设置的选项将可以在那里进行编辑。

很遗憾,这是一个非常基本的页面,看起来不像您制作的漂亮的 HTML 选项页面。

好机会。

【讨论】:

  • 确实,您的建议不是我要搜索的内容,但您的整个答案正是我想知道的……当将扩展程序“移植”到另一个浏览器时,您不会完全相同,因为浏览器的“规则”和用户使用。你的“双”按钮会让我想到的东西!顺便说一句,我找到了 simple-pref,但这个丑陋的设置页面让我感觉很糟糕......我发现一些扩展打开了另一个窗口而不是这个丑陋的窗口,我需要一种方法让插件管理器中的这个按钮打开我的“nice “选项页面!我想我会在另一个线程中这样做,以免在这个线程中增加问题!
  • 是的,它是just asked yesterday,但解决方案不是最佳的。如果您认为您的问题已得到解答,请随意投票或将其中一个答案标记为解决方案。
  • @willma :感谢您提供另一个问题的链接!我会在测试这些解决方案后立即投票(接下来的 2-3 天我没有时间...)
【解决方案4】:

除了其他答案中的所有保留并且不重复使用现有的工具栏上下文菜单,方法如下:

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);

【讨论】:

  • 从现在起一年或更短的时间内,当 XUL 被完全删除时,这不会是一个问题吗?
  • ButContext.appendChild(item); 应该是ButContext.appendChild(menuitem);
猜你喜欢
  • 2022-12-19
  • 1970-01-01
  • 1970-01-01
  • 2012-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-27
相关资源
最近更新 更多