【问题标题】:How to create a bottom-docked panel with XUL in Firefox?如何在 Firefox 中使用 XUL 创建底部停靠面板?
【发布时间】:2014-09-21 17:23:50
【问题描述】:

对于我的插件,我想创建一个底部停靠的“面板”来显示信息。类似的例子是 Firefox 自己的 Web 控制台面板,可以在 Web 开发人员工具下切换。

我试图挖掘代码,但无法弄清楚它是如何实现的。有人可以给我一个关于如何使用 XUL 创建它的基本解释,或者指出我正确的方向吗?

【问题讨论】:

    标签: javascript firefox-addon xul


    【解决方案1】:

    Web 控制台不是sidebar。在 Fireox 中只有一个侧边栏,它可以在浏览器内容的左侧或右侧。侧边栏是 UI 的固定部分,即使您更改选项卡也是如此。它通常用于内容、历史记录、书签或其他不会因您正在查看的标签而改变的此类信息。

    如果您还没有安装 DOM Inspector 插件,我建议您对此类内容进行调查。

    Web 控制台包含在其所在选项卡下<tabbrowser> 内的<iframe> 中。

    iframe 的 XUL 是: <iframe xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="devtools-toolbox-bottom-iframe" height="436" tooltip="aHTMLTooltip" src="chrome://browser/content/devtools/framework/toolbox.xul"/>

    首先是<splitter>,然后<iframe> 插入到<hbox class="browserSidebarContainer"> 之后,用于包含它的选项卡。

    通过调用gDevToolsBrowser.selectToolCommand(gBrowser, "webconsole");打开网络控制台

    gDevToolsBrowser 是从resource:///modules/devtools/gDevTools.jsm 的内容在chrome://browser/content/browser.js 中定义的

    它们实际上是在resource:///modules/devtools/framework/toolbox-hosts.js 内的函数SH_create 中创建的

    所有这些chrome://resource:/// URL 都应该在Firefox 中工作。 Firefox 安装有大量文件打包在三个文件 omni.ja 中。这些文件位于 /omni.ja/browser/omni.ja/webart/ omn​​i.jaomni.ja 文件只是扩展名重命名的 zip 格式文件。为了方便访问这些文件,我经常将它们解压到目录中(Firefox 安装目录树之外)。我发现当我想弄清楚某事是如何完成时,这使得搜索和操作文件变得更加容易。

    如果您只是在寻找可以创建类似于用于 Web 控制台的框的代码,那么复杂性取决于您所操作的上下文。以下内容几乎可以在任何地方工作:

    /**
     *   Creates an <iframe> based panel within the current tab,
     *   or opens a window, for use as an user interface box.
     *   If it is not a window, it is associated with the current
     *   browser tab.
     * @param location 
     *        Placement of the panel [right|left|top|bottom|window]
     *        The default location is "right".
     * @param size
     *        Width if on left or right. Height if top or bottom.
     *        Both width and height if location="window" unless
     *        features is a string. 
     *        Default is 400.
     * @param id
     *        The ID to assign to the iframe. Default is
     *        "makyen-interface-panel"
     *        The <splitter> will be assigned the
     *        ID = id + "-splitter"
     * @param chromeUrl
     *        This is the chrome://  URL to use for the contents
     *        of the iframe or the window.
     *        the default is:
     *        "chrome://browser/content/devtools/framework/toolbox.xul"
     * @param features
     *        The features string for the window. See:
     *        https://developer.mozilla.org/en-US/docs/Web/API/Window.open
     * returns [splitterEl, iframeEl]
     *        The elements for the <splitter> and <iframe>
     *
     * Copyright 2014 by Makyen.
     * Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
     **/
    function createInterfacePanelIframe(location,size,id,chromeUrl,features) {
        //defaults
        size = ( (typeof size !== "number") || size<1) ? 400 : size; 
        id = typeof id !== "string" ? "makyen-interface-panel" : id;
        chromeUrl = typeof chromeUrl !== "string"
            ? "chrome://browser/content/devtools/framework/toolbox.xul"
            : chromeUrl;
    
        //Create some common variables if they do not exist.
        //  This should work from any Firefox context.
        //  Depending on the context in which the function is being run,
        //  this could be simplified.
        if (typeof window === "undefined") {
            //If there is no window defined, get the most recent.
            var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
                               .getService(Components.interfaces.nsIWindowMediator)
                               .getMostRecentWindow("navigator:browser");
        }
        if (typeof document === "undefined") {
            //If there is no document defined, get it
            var document = window.content.document;
        }
        if (typeof gBrowser === "undefined") {
            //If there is no gBrowser defined, get it
            var gBrowser = window.gBrowser;
        }
    
        //Get the current tab & notification box (container for tab UI).
        let tab = gBrowser.selectedTab;
        let browserForTab = gBrowser.getBrowserForTab( tab );
        let notificationBox = gBrowser.getNotificationBox( browserForTab );
        let ownerDocument = gBrowser.ownerDocument;
    
        //Create the <iframe> use
        //ownerDocument for the XUL namespace.
        let iframeEl = ownerDocument.createElement("iframe");
        iframeEl.id = id;
        iframeEl.setAttribute("src",chromeUrl);
        iframeEl.setAttribute("height", size.toString());
        iframeEl.setAttribute("width", size.toString());
    
        //Call createInterfacePanel, pass the size if it is to be a window.
        let splitterEl;
        if(location == "window" ) {
            splitterEl = createInterfacePanel(location, size, size
                                            ,id + "-splitter", chromeUrl, features);
            return [splitterEl, null];
        } else {
            splitterEl = createInterfacePanel(location, iframeEl, iframeEl
                                            ,id + "-splitter", chromeUrl, features);
        }
        return [splitterEl, iframeEl];
    }
    
    /**
     * Creates a panel within the current tab, or opens a window, for use as a
     *   user interface box. If not a window, it is associated with the current
     *   browser tab.
     * @param location 
     *        Placement of the panel [right|left|top|bottom|window]
     *        The default location is "right".
     * @param objectEl
     *        The element of an XUL object that will be inserted into
     *        the DOM such that it is within the current tab.
     *        Some examples of possible objects are <iframe>,
     *        <browser>, <box>, <hbox>, <vbox>, etc.
     *        If the location="window" and features is not a string
     *        and this is a number then it is used as the width of the
     *        window.
     *        If features is a string, it is assumed the width is
     *        set in that, or elsewhere (e.g. in the XUL).
     * @param sizeEl
     *        The element that contains attributes of "width" and 
     *        "height". If location=="left"|"right" then the 
     *        "height" attribute is removed prior to the objectEl
     *        being inserted into the DOM.
     *        A spearate reference for the size element in case the
     *        objectEl is a documentFragment containing multiple elements.
     *        However, normal usage is for objectEl === sizeEl when
     *        location != "window".
     *        When location == "window" and features is not a string,
     *        and sizeEl is a number then it is used as the height
     *        of the window.
     *        If features is a string, it is assumed the height is
     *        set in that, or elsewhere (e.g. in the XUL).
     * @param id
     *        The ID to assign to the <splitter>. The default is:
     *        "makyen-interface-panel-splitter".
     * @param chromeUrl
     *        This is the chrome://  URL to use for the contents
     *        of the window.
     *        the default is:
     *        "chrome://browser/content/devtools/framework/toolbox.xul"
     * @param features
     *        The features string for the window. See:
     *        https://developer.mozilla.org/en-US/docs/Web/API/Window.open
     * returns
     *        if location != "window":
     *           splitterEl, The element for the <splitter>.
     *        if location == "window":
     *           The windowObjectReference returned by window.open().
     *
     * Copyright 2014 by Makyen.
     * Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
     **/
    function createInterfacePanel(location,objectEl,sizeEl,id,chromeUrl,features) {
        //Set location default:
        location = typeof location !== "string" ? "right" : location;
        if(location == "window") {
            if(typeof features !== "string") {
                let width = "";
                let height = "";
                if(typeof objectEl == "number") {
                    width = "width=" + objectEl.toString() + ",";
                }
                if(typeof sizeEl == "number") {
                    height = "height=" + sizeEl.toString() + ",";
                }
                features = width + height
                           + "menubar=no,toolbar=no,location=no,personalbar=no"
                           + ",status=no,chrome=yes,resizable,centerscreen";
            }
        }
        id = typeof id !== "string" ? "makyen-interface-panel-splitter" : id;
        chromeUrl = typeof chromeUrl !== "string"
            ? "chrome://browser/content/devtools/framework/toolbox.xul"
            : chromeUrl;
    
        //Create some common variables if they do not exist.
        //  This should work from any Firefox context.
        //  Depending on the context in which the function is being run,
        //  this could be simplified.
        if (typeof window === "undefined") {
            //If there is no window defined, get the most recent.
            var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
                               .getService(Components.interfaces.nsIWindowMediator)
                               .getMostRecentWindow("navigator:browser");
        }
        if (typeof document === "undefined") {
            //If there is no document defined, get it
            var document = window.content.document;
        }
        if (typeof gBrowser === "undefined") {
            //If there is no gBrowser defined, get it
            var gBrowser = window.gBrowser;
        }
    
        //Get the current tab & notification box (container for tab UI).
        let tab = gBrowser.selectedTab;
        let browserForTab = gBrowser.getBrowserForTab( tab );
        let notificationBox = gBrowser.getNotificationBox( browserForTab );
        let ownerDocument = gBrowser.ownerDocument;
    
    
        //Create a Document Fragment.
        //If doing multiple DOM additions, we should be in the habit
        //  of doing things in a way which causes the least number of reflows.
        //  We know that we are going to add more than one thing, so use a
        //  document fragment.
        let docFrag = ownerDocument.createDocumentFragment();
    
        //ownerDocument must be used here in order to have the XUL namespace
        //  or the splitter causes problems.
        //  createElementNS() does not work here.
        let splitterEl = ownerDocument.createElement("splitter");
        splitterEl.id =  id ;
    
        //Look for the child element with class="browserSidebarContainer".
        //It is the element in procimity to which the <splitter>
        //and objectEl will be placed.
        let theChild = notificationBox.firstChild;
        while (!theChild.hasAttribute("class")
            || !theChild.getAttribute("class").contains("browserSidebarContainer")
        ) {
            theChild = theChild.nextSibling;
            if(!theChild) {
                //We failed to find the correct node.
                //This implies that the structure Firefox
                //  uses has changed significantly and it should 
                //  be assumed that the extension is no longer compatible.
                return null;
            }
        }
    
        let toReturn = null;
        switch(location) {
            case "window"    :
                return window.open(chromeUrl,"_blank",features);
                break;
            case "top"    :
                if(sizeEl) {
                    sizeEl.removeAttribute("width");
                }
                docFrag.appendChild(objectEl);
                docFrag.appendChild(splitterEl);
                //Inserting the document fragment results in the same
                //  DOM structure as if you Inserted each child of the
                //  fragment separately. (i.e. the document fragment
                //  is just a temporary container).
                //Insert the interface prior to theChild.
                toReturn = notificationBox.insertBefore(docFrag,theChild);
                break;
            case "bottom" :
                if(sizeEl) {
                    sizeEl.removeAttribute("width");
                }
                docFrag.appendChild(splitterEl);
                docFrag.appendChild(objectEl);
                //Insert the interface just after theChild.
                toReturn = notificationBox.insertBefore(docFrag,theChild.nextSibling);
                break;
            case "left"   :
                if(sizeEl) {
                    sizeEl.removeAttribute("height");
                }
                docFrag.appendChild(objectEl);
                //Splitter is second in this orientaiton.
                docFrag.appendChild(splitterEl);
                //Insert the interface as the first child of theChild.
                toReturn = theChild.insertBefore(docFrag,theChild.firstChild);
                break;
            case "right"  :
            default       :
                //Right orientaiton, the default.
                if(sizeEl) {
                    sizeEl.removeAttribute("height");
                }
                docFrag.appendChild(splitterEl);
                docFrag.appendChild(objectEl);
                //Insert the interface as the last child of theChild.
                toReturn = theChild.appendChild(docFrag);
                break;
        }
        return splitterEl;
    }
    

    注意:toolbox-hosts.js 中的代码使用方法getSidebarContainer() 来查找要附加到的容器。没有关于该方法的文档,所以我使用了getNotificationBox()

    【讨论】:

    • 我能够根据您的代码创建一个工作面板。感谢您清晰而有启发性的解释。 :)
    • 很高兴这样做。我已将代码扩展为几个函数,它们将使用
    【解决方案2】:

    它被称为侧边栏。我在右边做了一个:https://gist.github.com/Noitidart/8728393

    它是一个完整的插件,您可以安装和破解。非常基本的模板。

    这是添加侧面板的部分:

        //START - EDIT BELOW HERE
        var browser = aDOMWindow.document.querySelector('#browser')
        if (browser) {
            var splitter = aDOMWindow.document.createElement('splitter');
            var propsToSet = {
                id: 'demo-sidebar-with-html_splitter',
                //class: 'sidebar-splitter' //im just copying what mozilla does for their social sidebar splitter //i left it out, but you can leave it in to see how you can style the splitter
            }
            for (var p in propsToSet) {
                splitter.setAttribute(p, propsToSet[p]);
            }
    
            var sidebar = aDOMWindow.document.createElement('vbox');
            var propsToSet = {
                id: 'demo-sidebar-with-html_sidebar',
                //persist: 'width' //mozilla uses persist width here, i dont know what it does and cant see it how makes a difference so i left it out
            }
            for (var p in propsToSet) {
                sidebar.setAttribute(p, propsToSet[p]);
            }
    
            var sidebarBrowser = aDOMWindow.document.createElement('browser');
            var propsToSet = {
                id: 'demo-sidebar-with-html_browser',
                type: 'content',
                context: 'contentAreaContextMenu',
                disableglobalhistory: 'true',
                tooltip: 'aHTMLTooltip',
                clickthrough: 'never',
                autoscrollpopup: 'autoscroller',
                flex: '1', //do not remove this
                style: 'min-width: 14em; width: 18em; max-width: 36em;', //you should change these widths to how you want
                src: 'data:text/html,%3Chtml%3E%0A%3Cbody%3E%0A%3Ciframe%20width%3D%22520%22%20height%3D%22390%22%20src%3D%22http%3A%2F%2Fweb2.0calc.com%2Fwidgets%2Fhorizontal%2F%22%20scrolling%3D%22no%22%20style%3D%22border%3A%201px%20solid%20silver%3B%20%22%3E%20%3C%2Fiframe%3E%0A%3Cbr%20%2F%3E%0A%3Ca%20href%3D%22http%3A%2F%2Fweb2.0calc.com%2F%22%3EWeb%202.0%20scientific%20calculator%3C%2Fa%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E' //or just set this to some url like http://www.bing.com/
            }
            for (var p in propsToSet) {
                sidebarBrowser.setAttribute(p, propsToSet[p]);
            }
    
            browser.appendChild(splitter);
    
            sidebar.appendChild(sidebarBrowser);
            browser.appendChild(sidebar);
        }
        //END - EDIT BELOW HERE
    

    可以将其复制粘贴到暂存器运行,但要从暂存器运行,您必须先将 var browser = aDomWindow.document.querySelector('#browser') 替换为 var browser = Services.wm.getMostRecentWindow('navigator:browser').document.querySelector('#browser')

    【讨论】:

    • 虽然这看起来很有帮助,但您应该在此处发布部分示例或“入门”示例,假设有太多代码无法发布整个内容。 Stack Overflow 的答案应该是自包含的,这样如果您的链接中断,该答案仍然可以帮助所有未来有类似问题的附加组件开发人员。希望这会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2013-11-24
    • 2010-12-31
    • 2013-11-14
    相关资源
    最近更新 更多