In a Firefox non-overlay extension, how to add text to a browser toolbar icon?

我与影子孤独终老i 提交于 2019-12-08 09:50:56

问题


How do you add text to your add-on's browser toolbar button?

I'm interested in something exactly like how the download manager button shows the "2h" text above a progress bar, when 2 hours is remaining to download the requested file(s).

Is it even possible to do this without resorting to having a whole lot of pre-made images including the text?


回答1:


When you have a known working example, one way to figure out how this type of thing is implemented in the DOM (the entire browser window is a DOM) is to install the add-on DOM Inspector and use it to investigate what the contents of the DOM looks like. You probably also want, the Element Inspector add-on which is a very useful addition to the DOM Inspector (shift-right-click opens the DOM Inspector to the element clicked). You might also find Stacked Inspector helpful.

In this instance, the downloads button starts out as a <toolbarbutton/>. Specifically:

<toolbarbutton id="downloads-button" 
    class="toolbarbutton-1 chromeclass-toolbar-additional" 
    key="key_openDownloads" oncommand="DownloadsIndicatorView.onCommand(event);"
    ondrop="DownloadsIndicatorView.onDrop(event);"
    ondragover="DownloadsIndicatorView.onDragOver(event);"
    ondragenter="DownloadsIndicatorView.onDragOver(event);"
    label="Downloads" removable="true" cui-areatype="toolbar" 
    tooltip="dynamic-shortcut-tooltip"/>

Once a download starts the structure is changed to:

<toolbarbutton id="downloads-button" 
    class="toolbarbutton-1 chromeclass-toolbar-additional" 
    key="key_openDownloads" oncommand="DownloadsIndicatorView.onCommand(event);"
    ondrop="DownloadsIndicatorView.onDrop(event);" 
    ondragover="DownloadsIndicatorView.onDragOver(event);" 
    ondragenter="DownloadsIndicatorView.onDragOver(event);" 
    label="Downloads" removable="true" cui-areatype="toolbar"
    tooltip="dynamic-shortcut-tooltip" 
    indicator="true" progress="true" counter="true">
       <stack id="downloads-indicator-anchor" class="toolbarbutton-icon">
           <vbox id="downloads-indicator-progress-area" pack="center">
               <description id="downloads-indicator-counter" value="6h"/>
               <progressmeter id="downloads-indicator-progress" class="plain"
                   min="0" max="100" value="3.484329371737533"/>
           </vbox>
           <vbox id="downloads-indicator-icon"/>
       </stack>
</toolbarbutton>

The above structure change is contained in chrome://browser/content/downloads/indicatorOverlay.xul which is loaded as an overlay onto the main browser document.

The code which controls the indicator is in chrome://browser/content/downloads/indicator.js. The code that actually loads the overlay is ensureOverlayLoaded() in chrome://browser/content/downloads/downloads.js.

Making the change without using an XUL overlay
Given that you are wanting this to be an overlay extension, you probably don't want to use an XUL Overlay. Thus, you would need to run through all places where your button exist and make the change to the structure in each. However, at this point you are probably dealing with a button which you have added via the CustomizableUI. With the CustomizableUI you don't run through the open windows, you run through the nodes which it provides to you. It is necessary to do it this way because the user many have placed the button you are interested in within the customization pallet instead of in the toolbar. If that is the case, trying to find the button with document.getElementById() using the browser window's document will fail. To run through these using the CustomizableUI interface, you could use something like the following:

function loadUi() {
    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("downloads-button", loadIntoButton, window);
}

function forEachCustomizableUiById(buttonId ,func, myWindow) {
    let groupWidgetWrap = myWindow.CustomizableUI.getWidget(buttonId);
    groupWidgetWrap.instances.forEach(function(perWindowUiWidget) {
        //For each button do the load task.
        func(perWindowUiWidget.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.
}

Obviously, unloading is just the reverse of loading:

function unloadUi() {
    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("downloads-button", unloadFromButton, window);
}

function unloadFromButton(buttonElement) {
    //Return the button to its original state
}

In the specific instance of changing the button to its download state you could probably do the following:

function loadIntoButton(buttonElement) {
    buttonElement.setAttribute("indicator","true");
    buttonElement.setAttribute("progress","true");
    buttonElement.setAttribute("counter","true");
    let additional = ''
       + '<stack id="downloads-indicator-anchor" class="toolbarbutton-icon">'
       + '    <vbox id="downloads-indicator-progress-area" pack="center">'
       + '        <description id="downloads-indicator-counter" value="6h"/>'
       + '        <progressmeter id="downloads-indicator-progress" class="plain"'
       + '            min="0" max="100" value="3.484329371737533"/>'
       + '    </vbox>'
       + '    <vbox id="downloads-indicator-icon"/>'
       + '</stack>';
    buttonElement.insertAdjacentHTML("beforeend",additional);
}

function unloadFromButton(buttonElement) {
    buttonElement.removeAttribute("indicator","true");
    buttonElement.removeAttribute("progress","true");
    buttonElement.removeAttribute("counter","true");
    buttonElement.removeChild(buttonElement.getElementsByTagName("stack")[0]);
}

I have not yet had a chance to test the above code, so there may be some issues.

Creating a complex CustomizableUI widget from scratch:
If it is that you are wanting to create something more complex from the beginning, then you should use the CustomizableUI and create a custom widget. The CustomizableUI.jsm page at MDN has a "simple" example of a complex widget which is:

CustomizableUI.createWidget({ 
    //Must run createWidget before windowListener.register because the register
    //  function needs the button added first.
        id: 'navigator-throbber',
        type: 'custom',
        defaultArea: CustomizableUI.AREA_NAVBAR,
        onBuild: function(aDocument) {
            var toolbaritem = aDocument.createElementNS(
                'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
                'toolbaritem');
            var image = aDocument.createElementNS(
                'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
                'image');
            var props = {
                id: 'navigator-throbber',
                title: 'Activity Indicator',
                align: 'center',
                pack: 'center',
                mousethrough: 'always',
                removable: 'true',
                sdkstylewidget: 'true',
                overflows: false
            };
            for (var p in props) {
                toolbaritem.setAttribute(p, props[p]);
            }

            toolbaritem.appendChild(image);
            return toolbaritem;
        }
    });

As a more complex example, Noitidart has provided a summary of the code the browser uses for a custom widget used for it's zoom controls and edit controls in the Panel. It is available at this gist: https://gist.github.com/Noitidart/10902477








Custom CSS and XML bindings for the Firefox download-button: To fully implement your own version of the download button you will also need the CSS and custom XML bindings which are used by Mozilla.

CSS for the download-button (from chrome://browser/content/browser.css:

#downloads-button {
  -moz-binding: url("chrome://browser/content/downloads/download.xml#download-toolbarbutton");
}

/*** Visibility of downloads indicator controls ***/

/* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
   and just show the icon. This is a hack to side-step very weird layout bugs that
   seem to be caused by the indicator stack interacting with the menu panel. */
#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > image.toolbarbutton-icon,
#downloads-button[indicator][cui-areatype="menu-panel"] > #downloads-indicator-anchor {
  display: none;
}

toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > image.toolbarbutton-icon {
  display: -moz-box;
}

toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > stack.toolbarbutton-icon {
  display: none;
}

#downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
#downloads-button:not(:-moz-any([progress], [counter], [paused]))
                                                   #downloads-indicator-progress-area
{
  visibility: hidden;
}

/* Hacks for toolbar full and text modes, until bug 573329 removes them */

toolbar[mode="text"] > #downloads-button {
  display: -moz-box;
  -moz-box-orient: vertical;
  -moz-box-pack: center;
}

toolbar[mode="text"] > #downloads-button > .toolbarbutton-text {
  -moz-box-ordinal-group: 1;
}

toolbar[mode="text"] > #downloads-button > .toolbarbutton-icon {
  display: -moz-box;
  -moz-box-ordinal-group: 2;
  visibility: collapse;
}

Custom download-button XML bindings (from chrome://browser/content/downloads/download.xml):

<?xml version="1.0"?>
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
<!-- vim: set ts=2 et sw=2 tw=80: -->

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">

<bindings id="downloadBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="download"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <content orient="horizontal"
             align="center"
             onclick="DownloadsView.onDownloadClick(event);">
      <xul:image class="downloadTypeIcon"
                 validate="always"
                 xbl:inherits="src=image"/>
      <xul:image class="downloadTypeIcon blockedIcon"/>
      <xul:vbox pack="center"
                flex="1"
                class="downloadContainer"
                style="width: &downloadDetails.width;">
        <!-- We're letting localizers put a min-width in here primarily
             because of the downloads summary at the bottom of the list of
             download items. An element in the summary has the same min-width
             on a description, and we don't want the panel to change size if the
             summary isn't being displayed, so we ensure that items share the
             same minimum width.
             -->
        <xul:description class="downloadTarget"
                         crop="center"
                         style="min-width: &downloadsSummary.minWidth2;"
                         xbl:inherits="value=target,tooltiptext=target"/>
        <xul:progressmeter anonid="progressmeter"
                           class="downloadProgress"
                           min="0"
                           max="100"
                           xbl:inherits="mode=progressmode,value=progress"/>
        <xul:description class="downloadDetails"
                         crop="end"
                         xbl:inherits="value=status,tooltiptext=statusTip"/>
      </xul:vbox>
      <xul:stack>
        <xul:button class="downloadButton downloadCancel"
                    tooltiptext="&cmd.cancel.label;"
                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
        <xul:button class="downloadButton downloadRetry"
                    tooltiptext="&cmd.retry.label;"
                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
        <xul:button class="downloadButton downloadShow"
                    tooltiptext="&cmd.show.label;"
                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
      </xul:stack>
    </content>
  </binding>

  <binding id="download-full-ui"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <resources>
      <stylesheet src="chrome://browser/content/downloads/download.css"/>
    </resources>

    <content orient="horizontal" align="center">
      <xul:image class="downloadTypeIcon"
                 validate="always"
                 xbl:inherits="src=image"/>
      <xul:image class="downloadTypeIcon blockedIcon"/>
      <xul:vbox pack="center" flex="1">
        <xul:description class="downloadTarget"
                         crop="center"
                         xbl:inherits="value=displayName,tooltiptext=displayName"/>
        <xul:progressmeter anonid="progressmeter"
                           class="downloadProgress"
                           min="0"
                           max="100"
                           xbl:inherits="mode=progressmode,value=progress"/>
        <xul:description class="downloadDetails"
                         style="width: &downloadDetails.width;"
                         crop="end"
                         xbl:inherits="value=status,tooltiptext=statusTip"/>
      </xul:vbox>

      <xul:button class="downloadButton downloadCancel"
                  tooltiptext="&cmd.cancel.label;"
                  oncommand="goDoCommand('downloadsCmd_cancel')"/>
      <xul:button class="downloadButton downloadRetry"
                  tooltiptext="&cmd.retry.label;"
                  oncommand="goDoCommand('downloadsCmd_retry')"/>
      <xul:button class="downloadButton downloadShow"
                  tooltiptext="&cmd.show.label;"
                  oncommand="goDoCommand('downloadsCmd_show')"/>

    </content>
  </binding>

  <binding id="download-toolbarbutton"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <children />
      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,wrap"/>
      <xul:label class="toolbarbutton-multiline-text" flex="1"
                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
    </content>
  </binding>
</bindings>


来源:https://stackoverflow.com/questions/26299849/in-a-firefox-non-overlay-extension-how-to-add-text-to-a-browser-toolbar-icon

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!