{"size": 103419, "ext": "js", "max_stars_repo_path": "keefox@chris.tomlinson/chrome/content/panel.js", "max_stars_repo_name": "thioshp/my_firefox_extensions", "max_stars_repo_stars_event_min_datetime": null, "max_stars_repo_stars_event_max_datetime": null, "max_issues_repo_path": "keefox@chris.tomlinson/chrome/content/panel.js", "max_issues_repo_name": "thioshp/my_firefox_extensions", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "keefox@chris.tomlinson/chrome/content/panel.js", "max_forks_repo_name": "thioshp/my_firefox_extensions", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": null, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 49.3646778043, "max_line_length": 192, "alphanum_fraction": 0.5808893917}
/*
  KeeFox - Allows Firefox to communicate with KeePass (via the KeePassRPC KeePass-plugin)
  Copyright 2008-2015 Chris Tomlinson <keefox@christomlinson.name>
  
  This panel.js file contains functions and data related to the visible
  user interface panel.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
"use strict";

//Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://kfmod/kfDataModel.js");

keefox_win.panel = {

    _observerService : null,

    // The Firefox CustomisableUI widget that our panel is attached to
    _widget: null,

    isOpen: false,

    construct : function (currentWindow) {
        this._currentWindow = currentWindow;
        this.panelContainerId = "keefox-panelview";

        try
        {
            // Get or create the main KeeFox widget (it's shared across windows)
            Components.utils.import("resource:///modules/CustomizableUI.jsm", this);
            let wrapperGroup = this.CustomizableUI.getWidget('keefox-button');
            if (wrapperGroup == null || wrapperGroup.type == 'custom')
            {
                keefox_win.Logger.debug("Didn't find KeeFox widget");
                wrapperGroup = this.CustomizableUI.createWidget({
                    id: "keefox-button",
                    type:"view",
                    viewId:"keefox-panelview",
                    defaultArea: "nav-bar",
                    removable: true,
                    label: "KeeFox",
                    tooltiptext: "KeeFox",
                    onViewShowing: function (evt)
                    {
                        var targetDoc = evt.target.ownerDocument;

                        keefox_win.panel.attachViewOpeningObserver(targetDoc);

                        // Tidy up if any mouse-leave events were missed last time
                        let optionsMenuTrigger = keefox_win.panel._currentWindow.document.getElementById("optionsMenuTrigger");
                        if (optionsMenuTrigger)
                        {
                            let li = optionsMenuTrigger.parentNode;
                            li.removeChild(optionsMenuTrigger);
                            li.removeEventListener("mouseleave", keefox_win.panel.onMouseLeaveLogin, false);
                            li.addEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);
                        }

                        // Make sure our notifications are visible here instead of the persistent
                        // panel (in case user clicks on KeeFox menu button while persistent panel
                        // is still visible). We achieve that by closing the persistent panel and
                        // letting its onhiding code tidy up after itself
                        evt.target.ownerGlobal.keefox_win.persistentPanel.onMainPanelShowing(evt);

                        let searchFilter = targetDoc.getElementById('KeeFox-PanelSection-searchfilter');
                        let current = targetDoc.getElementById('KeeFox-PanelSection-searchfilter-current');

                        evt.target.ownerGlobal.keefox_win.SearchFilter.updateSearchFilterStart(searchFilter, current);
                    },
                    onViewHiding: function (evt)
                    {
                        try {
                            // Close subpanels
                            evt.target.ownerGlobal.keefox_win.panel.hideSubSections();
                        } catch (e) {
                            keefox_win.Logger.error("onViewHiding failure. e: " + e + ". Stack: " + e.stack);
                        }
                    },
                    onBeforeCreated: function (doc)
                    {
                        keefox_win.Logger.debug("onBeforeCreated KeeFox widget called");
                        let win = doc.ownerGlobal;
                        win.keefox_win.panel.buildPanel.call(win.keefox_win.panel, win);
                    }
                });

                keefox_win.Logger.info("Created KeeFox widget");
            }
            this._widget = wrapperGroup.forWindow(this._currentWindow);
                
            this._currentWindow.document.addEventListener("keefoxMainPanelOpened", function (event) { 
                let preferMatchedLogins = keefox_win.panelInvokedByMatchedLoginsShortcut;
                keefox_win.panelInvokedByMatchedLoginsShortcut = false;
                let toFocus = keefox_win.panel._setInitialPanelFocus(preferMatchedLogins);
                if (toFocus) toFocus.focus();
            }, false);

            // Set the initial status of the KeeFox panel
            this.setupButton_ready();

            keefox_win.Logger.debug("KeeFox widget instance found");
        }
        catch (e)
        {
            // We only create the panel in FF >= Australis so should be able to rely on it working... but just in case...
            keefox_win.Logger.error("Failed to create KeeFox widget because: " + e);
        }

        this._observerService = Components.classes["@mozilla.org/observer-service;1"]
                    .getService(Components.interfaces.nsIObserverService);
        
        currentWindow.messageManager.addMessageListener("keefox:matchedLoginsChanged", this.matchedLoginsChangedListener);

    },

    resetSearchInterface: function ()
    {
        // Clear search terms
        this._currentWindow.document.getElementById('KeeFox-PanelSection-searchbox').value = "";
        // Clear search results
        keefox_win.panel.onSearchComplete.
            bind(keefox_win.panel)([]);

    },

    // Listen for notifications that we've decided on a new list of new matched logins and forward them to other interested observers in the main chrome process
    matchedLoginsChangedListener: function (message)
    {
        keefox_win.Logger.debug("panel matchedLoginsChangedListener called");
        // Make sure we only process messages from the currently displayed tab
        if (message.target === window.gBrowser.selectedBrowser)
            keefox_win.panel.setLogins(message.data.logins, message.data.notifyUserOnSuccess);
    },
    
    _currentWindow: null,

    shutdown: function () { },

    buildPanel: function (win) {
        this._currentWindow = win;

        let panelview = win.document.createElement('panelview');
        panelview.id = 'keefox-panelview';
        panelview.setAttribute('tooltip', 'aHTMLTooltip');
        panelview.setAttribute('flex', '1');
        
        // wrapping the content avoids the scrollbar most of the time but when it is really needed, we get given double scrollbars
        //this.wrapPanelContents(panelview);
        this.populatePanel(panelview);

        // Inject our panel view into the multiView panel
        let multiview = win.document.getElementById('PanelUI-multiView');
        multiview.appendChild(panelview);
        try { keefox_win.Logger.debug("Injected KeeFox panel"); } catch (e) {}
    },

    wrapPanelContents: function (panel) {
        let panelContentsWrapper = this.createUIElement('div', [
            ['id','KeeFox-PanelSection-div-wrapper']
        ]);
        
        this.populatePanel(panelContentsWrapper);

        // Inject our content wrapper into the panelview
        panel.appendChild(panelContentsWrapper);
        try { keefox_win.Logger.debug("Wrapped KeeFox panel"); } catch (e) {}
    },
    
    populatePanel: function (panel) {
        let closure = this;

        // Each main "panel section" component is a div with a button to invoke the main action.
        
        let notificationsTab = this.createUIElement('vbox', [
            ['class', 'KeeFox-PanelSection enabled'],
            ['id','KeeFox-PanelSection-notifications-tab']
        ], true);

        let notificationsWindow = this.createUIElement('vbox', [
            ['class','KeeFox-PanelSection enabled'],
            ['id', 'KeeFox-PanelSection-notifications-window']
        ], true);

        let statusTextContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelSection disabled'],
            ['id','KeeFox-PanelSection-status-text']
        ]);

        let status = this.createPanelSection(
            'KeeFox-PanelSection-status',
            null,
            // This localisation key won't exist but that's OK because we'll always 
            // set it to something relevant before displaying the status panel section
            'do-a-thing' 
        );
        this.disableUIElementNode(status);
        
        // This close panel will be displayed only when a subpanel is being displayed
        let subPanelCloser = this.createPanelSection(
            'KeeFox-PanelSection-close',
            function () { closure.hideSubSections(); },
            'KeeFox_Back'
        );        
        
        let searchPanel = this.createUIElement('div', [
            ['class','KeeFox-PanelSection enabled'],
            ['id','KeeFox-PanelSection-search']
        ]);
        let searchBox = this.createUIElement('input', [
            ['class','KeeFox-Search'],
            ['id','KeeFox-PanelSection-searchbox'],
            ['type','text'],
            ['placeholder',keefox_org.locale.$STR('KeeFox_Search.label')],
            ['title',keefox_org.locale.$STR('KeeFox_Search.tip')]
        ]);
        searchBox.addEventListener('input',function(e){
            //TODO:1.6: rate limit searches?
            keefox_org.search.execute(e.target.value,
                closure.onSearchComplete.bind(closure),
                e.target.ownerDocument.getElementById('KeeFox-PanelSection-searchfilter').selectedOptions[0].value.split(','));
        }, false);

        searchBox.addEventListener("keydown", this.keyboardNavHandler, false);
        searchBox.addEventListener("keefoxEscape", function (event) {
            //console.log("keefoxEscape event detected! coming from this element:", e.target);
            if (event.target.value.length > 0)
                event.target.value = "";
            else
                keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
        }, false);

        let searchFields = keefox_win.SearchFilter.attachFilterToSearchBox(searchBox, this);
        searchPanel.appendChild(searchFields);

        let searchResultsContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelInlineSection KeeFox-SearchResults enabled'],
            ['id','KeeFox-PanelSubSection-SearchResults']
        ]);
        searchPanel.appendChild(searchResultsContainer);
        
        // The main action for the list of matched logins will display the list of matched logins, but this behaviour is
        // not required in most cases (since the discovery of any matched logins will automatically
        // result in the list being displayed in the main panel)
        let matchedLogins = this.createPanelSection(
            'KeeFox-PanelSection-matchedLogins',
            function () { closure.showSubSectionMatchedLogins(); },
            'KeeFox_Matched-Logins-Button'
        );
        this.disableUIElementNode(matchedLogins);
        let matchedLoginsListContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelInlineSection KeeFox-LoginList enabled'],
            ['id','KeeFox-PanelSubSection-MatchedLoginsList']
        ]);
        matchedLogins.insertBefore(matchedLoginsListContainer,matchedLogins.lastChild);
        let matchedLoginsListOverflowContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelSubSection KeeFox-LoginList overflow enabled'],
            ['id','KeeFox-PanelSubSection-MatchedLoginsList-Overflow']
        ]);
        matchedLogins.insertBefore(matchedLoginsListOverflowContainer,matchedLogins.lastChild);

        // similar deal to the matchedLogins section
        let allLogins = this.createPanelSection(
            'KeeFox-PanelSection-allLogins', 
            function () { closure.showSubSectionAllLogins(); },
            'KeeFox_Logins-Button'
        );

        // is it really an PanelInlineSection? what about when user changes setting from 0 to 1+ items to display? then it will ahve to start behaving differently...
        this.disableUIElementNode(allLogins);
        let allLoginsListContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelInlineSection KeeFox-LoginList enabled'],
            ['id','KeeFox-PanelSubSection-AllLoginsList']
        ]);
        allLogins.insertBefore(allLoginsListContainer,allLogins.lastChild);
        let allLoginsListOverflowContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelSubSection KeeFox-LoginList overflow enabled'],
            ['id','KeeFox-PanelSubSection-AllLoginsList-Overflow']
        ]);
        allLogins.insertBefore(allLoginsListOverflowContainer,allLogins.lastChild);
        
        let generatePassword = this.createPanelSection(
            'KeeFox-PanelSection-generatePassword', 
            function () { closure.showSubSectionGeneratePassword(); },
            'KeeFox_Menu-Button.copyNewPasswordToClipboard'
        );
        this.disableUIElementNode(generatePassword);
        let generatePasswordListContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelSubSection KeeFox-PasswordProfileList enabled'],
            ['id','KeeFox-PanelSubSection-PasswordProfileList']
        ]);
        generatePassword.appendChild(generatePasswordListContainer);
        
        let changeDatabase = this.createPanelSection(
            'KeeFox-PanelSection-changeDatabase', 
            function () { closure.showSubSectionChangeDatabase(); },
            'KeeFox_Menu-Button.changeDB'
        );
        this.disableUIElementNode(changeDatabase);
        let changeDatabaseListContainer = this.createUIElement('div', [
            ['class','KeeFox-PanelSubSection KeeFox-DatabaseList enabled'],
            ['id','KeeFox-PanelSubSection-DatabaseList']
        ]);
        changeDatabase.appendChild(changeDatabaseListContainer);
        
        
        let detectForms = this.createPanelSection(
            'KeeFox-PanelSection-detectForms', 
            function () {
                keefox_win.UI.fillCurrentDocument();
                closure.CustomizableUI.hidePanelForNode(
                    closure._currentWindow.document.getElementById('keefox-panelview'));
            },
            'KeeFox_Menu-Button.fillCurrentDocument'
        );
        this.disableUIElementNode(detectForms);
        
        let options = this.createPanelSection(
            'KeeFox-PanelSection-options', 
            function () { openDialog(
               "chrome://keefox/content/options.xul",
               "",
               "centerscreen,dialog=no,chrome,resizable,dependent,modal"
              ); },
            'KeeFox_Menu-Button.options'
        );
        let help = this.createPanelSection(
            'KeeFox-PanelSection-help', 
            function () {
                keefox_org.utils._openAndReuseOneTabPerURL('http://keefox.org/help'); 
                closure.CustomizableUI.hidePanelForNode(
                    closure._currentWindow.document.getElementById('keefox-panelview'));
            },
            'KeeFox_Help-Centre-Button'
        );

        panel.appendChild(notificationsTab);
        panel.appendChild(notificationsWindow);
        panel.appendChild(statusTextContainer);
        panel.appendChild(status);
        panel.appendChild(subPanelCloser);
        panel.appendChild(matchedLogins);
        panel.appendChild(searchPanel);
        panel.appendChild(allLogins);
        panel.appendChild(generatePassword);
        panel.appendChild(changeDatabase);
        panel.appendChild(detectForms);
        panel.appendChild(options);
        panel.appendChild(help);
    },
    
    createPanelSection: function (id, onCommand, stringKeyBase)
    {
        // A panel section is a div that contains a variety of user interface items.
        // Some interface items will be displayed only under certain circumstances and others may be displayed 
        //but disabled in some circumstances, although my initial preference is to hide all items that 
        //can't be interacted with to keep things tidy.
        
        // All sections contain at least one button to trigger the section's primary action, other contents will be defined elsewhere
        
        let elem = this.createUIElement('div', [
            ['class','KeeFox-PanelSection enabled'],
            ['id',id]
        ]);
        let button = this.createUIElement('input', [
            ['class','KeeFox-Action enabled'],
            ['id',id+'-main-action'],
            ['type','button'],
            ['value',keefox_org.locale.$STR(stringKeyBase + '.label')],
            ['title',keefox_org.locale.$STR(stringKeyBase + '.tip')]
        ]);
        
        if (onCommand)
        {
            button.addEventListener("mouseup", function (event) { 
                if (event.button == 0 || event.button == 1) onCommand(event); }, false);
            button.addEventListener("keydown", this.keyboardNavHandler, false);
            button.addEventListener("keefoxCommand", onCommand, false);
        }
        elem.appendChild(button);
        return elem;
    },
    
    // elements that are neither enabled nor disabled should inherit the state of their parent
    enableUIElement: function (id)
    {
    //return;
        let elem = this._currentWindow.document.getElementById(id);
        if (!elem)
            return;
        this.enableUIElementNode(elem);
    },
    disableUIElement: function (id)
    {
    //return;
        let elem = this._currentWindow.document.getElementById(id);
        if (!elem)
            return;
        this.disableUIElementNode(elem);
    },

    enableUIElementNode: function (elem)
    {
        elem.classList.add('enabled');
        elem.classList.remove('disabled');
    },
    disableUIElementNode: function (elem)
    {
        elem.classList.remove('enabled');
        elem.classList.add('disabled');
    },

    createUIElement: function (tag, attrs, xul)
    {
        let elem;
        if (xul)
            elem = this._currentWindow.document.createElement(tag);
        else
            elem = this._currentWindow.document.createElementNS('http://www.w3.org/1999/xhtml', tag);
        for (let i=0; i<attrs.length; i++)
            elem.setAttribute(attrs[i][0], attrs[i][1]);
        return elem;
    },
    
    hideSubSections: function ()
    {
        let elem = this._currentWindow.document.getElementById(this.panelContainerId);
        elem.classList.remove('subpanel-enabled');
        let toHide = elem.getElementsByClassName('enabled KeeFox-PanelSubSection');
        if (toHide.length > 0)
            toHide[0].parentNode.classList.remove('subpanel-enabled');
        while (toHide.length)
            this.disableUIElementNode(toHide[0]); // removes enabled class and thus deletes from the toHide list

        //TODO:1.6: Might make more sense to remember which subpanel was just closed and focus that button instead
        this._currentWindow.document.getElementById('KeeFox-PanelSection-searchbox').focus();
    },

    showSubSection: function (id)
    {
        let elem = this._currentWindow.document.getElementById(id);
        this.enableUIElementNode(elem);
        elem.parentNode.classList.add('subpanel-enabled');
        let panel = this._currentWindow.document.getElementById(this.panelContainerId);
        panel.classList.add('subpanel-enabled');
        
        // Try to focus on the first item in the newly displayed sub section
        let matches = elem.getElementsByTagName('li');
        if (!matches)
            return;
        let firstMatch = matches[0];
        if (firstMatch)
            firstMatch.focus();
    },
    
    showSubSectionMatchedLogins: function ()
    {
        this.showSubSection('KeeFox-PanelSubSection-MatchedLoginsList-Overflow');
    },
    
    showSubSectionAllLogins: function ()
    {
        this.showSubSection('KeeFox-PanelSubSection-AllLoginsList-Overflow');
    },
    
    showSubSectionGeneratePassword: function ()
    {
        let profileContainer = this._currentWindow.document.getElementById('KeeFox-PanelSubSection-PasswordProfileList');
        // Remove all of the existing profiles
        for (let i = profileContainer.childNodes.length; i > 0; i--) {
            profileContainer.removeChild(profileContainer.childNodes[0]);
        }

        profileContainer.setAttribute("loading", "true");

        keefox_win.mainUI.generatePassword();

        this.showSubSection('KeeFox-PanelSubSection-PasswordProfileList');
        keefox_org.getPasswordProfiles();
    },
    
    showSubSectionChangeDatabase: function ()
    {
        let dbcontainer = this._currentWindow.document.getElementById('KeeFox-PanelSubSection-DatabaseList');
        
        // Remove all of the existing databases
        for (let i = dbcontainer.childNodes.length; i > 0; i--) {
            dbcontainer.removeChild(dbcontainer.childNodes[0]);
        }

        dbcontainer.setAttribute("loading", "true");
        keefox_win.mainUI._currentWindow.keefox_org.getAllDatabaseFileNames();
        this.showSubSection('KeeFox-PanelSubSection-DatabaseList');
    },
    

    // remove matched logins from the menu
    removeLogins: function () {
        keefox_win.Logger.debug("Removing all matched logins");
        // Get the toolbaritem "container" that we added to our XUL markup
        var container = this._currentWindow.document.getElementById("KeeFox-PanelSubSection-MatchedLoginsList");
        if (container === undefined || container == null)
            return;

        // Remove all of the existing buttons
        for (let i = container.childNodes.length; i > 0; i--) {
            container.removeChild(container.childNodes[0]);
        }

        var containeroverflow = this._currentWindow.document.getElementById("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");
        if (containeroverflow === undefined || containeroverflow == null)
            return;

        // Remove all of the existing buttons
        for (let i = containeroverflow.childNodes.length; i > 0; i--) {
            containeroverflow.removeChild(containeroverflow.childNodes[0]);
        }

        this.disableUIElement("KeeFox-PanelSection-matchedLogins-main-action");

        // get the context menu popup
        var contextPopup = this._currentWindow.document.getElementById("keefox-command-context-showMenuMatchedLogins-popup");
        if (contextPopup === undefined || contextPopup == null)
            return;

        // Remove all of the existing buttons
        // the node list changes as we remove items so we always just get rid of the first one
        for (let i = contextPopup.childNodes.length; i > 0; i--) {
            contextPopup.removeChild(contextPopup.childNodes[0]);
        }

        this.setupButton_ready(null, this._currentWindow);
    },

    compareRelevanceScores: function (a, b) {
        return b.relevanceScore - a.relevanceScore;
    },

    // populate the "all logins" menu with every login in this database
    setAllLogins: function () {
        keefox_win.Logger.debug("setAllLogins start");

        // get and reset the popup menu for the complete list of logins
        var container = this.getEmptyContainerFor("KeeFox-PanelSubSection-AllLoginsList");
        
        if (keefox_org._keeFoxStorage.get("KeePassDatabaseOpen", false)) {
            // start with the current root group uniqueID
            try {
                if (!window.keefox_org._keeFoxExtension.prefs.getValue("listAllOpenDBs",false))
                {
                    var rootGroup = keefox_org.KeePassDatabases[keefox_org.ActiveKeePassDatabaseIndex].root;
                    if (rootGroup != null && rootGroup != undefined && rootGroup.uniqueID)
                    {
                        var dbFileName = keefox_org.KeePassDatabases[keefox_org.ActiveKeePassDatabaseIndex].fileName;
                        this.setOneLoginsMenu(container, rootGroup, dbFileName, true);
                    }
                } else
                {
                    for (let i=0; i<keefox_org.KeePassDatabases.length; i++)
                    {
                        var rootGroup = keefox_org.KeePassDatabases[i].root;
                        if (rootGroup != null && rootGroup != undefined && rootGroup.uniqueID)
                        {
                            var dbFileName = keefox_org.KeePassDatabases[i].fileName;
                            
                            if (keefox_org.KeePassDatabases.length > 1)
                            {
                                // We have more than one database opened so the list of databases becomes our top-level view
                                // we still need to respect the list of max entries to be listed at the top level

                                // Switch to the overflow container if we reach the limit of the main panel container
                                // By default, we won't show any items in the main panel
                                if (container.id != "KeeFox-PanelSubSection-AllLoginsList-Overflow-Container" 
                                    && i >= keefox_org._keeFoxExtension.prefs.getValue("maxAllLoginsInMainPanel",0))
                                    container = this.getEmptyContainerFor("KeeFox-PanelSubSection-AllLoginsList-Overflow");

                                var dbName = keefox_org.KeePassDatabases[i].name;

                                let groupItem = this.createGroupItem(rootGroup,dbFileName, null, dbName + ' / ' + rootGroup.title);
                                container.appendChild(groupItem);

                            } else
                            {
                                this.setOneLoginsMenu(container, rootGroup, dbFileName, true);
                            }
                        }
                    }
                }
            } catch (e) {
                keefox_win.Logger.error("setAllLogins exception: " + e);
                return;
            }
            //loginButton.setAttribute("disabled", "false");
        } else {
            //loginButton.setAttribute("disabled", "true");
        }
        keefox_win.Logger.debug("setAllLogins end");
        return;
    },

    // add all the logins and subgroups for one KeePass group
    setOneLoginsMenu: function(container, group, dbFileName, isTopLevelContainer)
    {
        //keefox_win.Logger.debug("setOneLoginsMenu called for [" + container.id + "] with uniqueRef: " + group.uniqueID);

        // Remove all of the existing buttons
        for (let i = container.childNodes.length; i > 0; i--) {
            container.removeChild(container.childNodes[0]);
        }

        var foundGroups = group.childGroups;
        var foundLogins = group.childLightEntries;

        if ((foundGroups == null || foundGroups.length == 0) && (foundLogins == null || foundLogins.length == 0)) {
            let noItemsButton = null;
            noItemsButton = this.createUIElement('li', [
                ['class',''],
                ['data-fileName',dbFileName],
             //   ['data-uuid',rootGroup.uniqueID],
            //    ['id', 'KeeFox_Group-' + rootGroup.uniqueID],
                ['title',keefox_org.locale.$STR("loginsButtonEmpty.tip")],
                ['tabindex','-1']
            ]);
            noItemsButton.textContent = keefox_org.locale.$STR("loginsButtonEmpty.label");

            // Unless we are allowed to display one or more logins in the main panel, we need to switch to the overflow panel now
            if (isTopLevelContainer && keefox_org._keeFoxExtension.prefs.getValue("maxAllLoginsInMainPanel",0) == 0)
                container = this.getEmptyContainerFor("KeeFox-PanelSubSection-AllLoginsList-Overflow");
            container.appendChild(noItemsButton);
            return;
        }

        for (let i = 0; i < foundGroups.length; i++) {
            let group = foundGroups[i];
            
            if ((group.childGroups != null && group.childGroups.length > 0) 
                || (group.childLightEntries != null && group.childLightEntries.length > 0))
            {
                let groupItem = this.createGroupItem(group,dbFileName);
                if (isTopLevelContainer 
                    && container.id != "KeeFox-PanelSubSection-AllLoginsList-Overflow-Container" 
                    && i >= keefox_org._keeFoxExtension.prefs.getValue("maxAllLoginsInMainPanel",0))
                    container = this.getEmptyContainerFor("KeeFox-PanelSubSection-AllLoginsList-Overflow");
                container.appendChild(groupItem);
            }
        }

        for (let i = 0; i < foundLogins.length; i++) {
            var login = foundLogins[i];
            var usernameValue = "";
            var usernameName = "";
            var usernameDisplayValue = "[" + keefox_org.locale.$STR("noUsername.partial-tip") + "]";
            usernameValue = login.usernameValue;
            if (usernameValue != undefined && usernameValue != null && usernameValue != "")
                usernameDisplayValue = usernameValue;
            usernameName = login.usernameName;

            var loginItem = this.createUIElement('li', [
                ['class','login-item'],
                ['data-fileName',dbFileName],
                ['data-usernameName',usernameName],
                ['data-usernameValue',usernameValue],
                ['data-url',login.uRLs[0]],
                ['data-uuid',login.uniqueID],
                ['style','background-image:url(data:image/png;base64,' + login.iconImageData + ')'],
            //    ['id', 'KeeFox_Group-' + rootGroup.uniqueID],
                ['title',keefox_org.locale.$STRF(
                "loginsButtonLogin.tip", [login.uRLs[0], usernameDisplayValue])],
                ['tabindex','-1']
            ]);
            if (keefox_org._keeFoxExtension.prefs.getValue("alwaysDisplayUsernameWhenTitleIsShown",false))
                loginItem.textContent = keefox_org.locale.$STRF("matchedLogin.label", [usernameDisplayValue, login.title]);
            else
                loginItem.textContent = login.title;
            
            loginItem.addEventListener("keydown", this.keyboardNavHandler, false);
            loginItem.addEventListener("mouseup", function (event) { 
                // Make sure no parent groups override the actions of this handler
                event.stopPropagation();

                if (event.button == 0 || event.button == 1)
                {
                    this.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': event.button, 'ctrlKey': event.ctrlKey }}));
                } 
                if (event.button == 2)
                {
                    keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                    keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,event,'KeeFox-login-context');
                }
            }, false);
            loginItem.addEventListener("keefoxCommand", function (event) { 
                keefox_win.loadAndAutoSubmit(event.detail.button,
                                                     event.detail.ctrlKey,
                                                     this.getAttribute('data-url'),
                                                     this.getAttribute('data-uuid'), 
                                                     this.getAttribute('data-fileName')
                                                    );
                keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                keefox_win.panel.hideSubSections();
            }, false);
            loginItem.addEventListener("keefoxContext", function (event) {
                keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,
                    { target: event.detail.target, layerX: event.detail.layerX, layerY: event.detail.layerY },
                    'KeeFox-login-context');
            }, false);
            loginItem.addEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);
            
            // If the combined total of all groups and the current login index exceeds 
            // our allowed number of items in the main panel, we must switch to the overflow container
            if (isTopLevelContainer 
                && container.id != "KeeFox-PanelSubSection-AllLoginsList-Overflow-Container" 
                && (i + foundGroups.length) >= keefox_org._keeFoxExtension.prefs.getValue("maxAllLoginsInMainPanel",0))
                container = this.getEmptyContainerFor("KeeFox-PanelSubSection-AllLoginsList-Overflow");
            container.appendChild(loginItem);
        }
    },

    addLoginContextActions: function (document, uuid, fileName)
    {
        let context = document.getElementById('KeeFox-login-context');
        context = keefox_win.panel.addContextMenuItem(context,
            keefox_org.locale.$STR("KeeFox_Logins-Context-Edit-Login.label"),
            "chrome://keefox/skin/pencil.png",
            "KeeFox-login-context-edit",
            function (event) {
            keefox_org.launchLoginEditor(uuid, fileName);
            keefox_win.panel.CustomizableUI.hidePanelForNode(
                keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
        });
        let loadingMessage = document.createElement('menuitem');
        loadingMessage.setAttribute("label", keefox_org.locale.$STR("loading") + "...");
        loadingMessage.id = "KeeFox-login-context-loading";
        loadingMessage.setAttribute("data-uuid", uuid);
        loadingMessage.setAttribute("data-fileName", fileName);
        context.appendChild(loadingMessage);
        keefox_org.findLogins(null, null, null, uuid, fileName, null, null, keefox_win.panel.setLoginActions);
                    
        context.addEventListener("popuphidden", keefox_win.panel.removeLoginContextActions);
    },

    removeLoginContextActions: function (event) {
        if (event.target.id != "KeeFox-login-context")
            return;

        let context = document.getElementById('KeeFox-login-context');
        context.removeEventListener("popuphidden", keefox_win.panel.removeLoginContextActions);

        let edit = document.getElementById('KeeFox-login-context-edit');
        let loading = document.getElementById('KeeFox-login-context-loading');
        let copyUser = document.getElementById('KeeFox-login-context-copyuser');
        let copyPass = document.getElementById('KeeFox-login-context-copypass');
        let copyOther = document.getElementById('KeeFox-login-context-copyother');

        if (edit) context.removeChild(edit);
        if (loading) context.removeChild(loading);
        if (copyUser) context.removeChild(copyUser);
        if (copyPass) context.removeChild(copyPass);
        if (copyOther) context.removeChild(copyOther);
    },

    addGroupContextActions: function (document, uuid, fileName)
    {
        let context = document.getElementById('KeeFox-group-context');
        context = keefox_win.panel.addContextMenuItem(context,
            keefox_org.locale.$STR("KeeFox_Logins-Context-Edit-Group.label"),
            "chrome://keefox/skin/pencil.png",
            "KeeFox-group-context-edit",
            function (event) {
                keefox_org.launchGroupEditor(uuid, fileName);
                keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
            });
        context.addEventListener("popuphidden", keefox_win.panel.removeGroupContextActions);
    },

    removeGroupContextActions: function (event) {
        if (event.target.id != "KeeFox-group-context")
            return;

        let context = document.getElementById('KeeFox-group-context');
        context.removeEventListener("popuphidden", keefox_win.panel.removeGroupContextActions);

        let edit = document.getElementById('KeeFox-group-context-edit');
        if (edit) context.removeChild(edit);
    },

    addContextMenuItem: function (context, label, image, id, onCommand )
    {
        let cmi = document.createElement('menuitem');
        cmi.setAttribute("label", label);
        cmi.setAttribute('image', image);
        cmi.classList.add("menuitem-iconic");
        cmi.id = id;
        cmi.addEventListener("command", onCommand);
        context.appendChild(cmi);
        return context;
    },

    createGroupItem: function (group, dbFileName, extraCSSClasses, displayName)
    {
        let groupItem = this.createUIElement('li', [
            ['class','group-item'],
            ['data-fileName',dbFileName],
            ['data-uuid',group.uniqueID],
            ['style','background-image:url(data:image/png;base64,' + group.iconImageData + ')'],
            //   ['id', 'KeeFox_Group-' + rootGroup.uniqueID],
            ['title',keefox_org.locale.$STR("loginsButtonGroup.tip")],
            ['tabindex','-1']
        ]);
        groupItem.textContent = displayName || group.title;
        
        groupItem.addEventListener("keydown", this.keyboardNavHandler, false);
        groupItem.addEventListener("mouseup", function (event) { 
            // Make sure no parent groups override the actions of this handler
            event.stopPropagation();

            if (event.button == 0 || event.button == 1)
            {
                this.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': event.button, 'ctrlKey': event.ctrlKey }}));
            } 
            if (event.button == 2)
            {
                keefox_win.panel.addGroupContextActions(document,
                    this.getAttribute('data-uuid'), 
                    this.getAttribute('data-fileName'));
                keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,event,'KeeFox-group-context');
            }
        }, false);
        groupItem.addEventListener("keefoxContext", function (event) {
            keefox_win.panel.addGroupContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
            keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,
                { target: event.detail.target, layerX: event.detail.layerX, layerY: event.detail.layerY },
                'KeeFox-group-context');
        }, false);

        groupItem.addEventListener("keefoxCommand", this.navigateGroupHierachy, false);
                    
        let groupContainer = this.createUIElement('ul', [
            ['class',' ' + extraCSSClasses],
            ['id', 'KeeFox_Group-' + group.uniqueID]
        ]);
        this.setOneLoginsMenu(groupContainer,group,dbFileName);
        groupItem.appendChild(groupContainer);
        return groupItem;
    },

    navigateGroupHierachy: function (event) { 
        // Find the currently displayed subgroup (if any)
        let currentGroup = document.getElementById("KeeFox-PanelSection-allLogins")
                            .querySelector(".active-group");
                
        // Mark it as no longer active
        if (currentGroup != null)
            currentGroup.classList.remove("active-group");
                
        // If user clicked on the active group or any of its parents, make sure that its direct parent (if any) is activated
        if (currentGroup == event.target)
        {
            // ignore the containing ul node, but note that we may end up selecting some higher part of the panel rather than a group
            let parentGroup = currentGroup.parentNode.parentNode;
            if (parentGroup != null && parentGroup.classList.contains("group-item"))
            {
                parentGroup.classList.remove("active-group-parent");
                parentGroup.classList.add("active-group");
            }
            currentGroup.focus();
        } else
        {
            let parent;

            // remove active marker from all parents. If we find the target node in the parent list, we'll set its parent as active (if it has one) and finish
            if (currentGroup)
            {
                parent = currentGroup.parentNode;
                while (parent.nodeName == "ul" || parent.nodeName == "li")
                {
                    if (parent.nodeName == "li")
                    {
                        parent.classList.remove("active-group-parent");
                        if (parent == event.target)
                        {
                            if (parent.parentNode && parent.parentNode.parentNode && parent.parentNode.parentNode.nodeName == "li")
                            {
                                parent.parentNode.parentNode.classList.remove("active-group-parent");
                                parent.parentNode.parentNode.classList.add("active-group");
                                parent.parentNode.parentNode.focus();
                                return;
                            }
                        }
                    }
                    parent = parent.parentNode;
                }
            }

            // add active marker to all parents of new node
            parent = event.target.parentNode;
            while (parent.nodeName == "ul" || parent.nodeName == "li")
            {
                if (parent.nodeName == "li")
                    parent.classList.add("active-group-parent");
                parent = parent.parentNode;
            }

            // Set our new active group
            event.target.classList.add("active-group");

            // Focus keyboard on first child
            let fc = event.target.firstElementChild;
            if (fc) fc = fc.firstElementChild;
            if (fc) fc.focus();
                
        }
    },

    setLoginActions: function (resultWrapper)
    {
        let isError = false;

        try
        {
            if ("result" in resultWrapper && resultWrapper.result !== false && resultWrapper.result != null)
            {
                let foundLogin = resultWrapper.result[0]; 
                    
                var kfl = keeFoxLoginInfo();
                kfl.initFromEntry(foundLogin);

                let context = document.getElementById('KeeFox-login-context');
                let loadingMessage = document.getElementById('KeeFox-login-context-loading');

                if (kfl.uniqueID == loadingMessage.getAttribute('data-uuid')
                    && kfl.database.fileName == loadingMessage.getAttribute('data-fileName'))
                {
                    // We got an answer for the correct login

                    // later we'll ignore the one marked as username
                    let otherFieldCount = (kfl.otherFields != null && kfl.otherFields.length > 0) ? kfl.otherFields.length : 0;
                    let usernameField = (otherFieldCount > 0) ? kfl.otherFields[kfl.usernameIndex] : null;

                    // later we'll ignore the first password in the list
                    let passwordFieldCount = (kfl.passwords != null && kfl.passwords.length > 0) ? kfl.passwords.length : 0;
                    let passwordField = (passwordFieldCount > 0) ? kfl.passwords[0] : null;

                    if (usernameField != null)
                    {
                        context = keefox_win.panel.addContextMenuItem(context,
                           keefox_org.locale.$STR("copy-username.label"),
                           "chrome://keefox/skin/copy.png",
                            "KeeFox-login-context-copyuser",
                            function (event) {
                                keefox_org.utils.copyStringToClipboard(usernameField.value);
                                keefox_win.panel.CustomizableUI.hidePanelForNode(keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                                keefox_org.metricsManager.adjustAggregate("copyUsername", 1);
                            });
                    }
                    
                    if (passwordField != null) {
                        context = keefox_win.panel.addContextMenuItem(context,
                            keefox_org.locale.$STR("copy-password.label"),
                            "chrome://keefox/skin/copy.png",
                            "KeeFox-login-context-copypass",
                            function (event) {
                                keefox_org.utils.copyStringToClipboard(passwordField.value);
                                keefox_win.panel.CustomizableUI.hidePanelForNode(keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                                keefox_org.metricsManager.adjustAggregate("copyPassword", 1);
                            });
                    }
                    if (otherFieldCount > 1 || passwordFieldCount > 1) {
                        let copyOther = document.createElement('menu');
                        copyOther.setAttribute("label", keefox_org.locale.$STR("copy-other.label"));
                        copyOther.id = "KeeFox-login-context-copyother";
                        let copyOtherPopup = document.createElement('menupopup');
                        copyOther.appendChild(copyOtherPopup);
                        let validFieldFound = false;

                        if (otherFieldCount > 1) {
                            kfl.otherFields.forEach(function (o, i) {
                                if (i != kfl.usernameIndex && o.type != "checkbox") {
                                    validFieldFound = true;
                                    copyOtherPopup = keefox_win.panel.addContextMenuItem(copyOtherPopup,
                                        o.name + " (" + o.fieldId + ")",
                                        "chrome://keefox/skin/copy.png",
                                        "",
                                        function (event) {
                                            keefox_org.utils.copyStringToClipboard(o.value);
                                            keefox_win.panel.CustomizableUI.hidePanelForNode(keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                                            keefox_org.metricsManager.adjustAggregate("copyOther", 1);
                                        });
                                }
                            });
                        }
                        if (passwordFieldCount > 1) {
                            kfl.passwords.forEach(function (p, i) {
                                if (i != 0 && p.type != "checkbox") {
                                    validFieldFound = true;
                                    copyOtherPopup = keefox_win.panel.addContextMenuItem(copyOtherPopup,
                                    p.name + " (" + p.fieldId + ")",
                                    "chrome://keefox/skin/copy.png",
                                    "",
                                    function (event) {
                                        keefox_org.utils.copyStringToClipboard(p.value);
                                        keefox_win.panel.CustomizableUI.hidePanelForNode(keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                                        keefox_org.metricsManager.adjustAggregate("copyOther", 1);
                                    });
                                }
                            });
                        }
                        if (validFieldFound)
                            context.appendChild(copyOther);
                    }

                    context.removeChild(loadingMessage);
                } else
                {
                    isError = true;
                }
            } else
            {
                isError = true;
            }
        } catch (e) {
            isError = true;
        }
        return;
    },

    getContainerFor: function (id)
    {
        let panelSection = this._currentWindow.document.getElementById(id);
        if (panelSection === undefined || panelSection == null)
            return null;
        
        if (panelSection.childNodes.length > 0)
            return panelSection.childNodes[0];
        
        // Create the ul menu top level container        
        let groupContainer = this.createUIElement('ul', [
            ['class',''],
            ['id', id + '-Container']]);
        panelSection.appendChild(groupContainer);
        return groupContainer;
    },

    getEmptyContainerFor: function (id)
    {
        let panelSection = this._currentWindow.document.getElementById(id);
        if (panelSection === undefined || panelSection == null)
            return null;
        
        // Remove all of the existing items by removing the top-level list
        // if it has been created earlier
        if (panelSection.childNodes.length > 0)
            panelSection.removeChild(panelSection.childNodes[0]);
        
        // Create the ul menu top level container        
        let groupContainer = this.createUIElement('ul', [
            ['class',''],
            ['id', id + '-Container']
                                                  ]);
        panelSection.appendChild(groupContainer);
        return groupContainer;
    },

    // Calling this function with null or empty logins array will clear all existing logins (e.g. from iframes)
    setLogins: function (logins, notifyUserOnSuccess)
    {
        keefox_win.Logger.debug("panel setLogins started");
        
        // Get the container that we want to add our matched logins to.
        var container = this.getContainerFor("KeeFox-PanelSubSection-MatchedLoginsList");
        if (container === undefined || container == null)
            return;
        var overflowPanelContainer = this.getContainerFor("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");

        // Force the panel to close before modifying the contents
        keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
        keefox_win.panel.hideSubSections();
        
        if (logins == null || logins.length == 0) {
            // Disable all matched logins UI elements
            this.disableUIElement("KeeFox-PanelSection-matchedLogins");
            this.disableUIElement("KeeFox-PanelSubSection-MatchedLoginsList");
            this.disableUIElement("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");
            this.disableUIElement("KeeFox-PanelSection-matchedLogins-main-action");
            this.removeLogins();
            this.setupButton_ready(null, this._currentWindow);
            return;
        }

        logins.sort(this.compareRelevanceScores);

        keefox_win.Logger.debug("setting " + logins.length + " matched logins");

        let loginsHaveBeenChanged = this.checkAllMatchedLoginsForChanges(logins, container, overflowPanelContainer);

        if (!loginsHaveBeenChanged)
        {
            keefox_win.Logger.debug("setLogins found no changes");
            return;
        }

        // Disable all matched logins UI elements, perhaps just for a jiffy while 
        // we work out which ones need to be revealed again
        this.disableUIElement("KeeFox-PanelSection-matchedLogins");
        this.disableUIElement("KeeFox-PanelSubSection-MatchedLoginsList");
        this.disableUIElement("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");
        this.disableUIElement("KeeFox-PanelSection-matchedLogins-main-action");

        this.removeLogins();

        // Get the container again (it has been deleted by removeLogins()
        // so this creates and returns it)
        container = this.getContainerFor("KeeFox-PanelSubSection-MatchedLoginsList");

        this.setLoginsAllMatches(logins, container);        

        // Update the UI state to reflect the number of matches found and where they are displayed

        overflowPanelContainer = this.getContainerFor("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");

        if (container.childElementCount > 0 || overflowPanelContainer.childElementCount > 0)
            this.enableUIElement("KeeFox-PanelSection-matchedLogins");
        if (container.childElementCount > 0)
            this.enableUIElement("KeeFox-PanelSubSection-MatchedLoginsList");
        if (overflowPanelContainer.childElementCount > 0)
        {
            this.enableUIElement("KeeFox-PanelSection-matchedLogins-main-action");
        }
        
        // Set icon overlay on main panel widget button icon to say how many matches there were
        this.setWidgetNotificationForMatchedLogins(container.childElementCount + overflowPanelContainer.childElementCount);

        // notify user if necessary
        if (notifyUserOnSuccess 
            && keefox_org._keeFoxExtension.prefs.getValue("notifyWhenLateDiscovery",true))
        {
            keefox_win.UI.growl(
                keefox_org.locale.$STR("matched-logins-found"), 
                keefox_org.locale.$STR("view-them-in-the-main-keefox-panel"), 
                true);
        }

        keefox_win.Logger.debug(logins.length + " matched panel logins set!");
    },

    // also used by context.js
    checkAllMatchedLoginsForChanges: function (logins, container, overflowPanelContainer) {
        keefox_win.Logger.debug("checkAllMatchedLoginsForChanges started");
        
        // If the logins already present in the main panel and subview containers
        // are identical to the current list, we leave them be.
        // We assume the number of items in the main menu vs overflow container
        // are the same since when we added the current set of logins (user is
        // unlikely to be able to change this during a form search operation)
        var loginsListUnchanged = logins.length ===
            container.childElementCount + overflowPanelContainer.childElementCount;

        if (loginsListUnchanged)
            loginsListUnchanged = this.areLoginsInContainerUnchanged(logins, container, 0);

        // If we've not already found a mismatch, look at the overflow container too
        if (loginsListUnchanged && overflowPanelContainer != null && overflowPanelContainer.childElementCount > 0)
            loginsListUnchanged = this.areLoginsInContainerUnchanged(logins, overflowPanelContainer, container.children.length);

        return !loginsListUnchanged;
    },

    // also used by context.js
    areLoginsInContainerUnchanged: function (logins, container, searchOffset)
    {        
        for (let j = 0, n = container.children.length; j < n; j++) {
            var child = container.children[j];
            let login = logins[j + searchOffset];
            if (typeof (login) === 'undefined')
                return false;
            let uuid = child.hasAttribute('data-uuid') ? child.getAttribute('data-uuid') : null;
            let formIndex = child.hasAttribute('data-formIndex') ? child.getAttribute('data-formIndex') : null;
            let loginIndex = child.hasAttribute('data-loginIndex') ? child.getAttribute('data-loginIndex') : null;
            let frameKey = child.hasAttribute('data-frameKey') ? child.getAttribute('data-frameKey') : null;
            if (uuid != login.uniqueID || formIndex != login.formIndex
                || loginIndex != login.loginIndex || frameKey != login.frameKey)
                return false;
        }
        return true;
    },

    setLoginsAllMatches: function (logins, container) {
        keefox_win.Logger.debug("setLoginsAllMatches started");
        
        // Sometimes we need to refer to the original container even
        // if we have started adding extra logins to the overflow container
        let mainPanelContainer = container;

        // This allows us to track when we've switched into the overflow mode
        let overflowPanelContainer = null;

        // add every matched login to the container(s)
        for (let i = 0; i < logins.length; i++) {
            var login = logins[i];
            var usernameValue = "";
            var usernameDisplayValue = "[" + keefox_org.locale.$STR("noUsername.partial-tip") + "]";
            var usernameName = "";
            var usernameId = "";
            var displayGroupPath = login.database.name + '/' + login.parentGroup.path;

            if (login.usernameIndex != null && login.usernameIndex != undefined && login.usernameIndex >= 0
                && login.otherFields != null && login.otherFields.length > 0) {
                var field = login.otherFields[login.usernameIndex];

                usernameValue = field.value;
                if (usernameValue != undefined && usernameValue != null && usernameValue != "")
                    usernameDisplayValue = usernameValue;
                usernameName = field.name;
                usernameId = field.fieldId;
            }

            var loginItem = this.createUIElement('li', [
                ['class',''],
                ['style','background-image:url(data:image/png;base64,' + login.iconImageData + ')'],
                ['data-fileName',login.database.fileName],
                ['data-frameKey', login.frameKey],
                ['data-formIndex', login.formIndex],
                ['data-loginIndex', login.loginIndex],
                ['data-uuid',login.uniqueID],
                ['title',keefox_org.locale.$STRF("matchedLogin.tip", [login.title, displayGroupPath, usernameDisplayValue])],
                ['tabindex','-1']
            ]);
            loginItem.textContent = keefox_org.locale.$STRF("matchedLogin.label", [usernameDisplayValue, login.title]);
            loginItem.addEventListener("keydown", this.keyboardNavHandler, false);
            loginItem.addEventListener("mouseup", function (event) {
                event.stopPropagation();
                if (event.button == 0 || event.button == 1)
                    this.dispatchEvent(new Event("keefoxCommand"));
                else if (event.button == 2) {
                    keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                    keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document, event, 'KeeFox-login-context');
                }
            }, false);
            loginItem.addEventListener("keefoxCommand", function (event) { 
                keefox_win.fillAndSubmit(false,
                    this.hasAttribute('data-frameKey') ? this.getAttribute('data-frameKey') : null,
                    this.hasAttribute('data-formIndex') ? this.getAttribute('data-formIndex') : null,
                    this.hasAttribute('data-loginIndex') ? this.getAttribute('data-loginIndex') : null
                );
                keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                keefox_win.panel.hideSubSections();
            }, false);
            loginItem.addEventListener("keefoxContext", function (event) {
                keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,
                    { target: event.detail.target, layerX: event.detail.layerX, layerY: event.detail.layerY },
                    'KeeFox-login-context');
            }, false);
            loginItem.addEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);

            // If we've exceeded our allowed number of items in the main panel, we must switch to the overflow container
            if (mainPanelContainer.childElementCount >= keefox_org._keeFoxExtension.prefs.getValue("maxMatchedLoginsInMainPanel",5))
            {
                container = this.getContainerFor("KeeFox-PanelSubSection-MatchedLoginsList-Overflow");
                overflowPanelContainer = container;
            }
            container.appendChild(loginItem);
        }
        keefox_win.Logger.debug("setLoginsAllMatches ended");
    },

    onSearchComplete: function (logins)
    {
        logins = logins.sort(function(a,b) {
            if (a.relevanceScore > b.relevanceScore)
              return -1;
            if (a.relevanceScore < b.relevanceScore)
              return 1;
            return 0;
        });
        keefox_win.panel.showSearchResults.call(keefox_win.panel,logins);
    },

    // Calling this function with null or empty logins array will clear all existing search results
    showSearchResults: function (logins)
    {
        keefox_win.Logger.debug("panel showSearchResults started");
        // Get the container that we want to add our search results to.

        var container = this.getEmptyContainerFor("KeeFox-PanelSubSection-SearchResults");
        this.disableUIElement("KeeFox-PanelSubSection-SearchResults");
        if (container === undefined || container == null || logins == null || logins.length == 0)
            return;
            
        keefox_win.Logger.debug(logins.length + " search results found");

        for (let i = 0; i < logins.length; i++) {
            var login = logins[i];
            var usernameValue = "";
            var usernameName = "";
            var usernameDisplayValue = "[" + keefox_org.locale.$STR("noUsername.partial-tip") + "]";
            usernameValue = login.usernameValue;
            if (usernameValue != undefined && usernameValue != null && usernameValue != "")
                usernameDisplayValue = usernameValue;
            usernameName = login.usernameName;

            var loginItem = this.createUIElement('li', [
                ['class','login-item'],
                ['data-fileName',login.dbFileName],
                ['data-usernameName',usernameName],
                ['data-usernameValue',usernameValue],
                ['data-url',login.url],
                ['data-uuid',login.uniqueID],
                ['style','background-image:url(data:image/png;base64,' + login.iconImageData + ')'],
                ['title',keefox_org.locale.$STRF(
                "loginsButtonLogin.tip", [login.url, usernameDisplayValue])],
                ['tabindex','-1']
            ]);
            if (keefox_org._keeFoxExtension.prefs.getValue("alwaysDisplayUsernameWhenTitleIsShown",false))
                loginItem.textContent = keefox_org.locale.$STRF("matchedLogin.label", [usernameDisplayValue, login.title]);
            else
                loginItem.textContent = login.title;
            loginItem.addEventListener("keydown", this.keyboardNavHandler, false);
            loginItem.addEventListener("mouseup", function (event) { 
                // Make sure no parent groups override the actions of this handler
                event.stopPropagation();

                if (event.button == 0 || event.button == 1)
                {
                    this.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': event.button, 'ctrlKey': event.ctrlKey }}));
                } 
                if (event.button == 2)
                {
                    keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                    keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document, event, 'KeeFox-login-context');
                    if (keefox_win.SearchFilter.getFilterState(document, 'PanelSection') == "all")
                        keefox_org.metricsManager.adjustAggregate("searchResultContextAll", 1);
                    else
                        keefox_org.metricsManager.adjustAggregate("searchResultContextCurrent", 1);
                }
            }, false);
            loginItem.addEventListener("keefoxCommand", function (event) { 
                keefox_win.loadAndAutoSubmit(event.detail.button,
                                                    event.detail.ctrlKey,
                                                    this.getAttribute('data-url'),
                                                    this.getAttribute('data-uuid'), 
                                                    this.getAttribute('data-fileName')
                                                );
                keefox_win.panel.CustomizableUI.hidePanelForNode(
                    keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                keefox_win.panel.hideSubSections();
                if (keefox_win.SearchFilter.getFilterState(keefox_win.panel._currentWindow.document, 'PanelSection') == "all")
                    keefox_org.metricsManager.adjustAggregate("searchResultSelectedAll", 1);
                else
                    keefox_org.metricsManager.adjustAggregate("searchResultSelectedCurrent", 1);
            }, false);
            loginItem.addEventListener("keefoxContext", function (event) {
                keefox_win.panel.addLoginContextActions(document, this.getAttribute('data-uuid'), this.getAttribute('data-fileName'));
                keefox_win.panel.displayContextMenu(keefox_win.panel._currentWindow.document,
                    { target: event.detail.target, layerX: event.detail.layerX, layerY: event.detail.layerY },
                    'KeeFox-login-context');
                if (keefox_win.SearchFilter.getFilterState(keefox_win.panel._currentWindow.document, 'PanelSection') == "all")
                    keefox_org.metricsManager.adjustAggregate("searchResultContextAll", 1);
                else
                    keefox_org.metricsManager.adjustAggregate("searchResultContextCurrent", 1);
            }, false);
            loginItem.addEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);
            
            container.appendChild(loginItem);
        }
        
        // Update the UI state to reflect the number of logins found
        if (container.childElementCount > 0)
            this.enableUIElement("KeeFox-PanelSubSection-SearchResults");
        
        keefox_win.Logger.debug(logins.length + " search results set.");
    },

    onMouseEnterLogin: function (event) {
        if (keefox_win.panel.contextMenuShowing)
            return;

        // Fast user movement can result in the menu overlay button missing on the
        // login item under the mouse when the panel finishes opening but that's an
        // extremely minor problem compared to the alternative of requiring the user
        // to restart Firefox
        if (!keefox_win.panel.isOpen)
            return;

        let optionsMenuTrigger = keefox_win.panel.createUIElement('div', []);
        optionsMenuTrigger.addEventListener("mouseup", function (event) {

            event.preventDefault();
            event.stopPropagation();
            keefox_win.panel.addLoginContextActions(document,
                this.parentNode.getAttribute('data-uuid'),
                this.parentNode.getAttribute('data-fileName'));

            // Anchor to the parent li element because this trigger element will disappear soon
            keefox_win.panel.displayContextMenu(
                keefox_win.panel._currentWindow.document,
                {
                    target: event.target.parentNode,
                    layerX: this.offsetLeft + event.layerX,
                    layerY: this.offsetTop + event.layerY
                },
                'KeeFox-login-context');
            keefox_org.metricsManager.adjustAggregate("loginContextButton", 1);
            if (event.target.parentNode.parentNode.parentNode.id == "KeeFox-PanelSubSection-SearchResults") {
                if (keefox_win.SearchFilter.getFilterState(keefox_win.panel._currentWindow.document, 'PanelSection') == "all")
                    keefox_org.metricsManager.adjustAggregate("searchResultContextAll", 1);
                else
                    keefox_org.metricsManager.adjustAggregate("searchResultContextCurrent", 1);
            }
        }, false);
        optionsMenuTrigger.setAttribute("id", "optionsMenuTrigger");
        event.target.appendChild(optionsMenuTrigger);
        this.removeEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);
        this.addEventListener("mouseleave", keefox_win.panel.onMouseLeaveLogin, false);
    },
    onMouseLeaveLogin: function (event) {
        let optionsMenuTrigger = keefox_win.panel._currentWindow.document.getElementById("optionsMenuTrigger");
        event.target.removeChild(optionsMenuTrigger);
        this.removeEventListener("mouseleave", keefox_win.panel.onMouseLeaveLogin, false);
        this.addEventListener("mouseenter", keefox_win.panel.onMouseEnterLogin, false);
    },
    
    setWidgetNotificationForMatchedLogins: function (count)
    {
        keefox_win.Logger.debug("Going to notify that we have matched " + count + " logins");

        // This fails if called during startup and the widget is in the main panel (the node
        // has not been added to the DOM yet, pending user menu panel activation).
        //TODO:1.6: Is this still a problem now that we create the panel earlier? If so, maybe set some kind of on-main-menu-panel-open listener to retry this operation later

        // We can't know if this is going to be called just once (usual) or multiple times 
        // (e.g. for iframes) so we must make sure any race conditions are avoided or at least benign
        let widget = this._currentWindow.document.getElementById("keefox-button");
        if (widget)
            widget.setAttribute("keefox-match-count",count);
    },

    // Sets the overall widget status including the status panel
    // reports to the user the state of KeeFox and the KeePassRPC connection
    setWidgetStatus: function (enabled, buttonLabel, tooltip, detailedInfo, buttonAction)
    {
        keefox_win.Logger.debug("Setting widget status");
        let widgetButton = this._currentWindow.document.getElementById("keefox-button");
        let statusPanel = this._currentWindow.document.getElementById("KeeFox-PanelSection-status");
        let statusPanelText = this._currentWindow.document.getElementById("KeeFox-PanelSection-status-text");
        let statusPanelButton = this._currentWindow.document.getElementById("KeeFox-PanelSection-status-main-action");
        
        if (widgetButton === undefined || widgetButton == null)
            widgetButton = this._widget.node;
        if (statusPanel === undefined || statusPanel == null)
            statusPanel = widgetButton.querySelector("#KeeFox-PanelSection-status");
        if (statusPanelText === undefined || statusPanelText == null)
            statusPanelText = widgetButton.querySelector("#KeeFox-PanelSection-status-text");
        if (statusPanelButton === undefined || statusPanelButton == null)
            statusPanelButton = widgetButton.querySelector("#KeeFox-PanelSection-status-main-action");
        if (widgetButton === undefined || widgetButton == null 
            || statusPanel === undefined || statusPanel == null
            || statusPanelText === undefined || statusPanelText == null
            || statusPanelButton === undefined || statusPanelButton == null)
            return;

        widgetButton.classList.remove(enabled ? "disabled" : "enabled");
        widgetButton.classList.add(enabled ? "enabled" : "disabled");
        statusPanel.classList.remove(!enabled ? "disabled" : "enabled");
        statusPanel.classList.add(!enabled ? "enabled" : "disabled");
        statusPanelText.classList.remove(!enabled ? "disabled" : "enabled");
        statusPanelText.classList.add(!enabled ? "enabled" : "disabled");

        widgetButton.removeAttribute("keefox-match-count");

        // Remove all known possible event handlers
        statusPanelButton.removeEventListener("mouseup", this.mainButtonCommandInstallHandler, false);
        statusPanelButton.removeEventListener("mouseup", this.mainButtonCommandLaunchKPHandler, false);
        statusPanelButton.removeEventListener("mouseup", this.mainButtonCommandLoginKPHandler, false);
        statusPanelButton.removeEventListener("keefoxCommand", this.mainButtonCommandInstallHandler, false);
        statusPanelButton.removeEventListener("keefoxCommand", this.mainButtonCommandLaunchKPHandler, false);
        statusPanelButton.removeEventListener("keefoxCommand", this.mainButtonCommandLoginKPHandler, false);
        statusPanelButton.removeEventListener("keydown", this.keyboardNavHandler, false);

        if (enabled)
        {
            widgetButton.setAttribute("tooltiptext", keefox_org.locale.$STR("keefox-enabled"));
        } else
        {
            widgetButton.setAttribute("tooltiptext",tooltip);
            statusPanelText.textContent = detailedInfo;
            statusPanelButton.setAttribute("value", buttonLabel);
            statusPanelButton.setAttribute("tooltip",tooltip);
            statusPanelButton.addEventListener("mouseup", buttonAction, false);
            statusPanelButton.addEventListener("keefoxCommand", buttonAction, false);
            statusPanelButton.addEventListener("keydown", this.keyboardNavHandler, false);
        }
    },
    
    setupButton_install: function () {
        keefox_win.Logger.debug("setupButton_install start");

        this.setWidgetStatus(false,
            keefox_org.locale.$STR("installKeeFox.label"), 
            keefox_org.locale.$STR("installKeeFox.tip"),
            keefox_org.locale.$STR("installKeeFox.tip"),
            this.mainButtonCommandInstallHandler);
        this.disableUIElement("KeeFox-PanelSection-search");
        this.disableUIElement("KeeFox-PanelSection-matchedLogins");
        this.disableUIElement("KeeFox-PanelSection-allLogins");
        this.disableUIElement("KeeFox-PanelSection-generatePassword");
        this.disableUIElement("KeeFox-PanelSection-changeDatabase");
        this.disableUIElement("KeeFox-PanelSection-detectForms");
        keefox_win.Logger.debug("setupButton_install end");
    },

    // This is a generic "update panel status" method which will ensure the main widget icon
    // and overall panel configuration matches the current KeeFox state. In future we might
    // want to refactor this a little bit to use observers but the gain is not huge at this stage.
    setupButton_ready: function () {
        keefox_win.Logger.debug("setupButton_ready start");
        var mainButton;
        var mainWindow = this._currentWindow;
        mainButton = this._widget.node;
        if (mainButton === undefined || mainButton == null)
        {
            keefox_win.Logger.warn("widget node missing.");
            return;
        }

        if (keefox_org._keeFoxStorage.get("KeePassDatabaseOpen", false)) {

            var loggedInText = "";
            var activeDBIndex = mainWindow.keefox_org.ActiveKeePassDatabaseIndex;

            if (mainWindow.keefox_org.KeePassDatabases != null
                && mainWindow.keefox_org.KeePassDatabases.length > 0
                && mainWindow.keefox_org.KeePassDatabases[activeDBIndex] != null 
                && mainWindow.keefox_org.KeePassDatabases[activeDBIndex].root != null)
            {
                var numberOfDBs = mainWindow.keefox_org.KeePassDatabases.length;
                if (numberOfDBs == 1)
                    loggedInText = keefox_org.locale.$STRF("loggedIn.tip", [mainWindow.keefox_org.KeePassDatabases[activeDBIndex].name]);
                else
                    loggedInText = keefox_org.locale.$STRF("loggedInMultiple.tip", [numberOfDBs,mainWindow.keefox_org.KeePassDatabases[activeDBIndex].name]);
            } else
            {
                keefox_win.Logger.warn("KeePass database open but details unavailable.");
                return;
            }

            this.setWidgetStatus(true,
                keefox_org.locale.$STR("loggedIn.label"), 
                loggedInText,
                loggedInText,
                null);
            this.enableUIElement("KeeFox-PanelSection-search");
            this.enableUIElement("KeeFox-PanelSection-allLogins");
            this.enableUIElement("KeeFox-PanelSection-generatePassword");
            this.enableUIElement("KeeFox-PanelSection-changeDatabase");
            this.enableUIElement("KeeFox-PanelSection-detectForms");
        } else if (!keefox_org._keeFoxStorage.get("KeePassRPCInstalled", false)) {
            this.setupButton_install(mainWindow);
        } else if (!keefox_org._keeFoxStorage.get("KeePassRPCActive", false)) {
            this.setWidgetStatus(false,
                keefox_org.locale.$STR("launchKeePass.label"), 
                keefox_org.locale.$STR("notifyBarLaunchKeePassButton.tip"), 
                keefox_org.locale.$STR("notifyBarLaunchKeePass.label") + " " 
                    + keefox_org.locale.$STR("notifyBarLaunchKeePassButton.tip"), 
                this.mainButtonCommandLaunchKPHandler);
            this.disableUIElement("KeeFox-PanelSection-search");
            this.disableUIElement("KeeFox-PanelSection-matchedLogins");
            this.disableUIElement("KeeFox-PanelSection-allLogins");
            this.disableUIElement("KeeFox-PanelSection-generatePassword");
            this.disableUIElement("KeeFox-PanelSection-changeDatabase");
            this.disableUIElement("KeeFox-PanelSection-detectForms");
            this.hideSubSections();
        } else {
            this.setWidgetStatus(false,
                keefox_org.locale.$STR("loginToKeePass.label"), 
                keefox_org.locale.$STR("notifyBarLoginToKeePassButton.tip"), 
                keefox_org.locale.$STR("notifyBarLaunchKeePass.label") + " " 
                    + keefox_org.locale.$STR("notifyBarLoginToKeePassButton.tip"), 
                this.mainButtonCommandLoginKPHandler);
            this.disableUIElement("KeeFox-PanelSection-search");
            this.disableUIElement("KeeFox-PanelSection-matchedLogins");
            this.disableUIElement("KeeFox-PanelSection-allLogins");
            this.enableUIElement("KeeFox-PanelSection-generatePassword");
            this.enableUIElement("KeeFox-PanelSection-changeDatabase");
            this.disableUIElement("KeeFox-PanelSection-detectForms");
            this.hideSubSections();
        }

        keefox_win.Logger.debug("setupButton_ready end");
    },

    setMRUdatabasesCallback: function (result) {

        let container = this.getEmptyContainerFor("KeeFox-PanelSubSection-DatabaseList");
        if (container === undefined || container == null)
            return;

        // Remove the loading message
        container.parentNode.removeAttribute("loading");

        let mruArray = result.knownDatabases;
        let noItemsButton;
        if (mruArray == null || mruArray.length == 0) {
            noItemsButton = this.createUIElement('li', [
                ['class',''],
                ['title',keefox_org.locale.$STR("changeDBButtonEmpty.tip")],
                ['tabindex','-1']
            ]);
            noItemsButton.textContent = keefox_org.locale.$STR("changeDBButtonEmpty.label");
            container.appendChild(noItemsButton);
        } else {

            for (let i = 0; i < mruArray.length; i++)
            {
                let displayName = mruArray[i];
                let suffix, prefix;
                if (displayName.length > 50) {
                    var fileNameStartLocation = displayName.lastIndexOf('\\');
                    var spareChars = 50 - (displayName.length - fileNameStartLocation);
                    if (spareChars > 10) {
                        var path = displayName.substr(0, fileNameStartLocation);
                        var parentStartLocation = path.lastIndexOf('\\');
                        var preferredSpareChars = 50 - (displayName.length - parentStartLocation);
                        if (preferredSpareChars > 10) {
                            suffix = displayName.substr(parentStartLocation);
                            prefix = displayName.substr(0, preferredSpareChars);
                        }
                        else {
                            suffix = displayName.substr(fileNameStartLocation);
                            prefix = displayName.substr(0, spareChars);
                        }
                        displayName = prefix + ' ... ' + suffix;
                    } // otherwise there's not much we can do anyway so just leave it to truncate the end of the file name
                }
            
                let mruToUse = mruArray[i].replace(/[\\]/g, '\\');
                let mruItem = this.createUIElement('li', [
                    ['class',''],
                    ['mruToUse',mruToUse],
                    ['title',keefox_org.locale.$STRF("changeDBButtonListItem.tip", [mruArray[i]])],
                    ['tabindex','-1']
                ]);
                mruItem.textContent = displayName;
                mruItem.addEventListener("keydown", this.keyboardNavHandler, false);
                mruItem.addEventListener("mouseup", function (event) {
                    if (event.button == 0 || event.button == 1)
                    {
                        event.stopPropagation();
                        this.dispatchEvent(new Event("keefoxCommand"));
                    }
                }, false);
                mruItem.addEventListener("keefoxCommand", function (event) {
                    keefox_org.changeDatabase(this.getAttribute('mruToUse'), false);
                    keefox_win.panel.CustomizableUI.hidePanelForNode(
                        keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                    keefox_win.panel.hideSubSections();
                }, false);

                container.appendChild(mruItem);
            }
        }
        
        // Try to focus on the first item in the newly displayed sub section
        let matches = container.getElementsByTagName('li');
        if (!matches)
            return;
        let firstMatch = matches[0];
        if (firstMatch)
            firstMatch.focus();
    },

    setPasswordProfilesCallback: function (result) {

        let container = this.getEmptyContainerFor("KeeFox-PanelSubSection-PasswordProfileList");
        if (container === undefined || container == null)
            return;

        // Remove the loading message
        container.parentNode.removeAttribute("loading");

        let passwordProfilesExplanation = this.createUIElement('div', [
                ['class', '']
        ]);
        passwordProfilesExplanation.textContent = keefox_org.locale.$STR("generatePassword.copied")
            + " " + keefox_org.locale.$STR("PasswordProfilesExplanation.label");
        container.appendChild(passwordProfilesExplanation);

        let profileArray = result;
        let noItemsButton;
        if (profileArray == null || profileArray.length == 0) {
            noItemsButton = this.createUIElement('li', [
                ['class',''],
                ['tabindex','-1']
            ]);
            // This shouldn't ever happen since KeePass has built in profiles. So we won't bother localising
            // this string but it's here just in case some weird change is made to KeePass in future.
            noItemsButton.textContent = "There are no password profiles. Use KeePass to save new profiles.";
            container.appendChild(noItemsButton);
        } else {

            for (let i = 0; i < profileArray.length; i++)
            {
                let displayName = profileArray[i];
            
                let profileItem = this.createUIElement('li', [
                    ['class',''],
                    ['tabindex','-1']
                ]);
                profileItem.textContent = displayName;
                profileItem.addEventListener("keydown", this.keyboardNavHandler, false);
                profileItem.addEventListener("mouseup", function (event) {
                    if (event.button == 0 || event.button == 1)
                    {
                        event.stopPropagation();
                        this.dispatchEvent(new Event("keefoxCommand"));
                    }
                }, false);
                profileItem.addEventListener("keefoxCommand", function (event) {
                    let kf = keefox_org;
                    kf.metricsManager.pushEvent ("feature", "generatePasswordFromProfile");
                    kf.generatePassword(this.textContent, keefox_win.panel._currentWindow.gLastValidURLStr);
                    keefox_win.panel.CustomizableUI.hidePanelForNode(
                        keefox_win.panel._currentWindow.document.getElementById('keefox-panelview'));
                    keefox_win.panel.hideSubSections();
                }, false);

                container.appendChild(profileItem);
            }
        }
        
        // Try to focus on the first item in the newly displayed sub section
        let matches = container.getElementsByTagName('li');
        if (!matches)
            return;
        let firstMatch = matches[0];
        if (firstMatch)
            firstMatch.focus();
    },

    generatePassword: function () {
        let kf = this._currentWindow.keefox_org;
        kf.metricsManager.pushEvent ("feature", "generatePassword");
        kf.generatePassword(null, this._currentWindow.gLastValidURLStr);
    },

    mainButtonCommandInstallHandler: function (event) {
        keefox_org.KeeFox_MainButtonClick_install();
    },

    mainButtonCommandLaunchKPHandler: function (event) {
        keefox_org.launchKeePass('');
    },

    mainButtonCommandLoginKPHandler: function (event) {
        keefox_org.loginToKeePass();
    },

    displayPanel: function () {
        // Show our main viewpanel (might not work if its placed inside the main Firefox menu)
        let buttonPlacement = CustomizableUI.getPlacementOfWidget('keefox-button');
        if (!buttonPlacement)
            return; // Widget is not available (in the customisation pallette instead?)
        
        if (CustomizableUI.getAreaType(buttonPlacement.area) == CustomizableUI.TYPE_MENU_PANEL)
        {
            // We're in the main Firefox menu so we need to make that appear first
            this._currentWindow.document.defaultView.PanelUI.show();

            // There is probably a missing step here because the Firefox main menu
            // is all messed up if the user closes the KeeFox panel by clicking to the 
            // left on the main menu. It's just a bit messy rather than a big problem. 
            // Some event must be fired oncommand from buttons within the main menu but since 
            // devtools can't inspect the menu, we can't know what trick is happening. 
        }

        this._currentWindow.document.defaultView.PanelUI.showSubView('keefox-panelview',
                    this._widget.anchor,
                    buttonPlacement.area);
    },
    
    displayContextMenu: function (doc, event, id)
    {
        keefox_win.panel.contextMenuShowing = true;
        let context = doc.getElementById(id);
        context.addEventListener("popuphidden", keefox_win.panel.onContextMenuClose);
        context.openPopup(event.target
                        , "topleft topleft", event.layerX + 2, event.layerY + 2, true, false);
    },

    onContextMenuClose: function (event)
    {
        if (event.target.id != "KeeFox-login-context" && event.target.id != "KeeFox-group-context")
            return;

        this.removeEventListener("popuphidden", keefox_win.panel.onContextMenuClose);
        keefox_win.panel.contextMenuShowing = false;
    },

    keyboardNavHandler: function (event) {
        // If arrow, enter, esc, etc. do stuff. e.g. focusAdjacentPanelItem(event.target,false)
        // prevent propergation and default if action has been taken
        if (event.keyCode == 13) // enter
        {
            event.preventDefault();
            event.stopPropagation();
            this.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': 0, 'ctrlKey': event.ctrlKey }} ));
        } else if (event.keyCode == 40) // down
        {
            event.preventDefault();
            event.stopPropagation();
            keefox_win.panel.focusAdjacentPanelItem(event.target, false);
        } else if (event.keyCode == 38) // up
        {
            event.preventDefault();
            event.stopPropagation();
            keefox_win.panel.focusAdjacentPanelItem(event.target, true);
        } else if (event.keyCode == 37) // left
        {
            // if the focussed element is a group or item
            if (event.target.classList.contains("group-item") || event.target.classList.contains("login-item"))
            {
                event.preventDefault();
                event.stopPropagation();

                // if it has a parent group, behave as if the user had clicked on the parent and then focus the keyboard on the parent group item
                if (event.target.parentNode && event.target.parentNode.parentNode 
                    && event.target.parentNode.parentNode.nodeName == "li")
                    event.target.parentNode.parentNode.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': 0, 'ctrlKey': event.ctrlKey }} ));
            }
        } else if (event.keyCode == 39) // right
        {
            // if the focussed element is a group
            if (event.target.classList.contains("group-item"))
            {
                // if it has any child items, behave as if the user had clicked on the focussed group and then focus the keyboard on the first child item
                // actually both things should be handed by the standard command. maybe need to ensure keyboard focus is set right depending on wheher the active group has child items or not.
                
                
                event.preventDefault();
                event.stopPropagation();
                // Only issue the toggle command if the target element is not the active-group
                if (!event.target.classList.contains("active-group"))
                    this.dispatchEvent(new CustomEvent("keefoxCommand", { 'detail': { 'button': 0, 'ctrlKey': event.ctrlKey }} ));
                else
                {
                    let fc = event.target.firstElementChild;
                    if (fc) fc = fc.firstElementChild;
                    if (fc) fc.focus();
                }

            }
        } else if (event.keyCode == 27) // esc
        {
            event.preventDefault();
            event.stopPropagation();
            this.dispatchEvent(new CustomEvent("keefoxEscape", { 'detail': { 'button': 0, 'ctrlKey': event.ctrlKey }} ));
        } else if (event.keyCode == 93) // context
        {
            event.preventDefault();
            event.stopPropagation();
            this.dispatchEvent(new CustomEvent("keefoxContext", { 'detail': { 'target': this, 'layerX': 5, 'layerY': 5 }} ));
        }

    },
    
    focusAdjacentPanelItem: function (startNode, reverse) {
        let node = startNode;
        let isVisible = false;

        // if current element is a group item and it is active (open), we find the first child of the group list and focus that.
        if (!reverse && node.classList.contains("group-item") && (node.classList.contains("active-group") || node.classList.contains("active-group-parent")))
        {
            let fc = this._findFirstFocusableChildItem(node.firstElementChild, reverse);
            if (fc != null)
                fc.focus();
            return;
        }

        let maxParentNodeHeight = 100;
        if (reverse && node && node.parentNode && node.parentNode.parentNode && node.parentNode.parentNode.classList.contains("group-item") )
            maxParentNodeHeight = 2;

        while (true)
        {
            node = reverse ? this._getPreviousSiblingOrParent(node,maxParentNodeHeight) : this._getNextSiblingOrParent(node);

            if (node != null)
            {
                // If we're going backwards and have found a sibling node that is an active (open) group
                if (reverse && node.parentNode == startNode.parentNode && node.classList.contains("group-item") 
                    && (node.classList.contains("active-group") || node.classList.contains("active-group-parent")))
                {
                    // we need to find the last child in the group's heirachy (because it will be visually displayed directly above our start node)
                    let childNode = node;
                    while (true)
                    {
                        childNode = this._findFirstFocusableChildItem(childNode.firstElementChild, true);
                        if (childNode.classList.contains("group-item") 
                            && (childNode.classList.contains("active-group") || childNode.classList.contains("active-group-parent")))
                            continue;
                        node = childNode;
                        break;
                    }
                }

                let focusableNode = this._findFirstFocusableChildItem(node, reverse);
                if (focusableNode != null)
                {
                    node = focusableNode;
                    break;
                }
                // else try the next sibling or parent
            } else
                break;

        }

        if (node != null)
        {
            // We found a node to focus to
            node.focus();
        }
    },

    _setInitialPanelFocus: function (preferMatchedLogins)
    {
        let focusItem, startNode;

        // e.g. if user invoked main panel via the "fill matched login" shortcut (ctrl-shift-2)
        if (preferMatchedLogins)
        {
            startNode = keefox_win.panel._currentWindow.document.getElementById('KeeFox-PanelSubSection-MatchedLoginsList');
            focusItem = keefox_win.panel._findFirstFocusableChildItem(startNode, false);
            if (focusItem)
                return focusItem;
        }

        // if we can find the search box, focus on that (if not, we're not logged in to a database
        // so we fall back to the standard "focus on first visible child" behaviour)
        
        startNode = keefox_win.panel._currentWindow.document.getElementById('KeeFox-PanelSection-searchbox');
        focusItem = keefox_win.panel._findFirstFocusableChildItem(startNode, false);
        if (focusItem)
            return focusItem;
        
        startNode = keefox_win.panel._currentWindow.document.getElementById('keefox-panelview');
        return keefox_win.panel._findFirstFocusableChildItem(startNode, false);
    },

    _findFirstFocusableChildItem: function (startNode, reverse)
    {
        // search all children and focus on the first one we find that can be interacted with (i.e. li, button, input)
        // Note that this is a bit of a quick hack so will actually return
        // items prioritised by the order of the tags. I think this is fine 
        // for our initial panel design (only one input field and all buttons 
        // are at the bottom apart from "back" buttons which arguably
        // shouldn't be the subject of initial focus anyway).

        let tags = ['input','li','button'];
        if (tags.indexOf(startNode.tagName) < 0)
        {
            for (let i=0; i < tags.length; i++)
            {
                let list = startNode.getElementsByTagName(tags[i]);
                let start = reverse ? list.length-1 : 0;
                let incr = reverse ? -1 : 1;
                for (let j = start; (reverse && j >= 0) || (!reverse && j < list.length); j += incr)
                {
                    let isVisible = list[j].offsetWidth > 0 || list[j].offsetHeight > 0;
                    if (isVisible)
                        return list[j];
                }
            }
        } else
        {
            let isVisible = startNode.offsetWidth > 0 || startNode.offsetHeight > 0;
            if (isVisible)
                return startNode;
        }
        return null;
    },
    
    _getPreviousSiblingOrParent: function (startNode, height) {
        // If our height tracker goes below 0 we know we've now gone too far up the heirachy
        if (height <= 0)
           return startNode;
        if (startNode.tagName == 'ul')
           return this._getPreviousSiblingOrParent(startNode.parentNode, --height);
        let node = startNode;

        if (node == null)
            return null;

        node = node.previousElementSibling;

        if (node == null)
        {
            node = startNode.parentNode;

            // As long as we've not gone too far up the DOM tree, keep going until we find a previous element sibling
            if (node.id != this.panelContainerId)
                node = this._getPreviousSiblingOrParent(node, --height);
            else
                node = null;
        }
        // Either the next sibling or null if we couldn't find a sibling before we got to the top of the DOM tree 
        return node;
    },
    
    //loop through all following siblings and check if its visible. go to parents siblings after nodes siblings are exhausted.
    // ignore all ul nodes (we only care about the li that contains it and those contained within)
    _getNextSiblingOrParent: function (startNode) {
        if (startNode.tagName == 'ul')
            return this._getNextSiblingOrParent(startNode.parentNode);
        let node = startNode;

        if (node == null)
            return null;

        node = node.nextElementSibling;

        if (node == null)
        {
            node = startNode.parentNode;

            // As long as we've not gone too far up the DOM tree, keep going until we find a next element sibling
            if (node.id != this.panelContainerId)
                node = this._getNextSiblingOrParent(node);
            else
                node = null;
        }
        // Either the next sibling or null if we couldn't find a sibling before we got to the top of the DOM tree 
        return node;
    },

    attachViewOpeningObserver: function (targetDoc) {

        // First we have to look for the Australis panel to be added to the nav-bar
        let target = targetDoc.getElementById('nav-bar');
        let observer = new MutationObserver(function (mutations, childListObserver) {
            
            mutations.forEach(function (mutation) {
                for (var node of mutation.addedNodes) {
                    if (node.id == "customizationui-widget-panel")
                    {
                        childListObserver.disconnect();

                        // Then we look for the completion of the animation, etc.
                        // so that we know the KeeFox panel is fully open (or closed)
                        let observer = new MutationObserver(function (mutations, attributesObserver) {

                            mutations.forEach(function (mutation) {
                                let changeValue = mutation.target.getAttribute("panelopen");
                                if (changeValue == "true") {
                                    keefox_win.panel.isOpen = true;
                                    targetDoc.dispatchEvent(new CustomEvent("keefoxMainPanelOpened"));
                                } else {
                                    keefox_win.panel.isOpen = false;
                                    targetDoc.dispatchEvent(new CustomEvent("keefoxMainPanelClosed"));
                                    // We can keep the same observer until the panel is closed
                                    attributesObserver.disconnect();
                                }
                            });
                        });

                        let config = { attributes: true, attributeFilter: ["panelopen"] };
                        observer.observe(node, config);
                    }
                }
            });
        });

        let config = { childList: true };
        observer.observe(target, config);
    }

};
