ส่วนขยายของ Firefox แถบด้านข้างที่เกี่ยวข้องกับหน้าต่าง

ฉันกำลังเขียนส่วนขยายสำหรับ Firefox และฉันต้องการให้ UI ของส่วนขยายนี้อยู่บนแถบด้านข้าง ฉันทำตามบทช่วยสอนของ Mozilla บางรายการ แต่แถบด้านข้างไม่เกี่ยวข้องกับหน้าต่างเดียว

ฉันต้องการแถบด้านข้างเช่น UI ที่จะบันทึกข้อมูลการนำทางจากหน้าต่างเดียวกัน และต้องการให้เกี่ยวข้องกับเฉพาะหน้าต่างนั้น บางอย่างเช่น firebug

สิ่งที่ฉันทำจนถึงตอนนี้คือแค่สร้างเมนู และรายการหนึ่ง ฉันต้องการให้การคลิกที่รายการนี้จะสลับแถบด้านข้างของฉัน

ฉันได้ดูแหล่งที่มาของ firebug แล้ว ฉันไม่พบการซ้อนทับของแถบด้านข้างใน XUL สคริปต์นั้นซับซ้อนสำหรับฉัน ดังนั้นฉันจึงไม่รู้ว่าพวกเขาจะเพิ่ม UI ลงในหน้าต่างได้อย่างไร

แนวคิดหรือแหล่งข้อมูลใดที่ฉันสามารถอ่านเกี่ยวกับเรื่องนี้ได้


person elaich    schedule 16.01.2014    source แหล่งที่มา


คำตอบ (1)


เมื่อพูดถึงแถบด้านข้าง เราต้องระมัดระวังเกี่ยวกับคำศัพท์เฉพาะทาง คำเฉพาะที่ Mozilla ใช้ใน Firefox สำหรับ "แถบด้านข้าง" หมายถึงกล่องเนื้อหาที่อยู่ด้านข้างของ UI หากเปิดแถบด้านข้างไว้ จะเป็นส่วนที่คงที่ของ UI ซึ่งจะแสดงโดยไม่ขึ้นอยู่กับแท็บที่เลือก มีเพียงอันเดียวเท่านั้นที่สามารถเลือกให้อยู่ทางซ้ายหรือทางขวาได้ มักใช้กับเนื้อหาที่ไม่เปลี่ยนจากแท็บหนึ่งไปอีกแท็บหนึ่ง (เช่น บุ๊กมาร์ก หรือประวัติ)

UI ที่ใช้สำหรับ devtools ของ Firefox (และตำแหน่งถูกนำมาใช้เพื่อใช้งานโดย FireBug) เป็นแผงย่อยภายในแท็บปัจจุบัน จะแสดงเฉพาะภายในแท็บที่ถูกเรียกใช้เท่านั้น มีการใช้งานภายใน <iframe> นอกจากนี้ยังสามารถเปิดเป็นหน้าต่างแยกได้อีกด้วย

เมื่อคุณมีตัวอย่างการทำงานที่ทราบแล้ว วิธีหนึ่งในการพิจารณาว่าสิ่งประเภทนี้ถูกนำไปใช้อย่างไรใน DOM (หน้าต่างเบราว์เซอร์ทั้งหมดคือ DOM) คือการติดตั้ง ส่วนเสริม DOM Inspector และใช้เพื่อตรวจสอบลักษณะของเนื้อหาของ DOM คุณอาจต้องการโปรแกรมเสริม Element Inspector ซึ่งเป็นส่วนเสริมที่มีประโยชน์มากสำหรับ DOM Inspector (การกด shift-right คลิกจะเปิด DOM Inspector ไปยังองค์ประกอบที่คลิก) คุณอาจพบว่า Stacked Inspector มีประโยชน์เช่นกัน .

อีกวิธีหนึ่งในการค้นหาว่ากำลังดำเนินการอย่างไรคือการดูซอร์สโค้ด สำหรับ devtools อินเทอร์เฟซจะถูกสร้างขึ้นจริงในฟังก์ชัน SH_create ภายใน resource:///modules/devtools/framework/toolbox-hosts.js

เมื่อวางไว้ที่ตำแหน่งด้านล่าง UI จะถูกวางเป็นรายการย่อยของ <notificationbox> ซึ่งมีอยู่สำหรับแต่ละแท็บ คุณสามารถค้นหา <notificationbox> สำหรับแท็บได้โดยใช้วิธี gBrowser.getNotificationBox( browserForTab ) องค์ประกอบที่แทรกคือ <splitter> และ <iframe> เมื่อวางในตำแหน่งอื่นภายในแท็บเบราว์เซอร์ องค์ประกอบทั้งสองนี้จะถูกแทรกไว้ที่ตำแหน่งใน Browser DOM ไม่ว่าจะเป็นรายการย่อยของ <notificationbox> หรือรายการย่อยของรายการย่อย <hbox> ที่มี class="browserSidebarContainer"

ตามตัวอย่าง ฟังก์ชันต่อไปนี้จะสร้างแผงทางด้านซ้าย ขวา บน หรือล่างของแท็บปัจจุบัน หรือเป็นหน้าต่างแยกต่างหาก ทั้งนี้ขึ้นอยู่กับพารามิเตอร์ [location] ค่าเริ่มต้นคือแผงประกอบด้วย <iframe> ซึ่งแยกจากเนื้อหาเบราว์เซอร์ด้วย <splitter> ฟังก์ชัน createInterfacePanel() เป็นแบบทั่วไปมากกว่า และจะยอมรับองค์ประกอบหรือวัตถุ DOM ใดๆ เป็นพารามิเตอร์ตัวที่สอง ซึ่งจะถูกแทรกลงใน DOM ในตำแหน่งที่เหมาะสมตาม [location] และคั่นด้วยรูปแบบเนื้อหา ออบเจ็กต์ดังกล่าวคาดว่าจะเป็น Document Fragment หรือ องค์ประกอบ

/**
 *   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 พร้อมแถบด้านข้างทั้งด้านขวาและซ้ายในเวลาเดียวกัน เวลา". คุณน่าจะดีกว่าการใช้รหัสที่มีอยู่ในคำตอบนั้นมากกว่ารหัสที่พบที่นี่

person Makyen♦    schedule 29.09.2014
comment
ขอบคุณสำหรับคำอธิบายที่ดีนี้ ฉันใช้ sdk ส่วนเสริมของ Firefox แล้ว มันมีอินเทอร์เฟซที่ดีกว่าสำหรับการสร้างวัตถุ Chrome ต่างๆ - person elaich; 30.09.2014