Расширение Firefox, боковая панель, связанная с окном

Я пишу расширение для Firefox, и мне нужно, чтобы пользовательский интерфейс этого расширения находился на боковой панели, я следовал некоторым руководствам Mozilla, но боковые панели не связаны только с одним окном.

Мне нужна боковая панель, такая как пользовательский интерфейс, которая будет сохранять данные навигации из того же окна, и мне нужно, чтобы она была связана только с этим окном, что-то вроде firebug.

Что я сделал до сих пор, так это просто создал меню и элемент, мне нужно, чтобы щелчок по этому элементу переключал мою боковую панель.

Я взглянул на исходники firebug, я не нашел в его XUL наложения боковой панели, скрипты для меня сложные, поэтому я не знал, как они могут добавить свой пользовательский интерфейс в окно.

Любые идеи или источники, которые я могу прочитать об этом?


person elaich    schedule 16.01.2014    source источник


Ответы (1)


Говоря о боковой панели, нужно быть осторожным с терминологией. Специальный термин, который Mozilla использует в Firefox для обозначения «боковой панели" относится к блоку содержимого, который находится сбоку от пользовательского интерфейса. Боковая панель, если она открыта, является постоянной частью пользовательского интерфейса, которая отображается независимо от выбранной вкладки. Предусмотрен только один, который можно выбрать слева или справа. Он обычно используется для содержимого, которое не меняется от вкладки к вкладке (например, закладки или история).

Пользовательский интерфейс, который используется для инструментов разработчика Firefox (и размещение было адаптировано для использования FireBug), представляет собой подпанель на текущей вкладке. Он отображается только на той вкладке, на которой он был вызван. Он реализован в <iframe>. Его также можно открыть как отдельное окно.

Когда у вас есть известный рабочий пример, один из способов выяснить, как этот тип вещей реализован в DOM (все окно браузера является DOM), — это установить дополнение Инспектор DOM и используйте его для изучения содержимого DOM. Возможно, вам также понадобится дополнение Element Inspector. что является очень полезным дополнением к инспектору DOM (щелчок правой кнопкой мыши открывает инспектор DOM для выбранного элемента). Вы также можете найти полезным Stacked Inspector. .

Другой способ выяснить, как это делается, — посмотреть исходный код. Для devtools интерфейс фактически создается в функции SH_create внутри resource:///modules/devtools/framework/toolbox-hosts.js

При размещении в нижней части пользовательский интерфейс становится дочерним элементом <notificationbox>, который существует для каждой вкладки. Вы можете найти <notificationbox> для вкладки, используя метод gBrowser.getNotificationBox( browserForTab ). Вставлены элементы <splitter> и <iframe>. При размещении в других местах на вкладке браузера эти два элемента вставляются в места в DOM браузера либо как дочерние элементы <notificationbox>, либо как дочерние элементы <hbox>, имеющего class="browserSidebarContainer".

Например, следующие функции, в зависимости от параметра [location], создают панель слева, справа, сверху или снизу от текущей вкладки или как отдельное окно. По умолчанию панель состоит из <iframe>, отделенных от содержимого браузера <splitter>. Функция createInterfacePanel() является более общей и принимает любой элемент или объект DOM в качестве второго параметра, который вставляется в DOM в соответствующем месте на основе [местоположения] и отделяется формой содержимого. Ожидается, что такой объект будет либо фрагментом документа, либо < элемент href="https://developer.mozilla.org/en-US/docs/Web/API/Element" rel="nofollow noreferrer">.

/**
 *   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 с боковой панелью справа и слева одновременно время". Вероятно, вам гораздо лучше использовать код, содержащийся в этом ответе, а не код, найденный здесь.

person Makyen♦    schedule 29.09.2014
comment
Спасибо за это хорошее объяснение, я использовал надстройку Firefox SDK, она предлагает более приятный интерфейс для создания различных объектов Chrome. - person elaich; 30.09.2014