aboutsummaryrefslogblamecommitdiffstats
path: root/lib/bzpage.js
blob: 1731b6435c62d2c6e4c55d713a1393ac5aaca9f7 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                                                                                                                                                                                                           


                                                     
                           
                                     
                                    
                              
                                
                              
                                              
                                                 
                                     
                                 
                           

                                   


                                                            
                       
                                      
                       
                                        

                                           
                                   
 











                                                                    

                                                                                       
                                           




                                                                                   
                                    
                    

                            
                                               











                                                                
                                                      
 
                                               
                                                              
     
 
                           
                                             
                                                          

                                                        
     
 

                                                                       
     

                                                                
                                                                                     
     
 
                                                           


                                                                           





                                                          





                                                                                
                                  








                                                                                   
 
                                




                                                                                     

                                                                     





                                                                                 

     
                         
                           
  
 




                         

                                                  

  


   
                                                                            
                               
                             
 
                                                                
                                                                

                                                                    

                                                                                                

                                    
                                                                                     
             
                
                                                                   



                                                            

         
                                                          

                                                                            


























































































                                                                                                 


                                      

















                                                                  







                                                                                        










                                                                 
                             
  


                                                                              



                                                                         
                                                               
                                 

                          














































                                                                                    
                                                          


                                                                         


                                                           






























                                                                                        
                                                                    




































































                                                                                   











                                                                                          
                                                                                       
                        

                                                                             






                                  








                                                                  
                                    
                   






                                                                                           



                





                                                        

                                                                                
                                             

































                                                                               

                                                                         















                                                                      
                                                                        






                                                                       
   
                                                                              
                     

                         

                                          




                                                          

  




















                                                                                           














































































                                                                                       
                                                                























                                                                                     
                                              
















































                                                                                                   



                                                                                           
                                                                   



                                                                  
                                                              




                                                           
                                                                    



                                               
         

                    
     





                                                          

                                                      


                




                                                                                       
                                       
                                                                                         
                                                   
                                








                                                                                     
                                









                                                                    







                                                                                 
 











                                                                           
                 

                      

                                                                      
                             



                                                             




                                                   






                                                            



























































                                                                                   




























                                                                     
                       







                                                                    
                                                       
                        
/*jslint forin: true, rhino: true, onevar: false, browser: true, evil: true, laxbreak: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, maxerr: 1000, immed: false, white: false, plusplus: false, regexp: false, undef: false, strict: true */
// Released under the MIT/X11 license
// http://www.opensource.org/licenses/mit-license.php
"use strict";
var util = require("util");
var passUtils = require("passwords");
var apiUtils = require("api-utils");
var selfMod = require("self");
var clip = require("clipboard");
var fileMod = require("file");
var simpleStorage = require("simple-storage");
var preferences = require("preferences-service");
var selection = require("selection");
var prompts = require("prompts");
var tabs = require("tabs");
var Color = require("color").Color;

var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id=";

// Shared contstants
var TriagedDistro = 13;
exports.TriagedDistro = TriagedDistro;
var NumberOfFrames = 7;
exports.NumberOfFrames = NumberOfFrames;
var BTSPrefNS = "bugzilla-triage.setting.";
exports.BTSPrefNS = BTSPrefNS;
var BTSPassRealm = "BTSXMLRPCPass";

// ============================================

var NotLoggedinException = function NotLoggedinException (message) {
    this.message = message;
    this.name = "NotLoggedinException";
};

NotLoggedinException.prototype.toString = function () {
    return this.name + ': "' + this.message + '"';
};
exports.NotLoggedinException = NotLoggedinException;

// ====================================================================================
// BZPage's methods
var BZPage = function BZPage(win, config) {
    // constants
    this.SalmonPink = new Color(255, 224, 176); // RGB 255, 224, 176; HSL 36, 2,
                                                // 85
    this.ReporterColor = new Color(255, 255, 166); // RGB 255, 255, 166; HSL 60, 2,
                                                   // 83
    // initialize dynamic properties
    var that = this;
    this.win = win;
    this.doc = win.document;
    this.hostname = this.win.location.hostname;

    // First, preflight check ... if we are not logged in, there
    // is nothing we can do.
    var logoutLink = Array.some(this.doc.links, function (x) {
        return x.search === "?logout=1" ;
    });
    if (!logoutLink) {
        throw new NotLoggedinException("Not logged in");
    }

    // So, now we know we are logged in, so we can get to
    // the real work.
    this.packages = this.getInstalledPackages(config);

    if ("commentStrings" in config.gJSONData) {
        this.commentStrings = config.gJSONData.commentStrings;
    }

    this.constantData = {};
    if ("constantData" in config.gJSONData) {
        this.constantData = config.gJSONData.constantData;
        this.constantData.queryUpstreamBug = JSON.parse(
            selfMod.data.load("queryUpstreamBug.json"));
    }

    if ("CCmaintainer" in this.constantData) {
        this.defBugzillaMaintainerArr = this.constantData.CCmaintainer;
    }

    if ("suspiciousComponents" in config.gJSONData.configData) {
        this.suspiciousComponents = config.gJSONData.configData.suspiciousComponents;
    }

    if ("XorgLogAnalysis" in config.gJSONData.configData) {
        this.xorglogAnalysis = config.gJSONData.configData.XorgLogAnalysis;
    }

    if ("submitsLogging" in config.gJSONData.configData &&
        config.gJSONData.configData.submitsLogging) {
        this.log = config.logger;
        this.setUpLogging();
    }

    if ("killNodes" in config.gJSONData.configData &&
        this.hostname in config.gJSONData.configData.killNodes) {
            var killConf = config.gJSONData.configData.killNodes[this.hostname];
            util.killNodes(this.doc, killConf[0], killConf[1])
    }

    this.setConfigurationButton();
    this.submitHandlerInstalled = false;
    this.bugNo = util.getBugNo(this.doc.location.toString());

    this.reporter = this.getReporter();
    this.product = this.getOptionValue("product");
    this.component = this.getOptionValue("component");
    this.version = this.getVersion();
    this.title = this.doc.getElementById("short_desc_nonedit_display").textContent;
    this.CCList = this.getCCList();

    // Prepare for query buttons
    // element ID brElementPlace_location is later used in JSON files
    // Stay with this add_comment element even if RH BZ upgrades, this seems
    // to be generally much more stable (even with other bugzillas, e.g. b.gnome.org)
    // then some getElementById.
    var commentArea = this.doc.getElementsByName("add_comment")[0].parentNode;
    if (commentArea) {
        var brElementPlacer = commentArea.getElementsByTagName("br");
        brElementPlacer = brElementPlacer[0];
        if (brElementPlacer) {
            brElementPlacer.setAttribute("id","brElementPlacer_location");
            brElementPlacer.parentNode.insertBefore(this.doc.createElement("br"),
                brElementPlacer);
        }
    }

    this.checkComments();
    this.generateButtons();
};

/**
 * Get the ID of the bug.
 *
 * @return string
 */
BZPage.prototype.getBugId = function getBugId () {
    return util.getBugNo(this.doc.location.href);
};

/**
 *
 */
BZPage.prototype.getInstalledPackages = function getInstalledPackages(cfg) {
    var installedPackages = {};
    var enabledPackages = [];

    // Collect enabled packages per hostname (plus default ones)
    if (cfg.gJSONData && ("commentPackages" in cfg.gJSONData)) {
        if ("enabledPackages" in cfg.gJSONData.configData) {
            var epObject = cfg.gJSONData.configData.enabledPackages;
            if (this.hostname in epObject) {
                enabledPackages = enabledPackages.concat(epObject[this.hostname].split(/[, ]/));
            }
            if ("any" in epObject) {
                enabledPackages = enabledPackages.concat(epObject.any.split(/[, ]/));
            }
        } else {
            // Default to collecting all comment packages available
            enabledPackages = [];
            for (var key in cfg.gJSONData.commentPackages) {
                enabledPackages.push(key);
            }
        }

        enabledPackages.forEach(function (pkg, idx, arr) {
            if (pkg in cfg.gJSONData.commentPackages) {
                installedPackages[pkg] = cfg.gJSONData.commentPackages[pkg];
            }
        });
    }
    return installedPackages;
};

/**
 * Actual execution function
 *
 * @param cmdLabel String with the name of the command to be executed
 * @param cmdParams Object with the appropriate parameters for the command
 */
BZPage.prototype.centralCommandDispatch = function centralCommandDispatch (cmdLabel, cmdParams) {
    switch (cmdLabel) {
        case "resolution":
        case "product":
        case "component":
        case "version":
        case "priority":
            this.selectOption(cmdLabel, cmdParams);
            break;
        case "status":
            this.selectOption("bug_status", cmdParams);
            break;
        case "platform":
            this.selectOption("rep_platform", cmdParams);
            break;
        case "os":
            this.selectOption("op_sys", cmdParams);
            break;
        case "severity":
            this.selectOption("bug_severity", cmdParams);
            break;
        case "target":
            this.selectOption("target_milestone", cmdParams);
            break;
        case "addKeyword":
            this.addStuffToTextBox("keywords",cmdParams);
            break;
        case "removeKeyword":
            this.removeStuffFromTextBox("keywords", cmdParams);
            break;
        case "addWhiteboard":
            this.addStuffToTextBox("status_whiteboard",cmdParams);
            break;
        case "removeWhiteboard":
            this.removeStuffFromTextBox("status_whiteboard",cmdParams);
            break;
        case "assignee":
            this.changeAssignee(cmdParams);
            break;
        case "qacontact":
            this.clickMouse("bz_qa_contact_edit_action");
            this.doc.getElementById("qa_contact").value = cmdParams;
            break;
        case "url":
            this.clickMouse("bz_url_edit_action");
            this.doc.getElementById("bug_file_loc").value = cmdParams;
            break;
        // TODO dependson/blocked doesn't work. Find out why.
        case "addDependsOn":
            this.clickMouse("dependson_edit_action");
            this.addStuffToTextBox("dependson", cmdParams);
            break;
        case "removeDependsOn":
            this.clickMouse("dependson_edit_action");
            this.removeStuffFromTextBox("dependson", cmdParams);
            break;
        case "addBlocks":
            this.clickMouse("blocked_edit_action");
            this.addStuffToTextBox("blocked", cmdParams);
            break;
        case "removeBlocks":
            this.clickMouse("blocked_edit_action");
            this.removeStuffFromTextBox("blocked", cmdParams);
            break;
        case "comment":
            this.addStuffToTextBox("comment", cmdParams);
            break;
        case "commentIdx":
            var commentText = this.commentStrings[cmdParams];
            this.addStuffToTextBox("comment", commentText);
            break;
        case "setNeedinfo":
            // cmdParams are actually ignored for now; we may in future
            // distinguish different actors to be target of needinfo
            this.setNeedinfoReporter();
            break;
        case "addCC":
            this.addToCCList(cmdParams);
            break;
        case "queryStringOurBugzilla":
            this.queryForSelection();
            break;
        // TODO flags, see also

        case "commit":
            if (cmdParams) {
                // Directly commit the form
                this.doc.forms.namedItem("changeform").submit();
            }
            break;
    }
};

/**
 * Take the ID of the package/id combination, and execute it
 *
 * @param String combined package + "//" + id combination
 * Fetches the command object from this.installedPackages and then
 * goes through all commands contained in it, and calls
 * this.centralCommandDispatch to execute them.
 *
 * PROBLEM: according to https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\
 * /Statements/for...in there is no guaranteed order of execution of
 * commands (i.e., key, commentObj[key] pairs) in for..in cycle.
 * According to https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\
 * /Operators/Special_Operators/delete_Operator#Cross-browser_issues it seems that
 * everywhere except of Internet Explorer this should work well, but waiting
 * impatiently when this bite us.
 */
BZPage.prototype.executeCommand = function executeCommand (cmd) {
    var cmdArr = cmd.split("//");
    var commentObj = this.packages[cmdArr[0]][cmdArr[1]];

    for (var key in commentObj) {
        this.centralCommandDispatch(key,commentObj[key]);
    }
};

/**
 * Change assignee of the bug
 *
 * @param newAssignee String with the email address of new assigneeAElement
 *        or 'default' if the component's default assignee should be used.
 *        Value null clears "Reset Assignee to default for component" checkbox
 * @return none
 */
BZPage.prototype.changeAssignee = function changeAssignee (newAssignee) {
    var defAssigneeButton = null;
    // Previous assignee should know what's going on in his bug
    this.addToCCList(this.owner);

    // Optional value null
    if (newAssignee === null) {
        this.doc.getElementById("set_default_assignee").removeAttribute(
            "checked");
        return ;
    }

    if (this.getDefaultAssignee) {
        if (newAssignee === "default") {
            var defAss = this.getDefaultAssignee();
            if (defAss) {
                newAssignee = defAss;
            } else {
                return ;
            }
        }
    }

    if (newAssignee) {
        this.clickMouse("bz_assignee_edit_action");
        this.doc.getElementById("assigned_to").value = newAssignee;
        this.doc.getElementById("set_default_assignee").checked = false;
        defAssigneeButton = this.doc.getElementById("setDefaultAssignee_btn");
        if (defAssigneeButton) {
            defAssigneeButton.style.display = "none";
        }
    }
};

/**
 * Adds new option to the 'comment_action' scroll down box
 *
 * @param pkg String package name
 * @param cmd String with the name of the command
 * If the 'comment_action' scroll down box doesn't exist, this
 * function will set up new one.
 */
BZPage.prototype.addToCommentsDropdown = function addToCommentsDropdown (pkg, cmd) {
    var select = this.doc.getElementById("comment_action");
    if (!select) {
        var that = this;
        this.doc.getElementById("comments").innerHTML +=
            "<div id='make_bugzilla_comment_action'>" +
            "  <label for='comment_action'>Add Comment: </label>" +
            "  <select id='comment_action'>" +
            "    <option value=''>-- Select Comment from List --</option>" +
            "</div>";
        select = this.doc.getElementById("comment_action");
        select.addEventListener("change", function (evt) {
            var value = "";
            var valueElement = that.doc.getElementById("comment_action");
            if (valueElement) {
                var selIdx = valueElement.selectedIndex;
                value = valueElement.options[selIdx].value;
                console.log("value = " + value);
            } else {
                return;
            }
            that.executeCommand(value);
        }, false);
    }

    var opt = this.doc.createElement("option");
    opt.value = pkg + "//" + cmd;
    opt.textContent = this.packages[pkg][cmd].name;
    select.appendChild(opt);
};

/**
 * Generic function to add new button to the page. Actually copies new button
 * from the old one (in order to have the same look-and-feel, etc.
 *
 * @param location Object around which the new button will be added
 * @param after Boolean before or after location ?
 * @param pkg String which package to take the command from
 * @param id String which command to take
 * @return none
 */
BZPage.prototype.createNewButton = function createNewButton (location, after, pkg, id) {
    var that = this;
    var cmdObj = this.packages[pkg][id];
    var newId = id + "_btn";
    var label = cmdObj.name;

    // protection against double-firings
    if (this.doc.getElementById(newId)) {
        console.log("Element with id " + newId + "already exists!");
        return ;
    }

    // creation of button might be conditional on existence of data in constantData
    if ("ifExist" in cmdObj) {
        if (!(cmdObj.ifExist in this.constantData)) {
            return ;
        }
    }

    var newButton = this.doc.createElement("input");
    newButton.setAttribute("id", newId);
    newButton.setAttribute("type", "button");
    newButton.value = label;
    newButton.addEventListener("click", function(evt) {
        that.executeCommand(pkg + "//" + id);
    }, false);

    var originalLocation = this.doc.getElementById(location);

    if (after) {
        originalLocation.parentNode.insertBefore(newButton,
                originalLocation.nextSibling);
        originalLocation.parentNode.insertBefore(this.doc
                .createTextNode("\u00A0"), newButton);
    } else {
        originalLocation.parentNode.insertBefore(newButton, originalLocation);
        originalLocation.parentNode.insertBefore(this.doc
                .createTextNode("\u00A0"), originalLocation);
    }
};

/**
 *
 */
BZPage.prototype.generateButtons = function generateButtons () {
    var topRowPosition = "topRowPositionID";
    var bottomRowPosition = "commit";

    // create anchor for the top toolbar
    var commentBox = this.doc.getElementById("comment");
    var brElement = this.doc.createElement("br");
    brElement.setAttribute("id",topRowPosition);
    commentBox.parentNode.normalize();
    commentBox.parentNode.insertBefore(brElement, commentBox);

    for (var pkg in this.packages) {
        for (var cmdIdx in this.packages[pkg]) {
            var cmdObj = this.packages[pkg][cmdIdx];
            switch (cmdObj.position) {
                case "topRow":
                    this.createNewButton(topRowPosition, false, pkg, cmdIdx);
                    break;
                case "bottomRow":
                    this.createNewButton(bottomRowPosition, false, pkg, cmdIdx);
                    break;
                case "dropDown":
                    this.addToCommentsDropdown(pkg,cmdIdx);
                    break;
                default: // [+-]ID
                    var firstChr = cmdObj.position.charAt(0);
                    var newId = cmdObj.position.substr(1);
                    this.createNewButton(newId, firstChr === "+", pkg, cmdIdx);
                    break;
            }
        }
    }
};

BZPage.prototype.setConfigurationButton = function setConfigurationButton () {
    var additionalButtons = this.doc.querySelector("#bugzilla-body *.related_actions");
    var configurationButtonUI = this.doc.createElement("li");
    configurationButtonUI.innerHTML = "\u00A0-\u00A0<a href='#' id='configurationButton'>"
        + "Triage configuration</a>";
    additionalButtons.appendChild(configurationButtonUI);
    this.doc.getElementById("configurationButton").addEventListener(
        "click",
        function(evt) {
            var prfNm = BTSPrefNS+"JSONURL";
            var url = preferences.get(prfNm,"");

            var reply = prompts.prompt("New location of JSON configuration file", url);
            if (reply) {
                preferences.set(prfNm, reply.trim());
                prompts.alert("For now, you should really restart Firefox!");
            }

            evt.stopPropagation();
            evt.preventDefault();
    }, false);
};

/*
 * From <a href="mailto:email"> element diggs out just plain email
 * address
 *
 * @param aElement Element with href attribute or something else
 * @return String with the address or null
 *
 */
BZPage.prototype.parseMailto = function parseMailto(aElement) {
    var emailStr = "", hrefStr = "";
    if (aElement) {
        hrefStr = decodeURIComponent(aElement.getAttribute("href"));
        emailStr = hrefStr.split(":")[1];
        if (emailStr === undefined) {
            var params = util.getParamsFromURL("https://" + this.hostname + "/" + hrefStr);
            emailStr = decodeURI(params['login']);
        }
        return emailStr;
    }
    return null;
};

/**
 * Get the current email of the reporter of the bug.
 *
 * @return string
 */
BZPage.prototype.getReporter = function getReporter () {
    var reporterElement = this.doc.
        querySelector("#bz_show_bug_column_2 > table .vcard:first-of-type > a");
    return this.parseMailto(reporterElement);
};

/**
 * Get the current version of the Fedora release ... even if changed meanwhile
 * by bug triager.
 *
 * @return string (integer for released Fedora, float for RHEL, rawhide)
 */
BZPage.prototype.getVersion = function getVersion () {
    var verStr = this.getOptionValue("version").toLowerCase();
    var verNo = 0;
    if (/rawhide/.test(verStr)) {
        verNo = 999;
    } else {
        verNo = Number(verStr);
    }
    return verNo;
};

BZPage.prototype.commentsWalker = function commentsWalker (fce) {
    var comments = this.doc.getElementById("comments").getElementsByClassName(
            "bz_comment");
    Array.forEach(comments, function(item) {
        fce(item);
    }, this);
};

/**
 * Set background color of all comments made by reporter in ReporterColor color
 *
 */
BZPage.prototype.checkComments = function checkComments () {
    var that = this;
    this.commentsWalker(function(x) {
        var email = that.parseMailto(x.getElementsByClassName("vcard")[0]
                .getElementsByTagName("a")[0]);
        if (new RegExp(that.reporter).test(email)) {
            x.style.backgroundColor = that.ReporterColor.toString();
        }
    });
};

BZPage.prototype.collectComments = function collectComments () {
    var outStr = "";
    this.commentsWalker(function(x) {
        outStr += x.getElementsByTagName("pre")[0].textContent + "\n";
    });
    return outStr.trim();
};


/**
 * Select option with given value on the <SELECT> element with given id.
 *
 * Also execute change HTMLEvent, so that the form behaves accordingly.
 *
 * @param id
 * @param label
 * @return none
 *
 */
BZPage.prototype.selectOption = function selectOption (id, label, fireEvent) {
    if (!fireEvent) {
        fireEvent = true;
    }
    var sel = this.doc.getElementById(id);
    sel.value = label;
    if (fireEvent) {
        var intEvent = this.doc.createEvent("HTMLEvents");
        intEvent.initEvent("change", true, true);
        sel.dispatchEvent(intEvent);
    }
};

BZPage.prototype.selectOptionByLabel = function selectOptionByLabel(id, label, fireEvent) {
    if (!fireEvent) {
        fireEvent = true;
    }
    var sel = this.doc.getElementById(id);
    var labelRE = new RegExp(label.trim());
    var ourOption = Array.filter(sel.options, function (op) {
        return op.textContent.trim() == label;
    }, this);

    if (ourOption[0]) {
        sel.value = ourOption[0].value;
    }

    if (fireEvent) {
        var intEvent = this.doc.createEvent("HTMLEvents");
        intEvent.initEvent("change", true, true);
        sel.dispatchEvent(intEvent);
    }
};

/**
 * Send mouse click to the specified element
 *
 * @param String ID of the element to send mouseclick to
 * @return None
 */
BZPage.prototype.clickMouse = function clickMouse (targetID) {
    var localEvent = this.doc.createEvent("MouseEvents");
    localEvent.initMouseEvent("click", true, true, this.doc.defaultView, 0, 0,
            0, 0, 0, false, false, false, false, 0, null);
    this.doc.getElementById(targetID).dispatchEvent(localEvent);
};

/**
 * Add object to the text box (comment box or status whiteboard)
 *
 * @param id String with the id of the element
 * @param stuff String/Array to be added to the comment box
 *
 * @return none
 */
BZPage.prototype.addStuffToTextBox = function addStuffToTextBox (id, stuff) {
    var textBox = this.doc.getElementById(id);
    if (textBox.tagName.toLowerCase() === "textarea") {
        stuff = textBox.value ? "\n\n" + stuff : stuff;
        textBox.value += stuff;
    } else {
        textBox.value = util.addCSVValue(textBox.value,stuff);
    }
};

/**
 * Remove a keyword from the element if it is there
 *
 * @param id String with the id of the element
 * @param stuff String/Array with keyword(s) to be removed
 */
BZPage.prototype.removeStuffFromTextBox = function removeStuffFromTextBox (id, stuff) {
    var changedElement = this.getElementById(id);
    changedElement.value = util.removeCSVValue(changedElement.value,stuff);
};

/**
 * generalized hasKeyword ... search in the value of the box with given id
 *
 * @param id String with ID of the element we want to check
 * @param str String to be searched for
 * @return Boolean found?
 */
BZPage.prototype.idContainsWord = function idContainsWord (id, str) {
    var kwd = "";
    try {
        kwd = this.doc.getElementById(id).value;
    } catch (e) {
        // For those who don't have particular element at all or if it is empty
        return false;
    }
    return (kwd.trim().indexOf(str) !== -1);
};

/**
 * Check for the presence of a keyword
 *
 * @param str String with the keyword
 * @return Boolean
 */
BZPage.prototype.hasKeyword = function hasKeyword (str) {
    return (this.idContainsWord('keywords', str));
};

/**
 *
 */
BZPage.prototype.getOptionValue = function getOptionValue (id) {
    // Some special bugs don't have version for example
    var element = this.doc.getElementById(id);
    if (element) {
        return element.value;
    } else {
        console.error("Failed to find element with id = " + id);
        return "#NA";
    }
};

/**
 * Set the bug to NEEDINFO state
 *
 * Working function.
 * @return none
 * @todo TODO we may extend this to general setNeedinfo function
 * with parameter [reporter|assignee|general-email-address]
 */
BZPage.prototype.setNeedinfoReporter = function setNeedinfoReporter () {
    this.clickMouse("needinfo");
    this.selectOption("needinfo_role", "reporter");
};

/**
 *
 */
BZPage.prototype.getOwner = function getOwner () {
    var priorityParent = this.doc.querySelector("label[for~='target_milestone']")
        .parentNode.parentNode.parentNode;
    var assigneeAElement = priorityParent.querySelector("tr:nth-of-type(1) a.email");
    return this.parseMailto(assigneeAElement);
};

/**
 * Get login of the currently logged-in user.
 *
 * @return String with the login name of the currently logged-in user
 */
BZPage.prototype.getLogin = function getLogin () {
    var lastLIElement = this.doc.querySelector("#header ul.links li:last-of-type");
    var loginArr = lastLIElement.textContent.split("\n");
    var loginStr = loginArr[loginArr.length - 1].trim();
    return loginStr;
};

/**
 * Return maintainer which is per default by bugzilla
 * (which is not necessarily the one who is default maintainer per component)
 *
 * @return String with the maintainer's email address
 */
BZPage.prototype.getDefaultBugzillaMaintainer = function getDefaultBugzillaMaintainer (component) {
    var address = util.filterByRegexp(this.defBugzillaMaintainerArr, component);
    return address;
};

/**
 * collect the list of attachments in a structured format
 *
 * @return Array of arrays, one for each attachments;
 * each record has string name of the attachment, integer its id number,
 *         string of MIME type, integer of size in kilobytes, and the whole
 *         element itself
 */
BZPage.prototype.getAttachments = function getAttachments () {
    var outAtts = [];
    var atts = this.doc.getElementById("attachment_table")
            .getElementsByTagName("tr");
    for ( var i = 1, ii = atts.length - 1; i < ii; i++) {
        outAtts.push(this.parseAttachmentLine(atts[i]));
    }
    return outAtts;
};

/**
 * returns password from the current storage, or if there isn't
 * one, then it will ask user for it.
 *
 * @return String with the password
 */
BZPage.prototype.getPassword = function getPassword (login) {
    var passPrompt = "Enter your Bugzilla password for fixing MIME attachment types";
    var switchPrompt = "Do you want to switch off features requiring password completely?";
    var prefName = BTSPrefNS+"withoutPassowrd";
    var domain = this.win.location.protocol + "//" + this.hostname;

    var pass = passUtils.getPassword(login, domain, BTSPassRealm);
    // pass === null means no appropriate password in the storage
    if (!preferences.get(prefName,false) && (pass === null)) {
        var passwordText = prompts.promptPassword(passPrompt);
        if (passwordText && passwordText.length > 0) {
            passUtils.setLogin(login, passwordText, domain,
                BTSPassRealm);
            return passwordText;
        } else {
            var switchOff = prompts.promptYesNoCancel(switchPrompt);
            if (switchOff) {
                preferences.set(prefName,true);
            }
            return null;
        }
    } else {
        return pass;
    }
};

/**
 *
 */
BZPage.prototype.setUpLogging = function setUpLogging () {
    // Protection against double-call
    if (this.doc.getElementById("generateTSButton")) {
        return ;
    }

    // For adding additional buttons to the top toolbar
    var additionalButtons = this.doc.querySelector("#bugzilla-body *.related_actions");
    var that = this;

    // logging all submits for timesheet
    if (!this.submitHandlerInstalled) {
        this.doc.forms.namedItem("changeform").addEventListener("submit",function (evt) {
            var resp = that.log.addLogRecord(that);
            if (resp === null) {
                evt.stopPropagation();
                evt.preventDefault();
            }
        }, false);
        this.submitHandlerInstalled = true;
    }

    var generateTimeSheetUI = this.doc.createElement("li");
    generateTimeSheetUI.innerHTML = "\u00A0-\u00A0<a href='#' id='generateTSButton'>"
            + "Generate TS</a>";
    additionalButtons.appendChild(generateTimeSheetUI);
    this.doc.getElementById("generateTSButton").addEventListener(
            "click",
            function(evt) {
                that.log.createBlankPage.call(that.log, "TimeSheet",
                        that.log.generateTimeSheet);
                evt.stopPropagation();
                evt.preventDefault();
            }, false);

    var ImportTimeSheetUI = this.doc.createElement("li");
    ImportTimeSheetUI.innerHTML = "\u00A0-\u00A0<a href='#' id='importTSButton'>"
            + "Import TS</a>";
    additionalButtons.appendChild(ImportTimeSheetUI);
    this.doc.getElementById("importTSButton").addEventListener(
            "click",
            function(evt) {
                var otherTS = {}, thisTS = that.log.store;

                jsonPaths = prompts.promptFileOpenPicker(that.win);
                if (fileMod.exists(jsonPaths)) {
                    otherTS = JSON.parse(fileMod.read(jsonPaths));
                    if (otherTS.logs) {
                        for (var rec in otherTS.logs) {
                            thisTS[rec] = otherTS.logs[rec];
                        }
                    } else {
                        console.error("This is not a log file!");
                    }
                } else {
                    console.error("File " + jsonPaths + " doesn't exist!");
                }
            }, false);

    var clearLogsUI = this.doc.createElement("li");
    clearLogsUI.innerHTML = "\u00A0-\u00A0<a href='#' id='clearLogs'>"
            + "Clear TS</a>";
    additionalButtons.appendChild(clearLogsUI);
    var clearLogAElem = this.doc.getElementById("clearLogs");
    clearLogAElem.addEventListener("click", function() {
        that.log.store = {};
        this.style.color = that.log.EmptyLogsColor;
        this.style.fontWeight = "normal";
        console.log("this.store wiped out!");
    }, false);

    if (this.log.store.length > 0) {
        clearLogAElem.style.color = this.log.FullLogsColor;
        clearLogAElem.style.fontWeight = "bolder";
    } else {
        clearLogAElem.style.color = this.log.EmptyLogsColor;
        clearLogAElem.style.fontWeight = "normal";
    }
};

BZPage.prototype.getSelectionOrClipboard = function getSelectionOrClipboard () {
    var text = selection.text;
    if (!text) {
        text = clip.get();
    }
    return text;
};

/**
 * Opens a new tab with a query for the given text in the selected component
 *
 * @param text to be searched for
 * @param component String with the component name (maybe latter regexp?)
 * @param product (optional) string with the product name, if undefined,
 *        search in all products
 * @return None
 *
 */
BZPage.prototype.queryInNewTab = function(text, component, product) {
    var urlStr = "https://bugzilla.redhat.com/buglist.cgi?query_format=advanced";
    if (product) {
        urlStr += "&product=" + product.trim();
    }
    if (component) {
        urlStr += "&field0-0-0=component&type0-0-0=substring&value0-0-0="
                + component.trim();
    }
    // using more complicated query tables here, because they can be more easily
    // edited
    // for further investigative searches
    if (text) {
        text = encodeURIComponent(text.trim());
        var searchText = "&field1-0-0=longdesc&type1-0-0=substring&value1-0-0="
                + text
                + "&field1-0-1=attach_data.thedata&type1-0-1=substring&value1-0-1="
                + text
                + "&field1-0-2=status_whiteboard&type1-0-2=substring&value1-0-2="
                + text;
        urlStr += searchText;
        tabs.open({
            url: urlStr,
            inBackground: true,
            onOpen: function (t) {
                tabs.activeTab = t;
            }
        });
    }
};

/**
 * Get the text to search for and prepare other things for the real executive
 * function this.queryInNewTab, and run it.
 */
BZPage.prototype.queryForSelection = function() {
    var text = this.getSelectionOrClipboard();
    if (text) {
        this.queryInNewTab(text, this.component);
    }
};

/**
 * adds a person to the CC list, if it isn't already there
 *
 * @param who String with email address or "self" if the current user
 * of the bugzilla should be added
 */
BZPage.prototype.addToCCList = function addToCCList (who) {
    if (!who) {
        return ;
    }
    if (who === "self") {
        this.doc.getElementById("addselfcc").checked = true;
    } else {
        this.clickMouse("cc_edit_area_showhide");
        if (!util.isInList(who, this.CCList)) {
            this.addStuffToTextBox("newcc",who);
        }
    }
};

/**
 * a collect a list of emails on CC list
 *
 * @return Array with email addresses as Strings.
 */
BZPage.prototype.getCCList = function getCCList () {
    var CCListSelect = this.doc.getElementById("cc");
    var outCCList = [];
    if (CCListSelect) {
        outCCList = Array.map(CCListSelect.options, function(item) {
            return item.value;
        });
    }
    return outCCList;
};

// exports.BZPage = apiUtils.publicConstructor(BZPage);
exports.BZPage = BZPage;