【问题标题】:Firefox Extension, Window related sidebarFirefox 扩展,Window 相关侧边栏
【发布时间】:2014-01-16 11:54:54
【问题描述】:

我正在为 Firefox 编写一个扩展程序,我需要将此扩展程序的 UI 放在侧边栏上,我遵循了一些 mozilla 教程,但侧边栏不仅仅与一个窗口相关。

我需要一个类似于 UI 的侧边栏,它将保存来自同一个窗口的导航数据,并且需要它与那个窗口相关,比如 firebug。

到目前为止,我所做的只是创建一个菜单和一个项目,我需要单击该项目来切换我的侧边栏。

我查看了 firebug 源码,在它的 XUL 中没有发现任何侧边栏覆盖,脚本对我来说很复杂,所以我不知道他们如何将他们的 UI 添加到窗口中。

我能读到关于此的任何想法或资料吗?

【问题讨论】:

    标签: firefox firefox-addon xul


    【解决方案1】:

    在谈论侧边栏时,需要注意术语。 Mozilla 在 Firefox 中使用的特定术语“Sidebar”指的是 UI 侧面的内容框。侧边栏(如果打开)是 UI 的固定部分,其显示与所选选项卡无关。仅提供一个可以选择在左侧或右侧的。它通常用于在标签之间不会发生变化的内容(例如书签或历史记录)。

    用于 Firefox 开发工具的 UI(并且放置位置已被 FireBug 使用)是当前选项卡中的一个子面板。它仅在调用它的选项卡中显示。它在<iframe> 中实现。它也可以作为单独的窗口打开。

    当你有一个已知的工作示例时,弄清楚这种类型的东西是如何在 DOM 中实现的一种方法(整个浏览器窗口都是一个 DOM)是安装 add-on DOM Inspector 并使用它来调查DOM 的内容是什么样的。您可能还想要 Element Inspector 附加组件,它是 DOM 检查器的一个非常有用的补充(shift-right-click 打开 DOM 检查器到单击的元素)。您可能还会发现 Stacked Inspector 很有帮助。

    了解它是如何完成的另一种方法是查看源代码。对于 devtools,接口实际上是在 resource:///modules/devtools/framework/toolbox-hosts.js 内的函数 SH_create 中创建的

    当放置在底部位置时,UI 被放置为每个选项卡存在的 <notificationbox> 的子级。您可以使用gBrowser.getNotificationBox( browserForTab ) 方法找到选项卡的<notificationbox>。插入的元素是<splitter><iframe>。当放置在浏览器选项卡内的其他位置时,这两个元素将作为<notificationbox> 的子级或作为具有class="browserSidebarContainer" 的子级<hbox> 的子级插入到浏览器DOM 中的位置。

    例如,以下函数将根据 [location] 参数在当前选项卡的左侧、右侧、顶部或底部创建一个面板,或作为单独的窗口创建一个面板。默认情况下,面板由<iframe> 组成,与浏览器内容之间用<splitter> 分隔。 createInterfacePanel() 函数更通用,将接受任何元素或 DOM 对象作为第二个参数,该参数基于 [location] 插入到 DOM 中的适当位置,并由内容分隔。此类对象应为Document Fragmentelement

    /**
     *   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;
    }
    

    更新:

    对于我对“Firefox SDK Add-on with a sidebar on both the right and left at the same time”的回答,此答案中的代码得到了显着增强。使用该答案中包含的代码而不是此处找到的代码可能会好得多。

    【讨论】:

    • 感谢您的精彩解释,我使用了 firefox 插件 sdk,它为创建不同的 chrome 对象提供了更好的界面。
    猜你喜欢
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多